diff --git a/src/renderer/src/hooks/useAssistant.ts b/src/renderer/src/hooks/useAssistant.ts index dd2d47b89f..23b538b56a 100644 --- a/src/renderer/src/hooks/useAssistant.ts +++ b/src/renderer/src/hooks/useAssistant.ts @@ -15,7 +15,7 @@ import { updateTopic, updateTopics } from '@renderer/store/assistants' -import { setDefaultModel, setQuickAssistantModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm' +import { setDefaultModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm' import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types' import { useCallback, useMemo } from 'react' @@ -103,17 +103,15 @@ export function useDefaultAssistant() { } export function useDefaultModel() { - const { defaultModel, topicNamingModel, translateModel, quickAssistantModel } = useAppSelector((state) => state.llm) + const { defaultModel, topicNamingModel, translateModel } = useAppSelector((state) => state.llm) const dispatch = useAppDispatch() return { defaultModel, topicNamingModel, translateModel, - quickAssistantModel, setDefaultModel: (model: Model) => dispatch(setDefaultModel({ model })), setTopicNamingModel: (model: Model) => dispatch(setTopicNamingModel({ model })), - setTranslateModel: (model: Model) => dispatch(setTranslateModel({ model })), - setQuickAssistantModel: (model: Model) => dispatch(setQuickAssistantModel({ model })) + setTranslateModel: (model: Model) => dispatch(setTranslateModel({ model })) } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 46c32545c0..d533dac446 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1592,6 +1592,10 @@ "models.translate_model_prompt_title": "Translate Model Prompt", "models.quick_assistant_model": "Quick Assistant Model", "models.quick_assistant_model_description": "Default model used by Quick Assistant", + "models.quick_assistant_selection": "Select Assistant", + "models.quick_assistant_default_tag": "Default", + "models.use_model": "Default Model", + "models.use_assistant": "Use Assistant", "moresetting": "More Settings", "moresetting.check.confirm": "Confirm Selection", "moresetting.check.warn": "Please be cautious when selecting this option. Incorrect selection may cause the model to malfunction!", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index d155eea480..a59b2ed984 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1586,6 +1586,10 @@ "models.translate_model_prompt_title": "翻訳モデルのプロンプト", "models.quick_assistant_model": "クイックアシスタントモデル", "models.quick_assistant_model_description": "クイックアシスタントで使用されるデフォルトモデル", + "models.quick_assistant_selection": "アシスタントを選択します", + "models.quick_assistant_default_tag": "デフォルト", + "models.use_model": "デフォルトモデル", + "models.use_assistant": "アシスタントの活用", "moresetting": "詳細設定", "moresetting.check.confirm": "選択を確認", "moresetting.check.warn": "このオプションを選択する際は慎重に行ってください。誤った選択はモデルの誤動作を引き起こす可能性があります!", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 444440f604..4b1264e30e 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1586,6 +1586,10 @@ "models.translate_model_prompt_title": "Модель перевода", "models.quick_assistant_model": "Модель быстрого помощника", "models.quick_assistant_model_description": "Модель по умолчанию, используемая быстрым помощником", + "models.quick_assistant_selection": "Выберите помощника", + "models.quick_assistant_default_tag": "умолчанию", + "models.use_model": "модель по умолчанию", + "models.use_assistant": "Использование ассистентов", "moresetting": "Дополнительные настройки", "moresetting.check.confirm": "Подтвердить выбор", "moresetting.check.warn": "Пожалуйста, будьте осторожны при выборе этой опции. Неправильный выбор может привести к сбою в работе модели!", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 09ba5b4bdb..ca1d1da3d2 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1592,6 +1592,10 @@ "models.translate_model_prompt_title": "翻译模型提示词", "models.quick_assistant_model": "快捷助手模型", "models.quick_assistant_model_description": "快捷助手使用的默认模型", + "models.quick_assistant_selection": "选择助手", + "models.quick_assistant_default_tag": "默认", + "models.use_model": "默认模型", + "models.use_assistant": "使用助手", "moresetting": "更多设置", "moresetting.check.confirm": "确认勾选", "moresetting.check.warn": "请慎重勾选此选项,勾选错误会导致模型无法正常使用!!!", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index bae3c5331c..ca88470f2a 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1589,6 +1589,10 @@ "models.translate_model_prompt_title": "翻譯模型提示詞", "models.quick_assistant_model": "快捷助手模型", "models.quick_assistant_model_description": "快捷助手使用的預設模型", + "models.quick_assistant_selection": "選擇助手", + "models.quick_assistant_default_tag": "預設", + "models.use_model": "預設模型", + "models.use_assistant": "使用助手", "moresetting": "更多設定", "moresetting.check.confirm": "確認勾選", "moresetting.check.warn": "請謹慎勾選此選項,勾選錯誤會導致模型無法正常使用!!!", @@ -1957,7 +1961,7 @@ }, "opacity": { "title": "透明度", - "description": "設置視窗的默認透明度,100%為完全不透明" + "description": "設置視窗的預設透明度,100%為完全不透明" } }, "actions": { diff --git a/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx b/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx index 1003c011d0..d61767b3af 100644 --- a/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx +++ b/src/renderer/src/pages/settings/ModelSettings/ModelSettings.tsx @@ -1,37 +1,35 @@ import { RedoOutlined } from '@ant-design/icons' +import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import { HStack } from '@renderer/components/Layout' import PromptPopup from '@renderer/components/Popups/PromptPopup' import { isEmbeddingModel } from '@renderer/config/models' import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import { useTheme } from '@renderer/context/ThemeProvider' -import { useDefaultModel } from '@renderer/hooks/useAssistant' +import { useAssistants, useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssistant' import { useProviders } from '@renderer/hooks/useProvider' import { useSettings } from '@renderer/hooks/useSettings' import { getModelUniqId, hasModel } from '@renderer/services/ModelService' +import { useAppSelector } from '@renderer/store' import { useAppDispatch } from '@renderer/store' +import { setQuickAssistantId } from '@renderer/store/llm' import { setTranslateModelPrompt } from '@renderer/store/settings' import { Model } from '@renderer/types' import { Button, Select, Tooltip } from 'antd' import { find, sortBy } from 'lodash' -import { FolderPen, Languages, MessageSquareMore, Rocket, Settings2 } from 'lucide-react' +import { CircleHelp, FolderPen, Languages, MessageSquareMore, Rocket, Settings2 } from 'lucide-react' import { FC, useMemo } from 'react' import { useTranslation } from 'react-i18next' +import styled from 'styled-components' import { SettingContainer, SettingDescription, SettingGroup, SettingTitle } from '..' import DefaultAssistantSettings from './DefaultAssistantSettings' import TopicNamingModalPopup from './TopicNamingModalPopup' const ModelSettings: FC = () => { - const { - defaultModel, - topicNamingModel, - translateModel, - quickAssistantModel, - setDefaultModel, - setTopicNamingModel, - setTranslateModel, - setQuickAssistantModel - } = useDefaultModel() + const { defaultModel, topicNamingModel, translateModel, setDefaultModel, setTopicNamingModel, setTranslateModel } = + useDefaultModel() + const { defaultAssistant } = useDefaultAssistant() + const { assistants } = useAssistants() const { providers } = useProviders() const allModels = providers.map((p) => p.models).flat() const { theme } = useTheme() @@ -39,6 +37,7 @@ const ModelSettings: FC = () => { const { translateModelPrompt } = useSettings() const dispatch = useAppDispatch() + const { quickAssistantId } = useAppSelector((state) => state.llm) const selectOptions = providers .filter((p) => p.models.length > 0) @@ -68,11 +67,6 @@ const ModelSettings: FC = () => { [translateModel] ) - const defaultQuickAssistantModel = useMemo( - () => (hasModel(quickAssistantModel) ? getModelUniqId(quickAssistantModel) : undefined), - [quickAssistantModel] - ) - const onUpdateTranslateModel = async () => { const prompt = await PromptPopup.show({ title: t('settings.models.translate_model_prompt_title'), @@ -163,27 +157,118 @@ const ModelSettings: FC = () => { {t('settings.models.translate_model_description')} - - - - {t('settings.models.quick_assistant_model')} - - - - dispatch(setQuickAssistantId(value))} + placeholder={t('settings.models.quick_assistant_selection')}> + {assistants.map((a) => ( + + + + {a.name} + + {a.id === defaultAssistant.id && ( + {t('settings.models.quick_assistant_default_tag')} + )} + + + ))} + + + )} {t('settings.models.quick_assistant_model_description')} ) } +const QuestionIcon = styled(CircleHelp)` + cursor: pointer; + color: var(--color-text-3); +` + +const StyledButton = styled(Button)<{ selected: boolean }>` + border-radius: ${(props) => (props.selected ? '6px' : '6px')}; + z-index: ${(props) => (props.selected ? 1 : 0)}; + min-width: 80px; + + &:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right-width: 0px; // No right border for the first button when not selected + } + + &:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left-width: 1px; // Ensure left border for the last button + } + + // Override Ant Design's default hover and focus styles for a cleaner look + &:hover, + &:focus { + z-index: 1; + border-color: ${(props) => (props.selected ? 'var(--ant-primary-color)' : 'var(--ant-primary-color-hover)')}; + box-shadow: ${(props) => + props.selected ? '0 0 0 2px var(--ant-primary-color-outline)' : '0 0 0 2px var(--ant-primary-color-outline)'}; + } +` + +const AssistantItem = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + height: 28px; +` + +const AssistantName = styled.span` + max-width: calc(100% - 60px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +` + +const Spacer = styled.div` + flex: 1; +` + +const DefaultTag = styled.span<{ isCurrent: boolean }>` + color: ${(props) => (props.isCurrent ? 'var(--color-primary)' : 'var(--color-text-3)')}; + font-size: 12px; + padding: 2px 4px; + border-radius: 4px; +` + export default ModelSettings diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts index 8b3e5ed053..26611a6d2f 100644 --- a/src/renderer/src/store/llm.ts +++ b/src/renderer/src/store/llm.ts @@ -21,7 +21,7 @@ export interface LlmState { defaultModel: Model topicNamingModel: Model translateModel: Model - quickAssistantModel: Model + quickAssistantId: string | null settings: LlmSettings } @@ -514,7 +514,7 @@ const initialState: LlmState = { defaultModel: SYSTEM_MODELS.defaultModel[0], topicNamingModel: SYSTEM_MODELS.defaultModel[1], translateModel: SYSTEM_MODELS.defaultModel[2], - quickAssistantModel: SYSTEM_MODELS.defaultModel[3], + quickAssistantId: null, providers: INITIAL_PROVIDERS, settings: { ollama: { @@ -621,8 +621,9 @@ const llmSlice = createSlice({ setTranslateModel: (state, action: PayloadAction<{ model: Model }>) => { state.translateModel = action.payload.model }, - setQuickAssistantModel: (state, action: PayloadAction<{ model: Model }>) => { - state.quickAssistantModel = action.payload.model + + setQuickAssistantId: (state, action: PayloadAction) => { + state.quickAssistantId = action.payload }, setOllamaKeepAliveTime: (state, action: PayloadAction) => { state.settings.ollama.keepAliveTime = action.payload @@ -661,7 +662,7 @@ export const { setDefaultModel, setTopicNamingModel, setTranslateModel, - setQuickAssistantModel, + setQuickAssistantId, setOllamaKeepAliveTime, setLMStudioKeepAliveTime, setGPUStackKeepAliveTime, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 3cc36c7d99..f64bd30c5e 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1462,8 +1462,6 @@ const migrateConfig = { searchMessageShortcut.shortcut = [isMac ? 'Command' : 'Ctrl', 'Shift', 'F'] } } - // Quick assistant model - state.llm.quickAssistantModel = state.llm.defaultModel || SYSTEM_MODELS.silicon[1] return state } catch (error) { return state diff --git a/src/renderer/src/windows/mini/chat/ChatWindow.tsx b/src/renderer/src/windows/mini/chat/ChatWindow.tsx index 2156d405b5..d59169ba6f 100644 --- a/src/renderer/src/windows/mini/chat/ChatWindow.tsx +++ b/src/renderer/src/windows/mini/chat/ChatWindow.tsx @@ -1,5 +1,4 @@ import Scrollbar from '@renderer/components/Scrollbar' -import { getDefaultModel } from '@renderer/services/AssistantService' import { Assistant } from '@renderer/types' import { FC } from 'react' import styled from 'styled-components' @@ -11,11 +10,9 @@ interface Props { } const ChatWindow: FC = ({ route, assistant }) => { - // const { defaultAssistant } = useDefaultAssistant() - return (
- +
) } diff --git a/src/renderer/src/windows/mini/home/HomeWindow.tsx b/src/renderer/src/windows/mini/home/HomeWindow.tsx index 1b1b7b6db1..7ea0fd6959 100644 --- a/src/renderer/src/windows/mini/home/HomeWindow.tsx +++ b/src/renderer/src/windows/mini/home/HomeWindow.tsx @@ -4,13 +4,13 @@ import { useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssista import { useSettings } from '@renderer/hooks/useSettings' import i18n from '@renderer/i18n' import { fetchChatCompletion } from '@renderer/services/ApiService' -import { getDefaultAssistant, getDefaultModel } from '@renderer/services/AssistantService' +import { getAssistantById } from '@renderer/services/AssistantService' import { getAssistantMessage, getUserMessage } from '@renderer/services/MessagesService' -import store from '@renderer/store' +import store, { useAppSelector } from '@renderer/store' import { upsertManyBlocks } from '@renderer/store/messageBlock' import { updateOneBlock, upsertOneBlock } from '@renderer/store/messageBlock' import { newMessagesActions } from '@renderer/store/newMessage' -import { ThemeMode } from '@renderer/types' +import { Assistant, ThemeMode } from '@renderer/types' import { Chunk, ChunkType } from '@renderer/types/chunk' import { AssistantMessageStatus } from '@renderer/types/newMessage' import { MessageBlockStatus } from '@renderer/types/newMessage' @@ -37,14 +37,14 @@ const HomeWindow: FC = () => { const [isFirstMessage, setIsFirstMessage] = useState(true) const [clipboardText, setClipboardText] = useState('') const [selectedText, setSelectedText] = useState('') + const [currentAssistant, setCurrentAssistant] = useState({} as Assistant) const [text, setText] = useState('') const [lastClipboardText, setLastClipboardText] = useState(null) const textChange = useState(() => {})[1] const { defaultAssistant } = useDefaultAssistant() const topic = defaultAssistant.topics[0] - const { defaultModel, quickAssistantModel } = useDefaultModel() - // 如果 quickAssistantModel 未設定,則使用 defaultModel - const model = quickAssistantModel || defaultModel + const { defaultModel } = useDefaultModel() + const model = currentAssistant.model || defaultModel const { language, readClipboardAtStartup, windowStyle } = useSettings() const { theme } = useTheme() const { t } = useTranslation() @@ -54,6 +54,8 @@ const HomeWindow: FC = () => { const content = isFirstMessage ? (referenceText === text ? text : `${referenceText}\n\n${text}`).trim() : text.trim() + const { quickAssistantId } = useAppSelector((state) => state.llm) + const readClipboard = useCallback(async () => { if (!readClipboardAtStartup) return @@ -158,16 +160,36 @@ const HomeWindow: FC = () => { setText(e.target.value) } + useEffect(() => { + const defaultCurrentAssistant = { + ...defaultAssistant, + model: defaultModel + } + + if (quickAssistantId) { + // 獲取指定助手,如果不存在則使用默認助手 + const assistantFromId = getAssistantById(quickAssistantId) + const currentAssistant = assistantFromId || defaultCurrentAssistant + // 如果助手本身沒有設定模型,則使用預設模型 + if (!currentAssistant.model) { + currentAssistant.model = defaultModel + } + setCurrentAssistant(currentAssistant) + } else { + setCurrentAssistant(defaultCurrentAssistant) + } + }, [quickAssistantId, defaultAssistant, defaultModel]) + const onSendMessage = useCallback( async (prompt?: string) => { if (isEmpty(content)) { return } - + const topic = currentAssistant.topics[0] const messageParams = { role: 'user', - content: prompt ? `${prompt}\n\n${content}` : content, - assistant: defaultAssistant, + content: [prompt, content].filter(Boolean).join('\n\n'), + assistant: currentAssistant, topic, createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'), status: 'success' @@ -178,7 +200,7 @@ const HomeWindow: FC = () => { store.dispatch(newMessagesActions.addMessage({ topicId, message: userMessage })) store.dispatch(upsertManyBlocks(blocks)) - const assistant = getDefaultAssistant() + const assistant = currentAssistant let blockId: string | null = null let blockContent: string = '' @@ -187,7 +209,7 @@ const HomeWindow: FC = () => { fetchChatCompletion({ messages: [userMessage], - assistant: { ...assistant, model: quickAssistantModel || getDefaultModel(), settings: { streamOutput: true } }, + assistant: { ...assistant, settings: { streamOutput: true } }, onChunkReceived: (chunk: Chunk) => { if (chunk.type === ChunkType.TEXT_DELTA) { blockContent += chunk.text @@ -224,7 +246,7 @@ const HomeWindow: FC = () => { setIsFirstMessage(false) setText('') // ✅ 清除输入框内容 }, - [content, defaultAssistant, topic, quickAssistantModel] + [content, currentAssistant, topic] ) const clearClipboard = () => { @@ -277,7 +299,11 @@ const HomeWindow: FC = () => { text={text} model={model} referenceText={referenceText} - placeholder={t('miniwindow.input.placeholder.empty', { model: model.name })} + placeholder={ + quickAssistantId + ? t('miniwindow.input.placeholder.empty', { model: currentAssistant.name }) + : t('miniwindow.input.placeholder.empty', { model: model.name }) + } handleKeyDown={handleKeyDown} handleChange={handleChange} ref={inputBarRef} @@ -290,7 +316,7 @@ const HomeWindow: FC = () => { )} - +