mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-20 15:10:59 +08:00
* refactor: add QuickPanelReservedSymbol * refactor: pass assistant id instead of assistant object * refactor: simplify InputbarTools props * refactor: add root symbol * refactor: merge file panel to attachment button * refactor: extract ActionButton for reuse * chore: update CLAUDE.md * fix: colors * refactor: add more reserved symbols * fix: keep updateAssistant stable * refactor(components): replace styled-components with cn utility in ActionIconButton - Replace styled-components with cn utility from @heroui/react for better maintainability - Add new --icon and --color-icon CSS variables for consistent icon coloring - Simplify button styling using Tailwind CSS classes --------- Co-authored-by: icarus <eurfelux@gmail.com>
207 lines
7.8 KiB
TypeScript
207 lines
7.8 KiB
TypeScript
import { loggerService } from '@logger'
|
||
import {
|
||
getThinkModelType,
|
||
isSupportedReasoningEffortModel,
|
||
isSupportedThinkingTokenModel,
|
||
MODEL_SUPPORTED_OPTIONS,
|
||
MODEL_SUPPORTED_REASONING_EFFORT
|
||
} from '@renderer/config/models'
|
||
import { db } from '@renderer/databases'
|
||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||
import {
|
||
addAssistant,
|
||
addTopic,
|
||
insertAssistant,
|
||
removeAllTopics,
|
||
removeAssistant,
|
||
removeTopic,
|
||
setModel,
|
||
updateAssistant,
|
||
updateAssistants,
|
||
updateAssistantSettings as _updateAssistantSettings,
|
||
updateDefaultAssistant,
|
||
updateTopic,
|
||
updateTopics
|
||
} from '@renderer/store/assistants'
|
||
import { setDefaultModel, setQuickModel, setTranslateModel } from '@renderer/store/llm'
|
||
import { Assistant, AssistantSettings, Model, ThinkingOption, Topic } from '@renderer/types'
|
||
import { uuid } from '@renderer/utils'
|
||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||
import { useTranslation } from 'react-i18next'
|
||
|
||
import { TopicManager } from './useTopic'
|
||
|
||
export function useAssistants() {
|
||
const { t } = useTranslation()
|
||
const { assistants } = useAppSelector((state) => state.assistants)
|
||
const dispatch = useAppDispatch()
|
||
const logger = loggerService.withContext('useAssistants')
|
||
|
||
return {
|
||
assistants,
|
||
updateAssistants: (assistants: Assistant[]) => dispatch(updateAssistants(assistants)),
|
||
addAssistant: (assistant: Assistant) => dispatch(addAssistant(assistant)),
|
||
insertAssistant: (index: number, assistant: Assistant) => dispatch(insertAssistant({ index, assistant })),
|
||
copyAssistant: (assistant: Assistant): Assistant | undefined => {
|
||
if (!assistant) {
|
||
logger.error("assistant doesn't exists.")
|
||
return
|
||
}
|
||
const index = assistants.findIndex((_assistant) => _assistant.id === assistant.id)
|
||
const _assistant: Assistant = { ...assistant, id: uuid(), topics: [getDefaultTopic(assistant.id)] }
|
||
if (index === -1) {
|
||
logger.warn("Origin assistant's id not found. Fallback to addAssistant.")
|
||
dispatch(addAssistant(_assistant))
|
||
} else {
|
||
// 插入到后面
|
||
try {
|
||
dispatch(insertAssistant({ index: index + 1, assistant: _assistant }))
|
||
} catch (e) {
|
||
logger.error('Failed to insert assistant', e as Error)
|
||
window.toast.error(t('message.error.copy'))
|
||
}
|
||
}
|
||
return _assistant
|
||
},
|
||
removeAssistant: (id: string) => {
|
||
dispatch(removeAssistant({ id }))
|
||
const assistant = assistants.find((a) => a.id === id)
|
||
const topics = assistant?.topics || []
|
||
topics.forEach(({ id }) => TopicManager.removeTopic(id))
|
||
}
|
||
}
|
||
}
|
||
|
||
export function useAssistant(id: string) {
|
||
const assistant = useAppSelector((state) => state.assistants.assistants.find((a) => a.id === id) as Assistant)
|
||
const dispatch = useAppDispatch()
|
||
const { defaultModel } = useDefaultModel()
|
||
|
||
const model = useMemo(() => assistant?.model ?? assistant?.defaultModel ?? defaultModel, [assistant, defaultModel])
|
||
if (!model) {
|
||
throw new Error(`Assistant model is not set for assistant with name: ${assistant?.name ?? 'unknown'}`)
|
||
}
|
||
|
||
const assistantWithModel = useMemo(() => ({ ...assistant, model }), [assistant, model])
|
||
|
||
const settingsRef = useRef(assistant?.settings)
|
||
|
||
useEffect(() => {
|
||
settingsRef.current = assistant?.settings
|
||
}, [assistant?.settings])
|
||
|
||
const updateAssistantSettings = useCallback(
|
||
(settings: Partial<AssistantSettings>) => {
|
||
assistant?.id && dispatch(_updateAssistantSettings({ assistantId: assistant.id, settings }))
|
||
},
|
||
[assistant?.id, dispatch]
|
||
)
|
||
|
||
// 当model变化时,同步reasoning effort为模型支持的合法值
|
||
useEffect(() => {
|
||
const settings = settingsRef.current
|
||
if (settings) {
|
||
const currentReasoningEffort = settings.reasoning_effort
|
||
if (isSupportedThinkingTokenModel(model) || isSupportedReasoningEffortModel(model)) {
|
||
const modelType = getThinkModelType(model)
|
||
const supportedOptions = MODEL_SUPPORTED_OPTIONS[modelType]
|
||
if (supportedOptions.every((option) => option !== currentReasoningEffort)) {
|
||
const cache = settings.reasoning_effort_cache
|
||
let fallbackOption: ThinkingOption
|
||
|
||
// 选项不支持时,首先尝试恢复到上次使用的值
|
||
if (cache && supportedOptions.includes(cache)) {
|
||
fallbackOption = cache
|
||
} else {
|
||
// 灵活回退到支持的值
|
||
// 注意:这里假设可用的options不会为空
|
||
const enableThinking = currentReasoningEffort !== undefined
|
||
fallbackOption = enableThinking
|
||
? MODEL_SUPPORTED_REASONING_EFFORT[modelType][0]
|
||
: MODEL_SUPPORTED_OPTIONS[modelType][0]
|
||
}
|
||
|
||
updateAssistantSettings({
|
||
reasoning_effort: fallbackOption === 'off' ? undefined : fallbackOption,
|
||
reasoning_effort_cache: fallbackOption === 'off' ? undefined : fallbackOption,
|
||
qwenThinkMode: fallbackOption === 'off' ? undefined : true
|
||
})
|
||
} else {
|
||
// 对于支持的选项, 不再更新 cache.
|
||
}
|
||
} else {
|
||
// 切换到非思考模型时保留cache
|
||
updateAssistantSettings({
|
||
reasoning_effort: undefined,
|
||
reasoning_effort_cache: currentReasoningEffort,
|
||
qwenThinkMode: undefined
|
||
})
|
||
}
|
||
}
|
||
}, [model, updateAssistantSettings])
|
||
|
||
return {
|
||
assistant: assistantWithModel,
|
||
model,
|
||
addTopic: (topic: Topic) => dispatch(addTopic({ assistantId: assistant.id, topic })),
|
||
removeTopic: (topic: Topic) => {
|
||
TopicManager.removeTopic(topic.id)
|
||
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
||
},
|
||
moveTopic: (topic: Topic, toAssistant: Assistant) => {
|
||
dispatch(addTopic({ assistantId: toAssistant.id, topic: { ...topic, assistantId: toAssistant.id } }))
|
||
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
||
// update topic messages in database
|
||
db.topics
|
||
.where('id')
|
||
.equals(topic.id)
|
||
.modify((dbTopic) => {
|
||
if (dbTopic.messages) {
|
||
dbTopic.messages = dbTopic.messages.map((message) => ({
|
||
...message,
|
||
assistantId: toAssistant.id
|
||
}))
|
||
}
|
||
})
|
||
},
|
||
updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
|
||
updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
|
||
removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })),
|
||
setModel: useCallback(
|
||
(model: Model) => assistant && dispatch(setModel({ assistantId: assistant?.id, model })),
|
||
[assistant, dispatch]
|
||
),
|
||
updateAssistant: useCallback((assistant: Partial<Assistant>) => dispatch(updateAssistant(assistant)), [dispatch]),
|
||
updateAssistantSettings
|
||
}
|
||
}
|
||
|
||
export function useDefaultAssistant() {
|
||
const defaultAssistant = useAppSelector((state) => state.assistants.defaultAssistant)
|
||
const dispatch = useAppDispatch()
|
||
const memoizedTopics = useMemo(() => [getDefaultTopic(defaultAssistant.id)], [defaultAssistant.id])
|
||
|
||
return {
|
||
defaultAssistant: {
|
||
...defaultAssistant,
|
||
topics: memoizedTopics
|
||
},
|
||
updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant }))
|
||
}
|
||
}
|
||
|
||
export function useDefaultModel() {
|
||
const { defaultModel, quickModel, translateModel } = useAppSelector((state) => state.llm)
|
||
const dispatch = useAppDispatch()
|
||
|
||
return {
|
||
defaultModel,
|
||
quickModel,
|
||
translateModel,
|
||
setDefaultModel: (model: Model) => dispatch(setDefaultModel({ model })),
|
||
setQuickModel: (model: Model) => dispatch(setQuickModel({ model })),
|
||
setTranslateModel: (model: Model) => dispatch(setTranslateModel({ model }))
|
||
}
|
||
}
|