Merge remote-tracking branch 'origin/main' into feat/agents-new
This commit is contained in:
@@ -178,14 +178,13 @@ export class AiSdkToChunkAdapter {
|
||||
final.reasoningContent += chunk.text || ''
|
||||
this.onChunk({
|
||||
type: ChunkType.THINKING_DELTA,
|
||||
text: final.reasoningContent || '',
|
||||
thinking_millsec: (chunk.providerMetadata?.metadata?.thinking_millsec as number) || 0
|
||||
text: final.reasoningContent || ''
|
||||
})
|
||||
break
|
||||
case 'reasoning-end':
|
||||
this.onChunk({
|
||||
type: ChunkType.THINKING_COMPLETE,
|
||||
text: (chunk.providerMetadata?.metadata?.thinking_content as string) || final.reasoningContent
|
||||
text: final.reasoningContent || ''
|
||||
})
|
||||
final.reasoningContent = ''
|
||||
break
|
||||
|
||||
@@ -5,7 +5,6 @@ import { getEnableDeveloperMode } from '@renderer/hooks/useSettings'
|
||||
import { Assistant } from '@renderer/types'
|
||||
|
||||
import { AiSdkMiddlewareConfig } from '../middleware/AiSdkMiddlewareBuilder'
|
||||
import reasoningTimePlugin from './reasoningTimePlugin'
|
||||
import { searchOrchestrationPlugin } from './searchOrchestrationPlugin'
|
||||
import { createTelemetryPlugin } from './telemetryPlugin'
|
||||
|
||||
@@ -39,9 +38,9 @@ export function buildPlugins(
|
||||
}
|
||||
|
||||
// 3. 推理模型时添加推理插件
|
||||
if (middlewareConfig.enableReasoning) {
|
||||
plugins.push(reasoningTimePlugin)
|
||||
}
|
||||
// if (middlewareConfig.enableReasoning) {
|
||||
// plugins.push(reasoningTimePlugin)
|
||||
// }
|
||||
|
||||
// 4. 启用Prompt工具调用时添加工具插件
|
||||
if (middlewareConfig.isPromptToolUse) {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1804,5 +1804,19 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
||||
provider: 'aionly',
|
||||
group: 'gemini'
|
||||
}
|
||||
],
|
||||
longcat: [
|
||||
{
|
||||
id: 'LongCat-Flash-Chat',
|
||||
name: 'LongCat Flash Chat',
|
||||
provider: 'longcat',
|
||||
group: 'LongCat'
|
||||
},
|
||||
{
|
||||
id: 'LongCat-Flash-Thinking',
|
||||
name: 'LongCat Flash Thinking',
|
||||
provider: 'longcat',
|
||||
group: 'LongCat'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import InfiniProviderLogo from '@renderer/assets/images/providers/infini.png'
|
||||
import JinaProviderLogo from '@renderer/assets/images/providers/jina.png'
|
||||
import LanyunProviderLogo from '@renderer/assets/images/providers/lanyun.png'
|
||||
import LMStudioProviderLogo from '@renderer/assets/images/providers/lmstudio.png'
|
||||
import LongCatProviderLogo from '@renderer/assets/images/providers/longcat.png'
|
||||
import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png'
|
||||
import MistralProviderLogo from '@renderer/assets/images/providers/mistral.png'
|
||||
import ModelScopeProviderLogo from '@renderer/assets/images/providers/modelscope.png'
|
||||
@@ -630,6 +631,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
||||
models: SYSTEM_MODELS['poe'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
longcat: {
|
||||
id: 'longcat',
|
||||
name: 'LongCat',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.longcat.chat/openai',
|
||||
models: SYSTEM_MODELS.longcat,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
}
|
||||
} as const
|
||||
|
||||
@@ -692,7 +703,8 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
|
||||
'new-api': NewAPIProviderLogo,
|
||||
'aws-bedrock': AwsProviderLogo,
|
||||
poe: 'poe', // use svg icon component
|
||||
aionly: AiOnlyProviderLogo
|
||||
aionly: AiOnlyProviderLogo,
|
||||
longcat: LongCatProviderLogo
|
||||
} as const
|
||||
|
||||
export function getProviderLogo(providerId: string) {
|
||||
@@ -1298,6 +1310,17 @@ export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
|
||||
docs: 'https://www.aiionly.com/document',
|
||||
models: 'https://www.aiionly.com'
|
||||
}
|
||||
},
|
||||
longcat: {
|
||||
api: {
|
||||
url: 'https://api.longcat.chat/openai'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://longcat.chat',
|
||||
apiKey: 'https://longcat.chat/platform/api_keys',
|
||||
docs: 'https://longcat.chat/platform/docs/zh/',
|
||||
models: 'https://longcat.chat/platform/docs/zh/APIDocs.html'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
|
||||
import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage'
|
||||
import { Collapse, message as antdMessage, Tooltip } from 'antd'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@@ -105,30 +105,37 @@ const ThinkingBlock: React.FC<Props> = ({ block }) => {
|
||||
const ThinkingTimeSeconds = memo(
|
||||
({ blockThinkingTime, isThinking }: { blockThinkingTime: number; isThinking: boolean }) => {
|
||||
const { t } = useTranslation()
|
||||
// const [thinkingTime, setThinkingTime] = useState(blockThinkingTime || 0)
|
||||
const [displayTime, setDisplayTime] = useState(blockThinkingTime)
|
||||
|
||||
// FIXME: 这里统计的和请求处统计的有一定误差
|
||||
// useEffect(() => {
|
||||
// let timer: NodeJS.Timeout | null = null
|
||||
// if (isThinking) {
|
||||
// timer = setInterval(() => {
|
||||
// setThinkingTime((prev) => prev + 100)
|
||||
// }, 100)
|
||||
// } else if (timer) {
|
||||
// // 立即清除计时器
|
||||
// clearInterval(timer)
|
||||
// timer = null
|
||||
// }
|
||||
const timer = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
// return () => {
|
||||
// if (timer) {
|
||||
// clearInterval(timer)
|
||||
// timer = null
|
||||
// }
|
||||
// }
|
||||
// }, [isThinking])
|
||||
useEffect(() => {
|
||||
if (isThinking) {
|
||||
if (!timer.current) {
|
||||
timer.current = setInterval(() => {
|
||||
setDisplayTime((prev) => prev + 100)
|
||||
}, 100)
|
||||
}
|
||||
} else {
|
||||
if (timer.current) {
|
||||
clearInterval(timer.current)
|
||||
timer.current = null
|
||||
}
|
||||
setDisplayTime(blockThinkingTime)
|
||||
}
|
||||
|
||||
const thinkingTimeSeconds = useMemo(() => (blockThinkingTime / 1000).toFixed(1), [blockThinkingTime])
|
||||
return () => {
|
||||
if (timer.current) {
|
||||
clearInterval(timer.current)
|
||||
timer.current = null
|
||||
}
|
||||
}
|
||||
}, [isThinking, blockThinkingTime])
|
||||
|
||||
const thinkingTimeSeconds = useMemo(
|
||||
() => ((displayTime < 1000 ? 100 : displayTime) / 1000).toFixed(1),
|
||||
[displayTime]
|
||||
)
|
||||
|
||||
return isThinking
|
||||
? t('chat.thinking', {
|
||||
|
||||
@@ -235,13 +235,12 @@ describe('ThinkingBlock', () => {
|
||||
renderThinkingBlock(thinkingBlock)
|
||||
|
||||
const activeTimeText = getThinkingTimeText()
|
||||
expect(activeTimeText).toHaveTextContent('1.0s')
|
||||
expect(activeTimeText).toHaveTextContent('Thinking...')
|
||||
})
|
||||
|
||||
it('should handle extreme thinking times correctly', () => {
|
||||
const testCases = [
|
||||
{ thinking_millsec: 0, expectedTime: '0.0s' },
|
||||
{ thinking_millsec: 0, expectedTime: '0.1s' }, // New logic: values < 1000ms display as 0.1s
|
||||
{ thinking_millsec: 86400000, expectedTime: '86400.0s' }, // 1 day
|
||||
{ thinking_millsec: 259200000, expectedTime: '259200.0s' } // 3 days
|
||||
]
|
||||
|
||||
@@ -58,7 +58,7 @@ const MessageMcpTool: FC<Props> = ({ block }) => {
|
||||
const [progress, setProgress] = useState<number>(0)
|
||||
const { setTimeoutTimer } = useTimer()
|
||||
|
||||
const toolResponse = block.metadata?.rawMcpToolResponse
|
||||
const toolResponse = block.metadata?.rawMcpToolResponse as MCPToolResponse
|
||||
|
||||
const { id, tool, status, response } = toolResponse as MCPToolResponse
|
||||
const isPending = status === 'pending'
|
||||
|
||||
@@ -37,12 +37,13 @@ const isAgentTool = (toolName: string) => {
|
||||
|
||||
const ChooseTool = (toolResponse: NormalToolResponse): React.ReactNode | null => {
|
||||
let toolName = toolResponse.tool.name
|
||||
const toolType = toolResponse.tool.type
|
||||
if (toolName.startsWith(prefix)) {
|
||||
toolName = toolName.slice(prefix.length)
|
||||
switch (toolName) {
|
||||
case 'web_search':
|
||||
case 'web_search_preview':
|
||||
return <MessageWebSearchToolTitle toolResponse={toolResponse} />
|
||||
return toolType === 'provider' ? null : <MessageWebSearchToolTitle toolResponse={toolResponse} />
|
||||
case 'knowledge_search':
|
||||
return <MessageKnowledgeSearchToolTitle toolResponse={toolResponse} />
|
||||
case 'memory_search':
|
||||
@@ -58,7 +59,7 @@ const ChooseTool = (toolResponse: NormalToolResponse): React.ReactNode | null =>
|
||||
|
||||
export default function MessageTool({ block }: Props) {
|
||||
// FIXME: 语义错误,这里已经不是 MCP tool 了,更改rawMcpToolResponse需要改用户数据, 所以暂时保留
|
||||
const toolResponse = block.metadata?.rawMcpToolResponse
|
||||
const toolResponse = block.metadata?.rawMcpToolResponse as NormalToolResponse
|
||||
|
||||
if (!toolResponse) return null
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) =>
|
||||
|
||||
// 内部维护的状态
|
||||
let thinkingBlockId: string | null = null
|
||||
let _thinking_millsec = 0
|
||||
let thinking_millsec_now: number = 0
|
||||
|
||||
return {
|
||||
onThinkingStart: async () => {
|
||||
@@ -24,27 +24,27 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) =>
|
||||
type: MessageBlockType.THINKING,
|
||||
content: '',
|
||||
status: MessageBlockStatus.STREAMING,
|
||||
thinking_millsec: _thinking_millsec
|
||||
thinking_millsec: 0
|
||||
}
|
||||
thinkingBlockId = blockManager.initialPlaceholderBlockId!
|
||||
blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true)
|
||||
} else if (!thinkingBlockId) {
|
||||
const newBlock = createThinkingBlock(assistantMsgId, '', {
|
||||
status: MessageBlockStatus.STREAMING,
|
||||
thinking_millsec: _thinking_millsec
|
||||
thinking_millsec: 0
|
||||
})
|
||||
thinkingBlockId = newBlock.id
|
||||
await blockManager.handleBlockTransition(newBlock, MessageBlockType.THINKING)
|
||||
}
|
||||
thinking_millsec_now = performance.now()
|
||||
},
|
||||
|
||||
onThinkingChunk: async (text: string, thinking_millsec?: number) => {
|
||||
_thinking_millsec = thinking_millsec || 0
|
||||
onThinkingChunk: async (text: string) => {
|
||||
if (thinkingBlockId) {
|
||||
const blockChanges: Partial<MessageBlock> = {
|
||||
content: text,
|
||||
status: MessageBlockStatus.STREAMING,
|
||||
thinking_millsec: _thinking_millsec
|
||||
status: MessageBlockStatus.STREAMING
|
||||
// thinking_millsec: performance.now() - thinking_millsec_now
|
||||
}
|
||||
blockManager.smartBlockUpdate(thinkingBlockId, blockChanges, MessageBlockType.THINKING)
|
||||
}
|
||||
@@ -52,14 +52,15 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) =>
|
||||
|
||||
onThinkingComplete: (finalText: string) => {
|
||||
if (thinkingBlockId) {
|
||||
const now = performance.now()
|
||||
const changes: Partial<MessageBlock> = {
|
||||
content: finalText,
|
||||
status: MessageBlockStatus.SUCCESS,
|
||||
thinking_millsec: _thinking_millsec
|
||||
thinking_millsec: now - thinking_millsec_now
|
||||
}
|
||||
blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true)
|
||||
thinkingBlockId = null
|
||||
_thinking_millsec = 0
|
||||
thinking_millsec_now = 0
|
||||
} else {
|
||||
logger.warn(
|
||||
`[onThinkingComplete] Received thinking.complete but last block was not THINKING (was ${blockManager.lastBlockType}) or lastBlockId is null.`
|
||||
|
||||
@@ -2570,6 +2570,7 @@ const migrateConfig = {
|
||||
'158': (state: RootState) => {
|
||||
try {
|
||||
state.llm.providers = state.llm.providers.filter((provider) => provider.id !== 'cherryin')
|
||||
addProvider(state, 'longcat')
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 158 error', error as Error)
|
||||
|
||||
@@ -425,7 +425,10 @@ describe('streamCallback Integration Tests', () => {
|
||||
expect(thinkingBlock).toBeDefined()
|
||||
expect(thinkingBlock?.content).toBe('Final thoughts')
|
||||
expect(thinkingBlock?.status).toBe(MessageBlockStatus.SUCCESS)
|
||||
expect((thinkingBlock as any)?.thinking_millsec).toBe(3000)
|
||||
// thinking_millsec 现在是本地计算的,只验证它存在且是一个合理的数字
|
||||
expect((thinkingBlock as any)?.thinking_millsec).toBeDefined()
|
||||
expect(typeof (thinkingBlock as any)?.thinking_millsec).toBe('number')
|
||||
expect((thinkingBlock as any)?.thinking_millsec).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
it('should handle tool call flow', async () => {
|
||||
|
||||
@@ -334,7 +334,8 @@ export const SystemProviderIds = {
|
||||
voyageai: 'voyageai',
|
||||
'aws-bedrock': 'aws-bedrock',
|
||||
poe: 'poe',
|
||||
aionly: 'aionly'
|
||||
aionly: 'aionly',
|
||||
longcat: 'longcat'
|
||||
} as const
|
||||
|
||||
export type SystemProviderId = keyof typeof SystemProviderIds
|
||||
|
||||
Reference in New Issue
Block a user