diff --git a/electron-builder.yml b/electron-builder.yml index 8164bb2fd9..59de8a4f50 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -91,6 +91,7 @@ afterSign: scripts/notarize.js artifactBuildCompleted: scripts/artifact-build-completed.js releaseInfo: releaseNotes: | + ⚠️ 注意:升级前请备份数据,否则将无法降级 重构消息结构,支持不同类型消息按时间顺序显示 智能体支持导入和导出 快捷面板增加网络搜索引擎选择 diff --git a/package.json b/package.json index 16d148f658..8531b1bb1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CherryStudio", - "version": "1.3.4", + "version": "1.3.5", "private": true, "description": "A powerful AI assistant for producer.", "main": "./out/main/index.js", diff --git a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx index 212ee53ee9..476c67e57e 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx @@ -5,7 +5,7 @@ import { lightbulbVariants } from '@renderer/utils/motionVariants' import { Collapse, message as antdMessage, Tooltip } from 'antd' import { Lightbulb } from 'lucide-react' import { motion } from 'motion/react' -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -20,8 +20,6 @@ const ThinkingBlock: React.FC = ({ block }) => { const { t } = useTranslation() const { messageFont, fontSize, thoughtAutoCollapse } = useSettings() const [activeKey, setActiveKey] = useState<'thought' | ''>(thoughtAutoCollapse ? '' : 'thought') - const [thinkingTime, setThinkingTime] = useState(block.thinking_millsec || 0) - const intervalId = useRef(null) const isThinking = useMemo(() => block.status === MessageBlockStatus.STREAMING, [block.status]) @@ -55,28 +53,6 @@ const ThinkingBlock: React.FC = ({ block }) => { } }, [block.content, t]) - // FIXME: 这里统计的和请求处统计的有一定误差 - useEffect(() => { - if (isThinking) { - intervalId.current = setInterval(() => { - setThinkingTime((prev) => prev + 100) - }, 100) - } else if (intervalId.current) { - // 立即清除计时器 - clearInterval(intervalId.current) - intervalId.current = null - } - - return () => { - if (intervalId.current) { - clearInterval(intervalId.current) - intervalId.current = null - } - } - }, [isThinking]) - - const thinkingTimeSeconds = useMemo(() => (thinkingTime / 1000).toFixed(1), [thinkingTime]) - if (!block.content) { return null } @@ -101,9 +77,7 @@ const ThinkingBlock: React.FC = ({ block }) => { - {t(isThinking ? 'chat.thinking' : 'chat.deeply_thought', { - seconds: thinkingTimeSeconds - })} + {/* {isThinking && } */} {!isThinking && ( @@ -134,6 +108,41 @@ const ThinkingBlock: React.FC = ({ block }) => { ) } +const ThinkingTimeSeconds = memo( + ({ blockThinkingTime, isThinking }: { blockThinkingTime?: number; isThinking: boolean }) => { + const { t } = useTranslation() + + const [thinkingTime, setThinkingTime] = useState(blockThinkingTime || 0) + + // FIXME: 这里统计的和请求处统计的有一定误差 + useEffect(() => { + let timer: NodeJS.Timeout | null = null + if (isThinking) { + timer = setInterval(() => { + setThinkingTime((prev) => prev + 100) + }, 100) + } else if (timer) { + // 立即清除计时器 + clearInterval(timer) + timer = null + } + + return () => { + if (timer) { + clearInterval(timer) + timer = null + } + } + }, [isThinking]) + + const thinkingTimeSeconds = useMemo(() => (thinkingTime / 1000).toFixed(1), [thinkingTime]) + + return t(isThinking ? 'chat.thinking' : 'chat.deeply_thought', { + seconds: thinkingTimeSeconds + }) + } +) + const CollapseContainer = styled(Collapse)` margin-bottom: 15px; ` diff --git a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx index ad93b8fa38..d58cf0c01a 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx @@ -237,6 +237,7 @@ const DisplaySettings: FC = () => { minHeight: 200, fontFamily: 'monospace' }} + spellCheck={false} /> diff --git a/src/renderer/src/providers/AiProvider/GeminiProvider.ts b/src/renderer/src/providers/AiProvider/GeminiProvider.ts index 46ea431f6f..222abeaaf1 100644 --- a/src/renderer/src/providers/AiProvider/GeminiProvider.ts +++ b/src/renderer/src/providers/AiProvider/GeminiProvider.ts @@ -287,7 +287,8 @@ export default class GeminiProvider extends BaseProvider { if (reasoningEffort === undefined) { return { thinkingConfig: { - includeThoughts: false + includeThoughts: false, + thinkingBudget: 0 } as ThinkingConfig } } @@ -921,7 +922,8 @@ export default class GeminiProvider extends BaseProvider { config = { ...config, thinkingConfig: { - includeThoughts: false + includeThoughts: false, + thinkingBudget: 0 } as ThinkingConfig } } diff --git a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts index 0c2ef2f5c2..545c92ed3e 100644 --- a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts @@ -10,6 +10,7 @@ import { isSupportedReasoningEffortModel, isSupportedReasoningEffortOpenAIModel, isSupportedThinkingTokenClaudeModel, + isSupportedThinkingTokenGeminiModel, isSupportedThinkingTokenModel, isSupportedThinkingTokenQwenModel, isVisionModel, @@ -258,6 +259,19 @@ export default class OpenAIProvider extends BaseOpenAIProvider { return { thinking: { type: 'disabled' } } } + if (isSupportedThinkingTokenGeminiModel(model)) { + // openrouter没有提供一个不推理的选项,先隐藏 + if (this.provider.id === 'openrouter') { + return { reasoning: { maxTokens: 0, exclude: true } } + } + return { + thinkingConfig: { + includeThoughts: false, + thinkingBudget: 0 + } + } + } + return {} } const effortRatio = EFFORT_RATIO[reasoningEffort] @@ -313,6 +327,16 @@ export default class OpenAIProvider extends BaseOpenAIProvider { } } + // Gemini models + if (isSupportedThinkingTokenGeminiModel(model)) { + return { + thinkingConfig: { + thinkingBudget: budgetTokens, + includeThoughts: true + } + } + } + // Default case: no special thinking settings return {} } @@ -718,9 +742,17 @@ export default class OpenAIProvider extends BaseOpenAIProvider { const usage = chunk.usage const originalFinishDelta = chunk.delta const originalFinishRawChunk = chunk.chunk - if (!isEmpty(finishReason)) { - onChunk({ type: ChunkType.TEXT_COMPLETE, text: content }) + if (content) { + onChunk({ type: ChunkType.TEXT_COMPLETE, text: content }) + } + if (thinkingContent) { + onChunk({ + type: ChunkType.THINKING_COMPLETE, + text: thinkingContent, + thinking_millsec: new Date().getTime() - time_first_token_millsec + }) + } if (usage) { finalUsage.completion_tokens += usage.completion_tokens || 0 finalUsage.prompt_tokens += usage.prompt_tokens || 0 @@ -812,7 +844,6 @@ export default class OpenAIProvider extends BaseOpenAIProvider { if (toolResults.length) { await processToolResults(toolResults, idx) } - onChunk({ type: ChunkType.BLOCK_COMPLETE, response: { diff --git a/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts b/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts index b0005d27fd..af66a835bc 100644 --- a/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts @@ -593,7 +593,7 @@ export abstract class BaseOpenAIProvider extends BaseProvider { onChunk({ type: ChunkType.LLM_WEB_SEARCH_COMPLETE, llm_web_search: { - source: WebSearchSource.OPENAI, + source: WebSearchSource.OPENAI_RESPONSE, results: chunk.part.annotations } }) diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index 86707a938c..c2c51b25e4 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -622,6 +622,14 @@ const fetchAndProcessAssistantResponseImpl = async ( const contextForUsage = userMsgIndex !== -1 ? orderedMsgs.slice(0, userMsgIndex + 1) : [] const finalContextWithAssistant = [...contextForUsage, finalAssistantMsg] + if (lastBlockId) { + const changes: Partial = { + status: MessageBlockStatus.SUCCESS + } + dispatch(updateOneBlock({ id: lastBlockId, changes })) + saveUpdatedBlockToDB(lastBlockId, assistantMsgId, topicId, getState) + } + // 更新topic的name autoRenameTopic(assistant, topicId) @@ -734,7 +742,6 @@ export const loadTopicMessagesThunk = try { const topic = await db.topics.get(topicId) - if (!topic) { await db.topics.add({ id: topicId, messages: [] }) } diff --git a/src/renderer/src/utils/mcp-tools.ts b/src/renderer/src/utils/mcp-tools.ts index 4c446ffa78..8bdc499135 100644 --- a/src/renderer/src/utils/mcp-tools.ts +++ b/src/renderer/src/utils/mcp-tools.ts @@ -412,6 +412,7 @@ export function upsertMCPToolResponse( const cur = { ...results[index], response: resp.response, + arguments: resp.arguments, status: resp.status } results[index] = cur