mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 18:50:56 +08:00
Merge branch 'main' into develop
This commit is contained in:
commit
47c9465699
@ -91,6 +91,7 @@ afterSign: scripts/notarize.js
|
||||
artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
⚠️ 注意:升级前请备份数据,否则将无法降级
|
||||
重构消息结构,支持不同类型消息按时间顺序显示
|
||||
智能体支持导入和导出
|
||||
快捷面板增加网络搜索引擎选择
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<Props> = ({ 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<NodeJS.Timeout>(null)
|
||||
|
||||
const isThinking = useMemo(() => block.status === MessageBlockStatus.STREAMING, [block.status])
|
||||
|
||||
@ -55,28 +53,6 @@ const ThinkingBlock: React.FC<Props> = ({ 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<Props> = ({ block }) => {
|
||||
<Lightbulb size={18} />
|
||||
</motion.span>
|
||||
<ThinkingText>
|
||||
{t(isThinking ? 'chat.thinking' : 'chat.deeply_thought', {
|
||||
seconds: thinkingTimeSeconds
|
||||
})}
|
||||
<ThinkingTimeSeconds blockThinkingTime={block.thinking_millsec} isThinking={isThinking} />
|
||||
</ThinkingText>
|
||||
{/* {isThinking && <BarLoader color="#9254de" />} */}
|
||||
{!isThinking && (
|
||||
@ -134,6 +108,41 @@ const ThinkingBlock: React.FC<Props> = ({ 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;
|
||||
`
|
||||
|
||||
@ -237,6 +237,7 @@ const DisplaySettings: FC = () => {
|
||||
minHeight: 200,
|
||||
fontFamily: 'monospace'
|
||||
}}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</SettingGroup>
|
||||
</SettingContainer>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@ -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<MessageBlock> = {
|
||||
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: [] })
|
||||
}
|
||||
|
||||
@ -412,6 +412,7 @@ export function upsertMCPToolResponse(
|
||||
const cur = {
|
||||
...results[index],
|
||||
response: resp.response,
|
||||
arguments: resp.arguments,
|
||||
status: resp.status
|
||||
}
|
||||
results[index] = cur
|
||||
|
||||
Loading…
Reference in New Issue
Block a user