mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 07:19:02 +08:00
feat: add quick assistant settings panel and management functionality (#6201)
* feat: add quick assistant settings panel and management functionality - Create QuickAssistantSettings component for UI - Extend useAssistant hook with quick assistant controls - Add settings button in ModelSettings page - Implement temperature, context count, max tokens, and other parameters - Connect settings to store via updateQuickAssistant action Separate quick assistant preferences from default assistant settings for better customization. * refactor(QuickAssistantSettings): remove maxTokens and refine UI layout - Removed maxTokens related state, logic, and UI elements - Simplified settings page by eliminating unused configuration - Adjusted layout for Slider and InputNumber for better usability - Removed fixed width from Modal to enable responsive behavior * refactor(HomeWindow): optimize message building logic - Removed redundant quickAssistant fetching logic - Use `useQuickAssistant` hook directly for cleaner code - Simplified message content concatenation method * style(QuickAssistantSettings): Adjust spacing in settings page layout Change the column width of sliders and input fields from 20/4 to 21/3 for a more reasonable layout Also set the popup width to 800px to improve user experience * feat(Quick Assistant): Add option to select assistant or model, and optimize Quick Assistant logic - Added functionality to choose between using models or referencing other assistants - Optimized model selection logic to automatically select based on settings - Added relevant internationalization texts * fix(HomeWindow): Dynamically display input box placeholder text based on quick assistant states * refactor(QuickAssistant): remove the implement of the quick assistant feature and restructure related logic - Remove code related to the quick assistant feature, including the useQuickAssistant hook, QuickAssistantSettings component, and associated store logic. - Restructure the HomeWindow component to use default or specified assistants instead of the quick assistant functionality, simplifying the code structure. * refactor(QuickAssistant): Remove custom default model for quick assistant and switch to default assistant - Refactor quick assistant functionality, remove independent model settings, change to select via assistant ID - Update multilingual translation text to match new features * refactor(QuickAssistant): Remove quick assistant-related states and simplify logic - Remove unused quick assistant states and toggle functionality, simplifying related logic - Update multilingual files to match the new default model and assistant labels * refactor(i18n): Unify translation keys for input field placeholders Unify the placeholder translation keys from `model_empty` and `assistant_empty` into empty across different scenarios, streamlining code logic * refactor(settings): simplify quick helper selection logic by directly using the preset helper - Removed redundant helper filtering logic, directly using the preset helper as the quick helper
This commit is contained in:
parent
9faa45b571
commit
477b5d2449
@ -15,7 +15,7 @@ import {
|
|||||||
updateTopic,
|
updateTopic,
|
||||||
updateTopics
|
updateTopics
|
||||||
} from '@renderer/store/assistants'
|
} 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 { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
|
||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
@ -103,17 +103,15 @@ export function useDefaultAssistant() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useDefaultModel() {
|
export function useDefaultModel() {
|
||||||
const { defaultModel, topicNamingModel, translateModel, quickAssistantModel } = useAppSelector((state) => state.llm)
|
const { defaultModel, topicNamingModel, translateModel } = useAppSelector((state) => state.llm)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultModel,
|
defaultModel,
|
||||||
topicNamingModel,
|
topicNamingModel,
|
||||||
translateModel,
|
translateModel,
|
||||||
quickAssistantModel,
|
|
||||||
setDefaultModel: (model: Model) => dispatch(setDefaultModel({ model })),
|
setDefaultModel: (model: Model) => dispatch(setDefaultModel({ model })),
|
||||||
setTopicNamingModel: (model: Model) => dispatch(setTopicNamingModel({ model })),
|
setTopicNamingModel: (model: Model) => dispatch(setTopicNamingModel({ model })),
|
||||||
setTranslateModel: (model: Model) => dispatch(setTranslateModel({ model })),
|
setTranslateModel: (model: Model) => dispatch(setTranslateModel({ model }))
|
||||||
setQuickAssistantModel: (model: Model) => dispatch(setQuickAssistantModel({ model }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1592,6 +1592,10 @@
|
|||||||
"models.translate_model_prompt_title": "Translate Model Prompt",
|
"models.translate_model_prompt_title": "Translate Model Prompt",
|
||||||
"models.quick_assistant_model": "Quick Assistant Model",
|
"models.quick_assistant_model": "Quick Assistant Model",
|
||||||
"models.quick_assistant_model_description": "Default model used by Quick Assistant",
|
"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": "More Settings",
|
||||||
"moresetting.check.confirm": "Confirm Selection",
|
"moresetting.check.confirm": "Confirm Selection",
|
||||||
"moresetting.check.warn": "Please be cautious when selecting this option. Incorrect selection may cause the model to malfunction!",
|
"moresetting.check.warn": "Please be cautious when selecting this option. Incorrect selection may cause the model to malfunction!",
|
||||||
|
|||||||
@ -1586,6 +1586,10 @@
|
|||||||
"models.translate_model_prompt_title": "翻訳モデルのプロンプト",
|
"models.translate_model_prompt_title": "翻訳モデルのプロンプト",
|
||||||
"models.quick_assistant_model": "クイックアシスタントモデル",
|
"models.quick_assistant_model": "クイックアシスタントモデル",
|
||||||
"models.quick_assistant_model_description": "クイックアシスタントで使用されるデフォルトモデル",
|
"models.quick_assistant_model_description": "クイックアシスタントで使用されるデフォルトモデル",
|
||||||
|
"models.quick_assistant_selection": "アシスタントを選択します",
|
||||||
|
"models.quick_assistant_default_tag": "デフォルト",
|
||||||
|
"models.use_model": "デフォルトモデル",
|
||||||
|
"models.use_assistant": "アシスタントの活用",
|
||||||
"moresetting": "詳細設定",
|
"moresetting": "詳細設定",
|
||||||
"moresetting.check.confirm": "選択を確認",
|
"moresetting.check.confirm": "選択を確認",
|
||||||
"moresetting.check.warn": "このオプションを選択する際は慎重に行ってください。誤った選択はモデルの誤動作を引き起こす可能性があります!",
|
"moresetting.check.warn": "このオプションを選択する際は慎重に行ってください。誤った選択はモデルの誤動作を引き起こす可能性があります!",
|
||||||
|
|||||||
@ -1586,6 +1586,10 @@
|
|||||||
"models.translate_model_prompt_title": "Модель перевода",
|
"models.translate_model_prompt_title": "Модель перевода",
|
||||||
"models.quick_assistant_model": "Модель быстрого помощника",
|
"models.quick_assistant_model": "Модель быстрого помощника",
|
||||||
"models.quick_assistant_model_description": "Модель по умолчанию, используемая быстрым помощником",
|
"models.quick_assistant_model_description": "Модель по умолчанию, используемая быстрым помощником",
|
||||||
|
"models.quick_assistant_selection": "Выберите помощника",
|
||||||
|
"models.quick_assistant_default_tag": "умолчанию",
|
||||||
|
"models.use_model": "модель по умолчанию",
|
||||||
|
"models.use_assistant": "Использование ассистентов",
|
||||||
"moresetting": "Дополнительные настройки",
|
"moresetting": "Дополнительные настройки",
|
||||||
"moresetting.check.confirm": "Подтвердить выбор",
|
"moresetting.check.confirm": "Подтвердить выбор",
|
||||||
"moresetting.check.warn": "Пожалуйста, будьте осторожны при выборе этой опции. Неправильный выбор может привести к сбою в работе модели!",
|
"moresetting.check.warn": "Пожалуйста, будьте осторожны при выборе этой опции. Неправильный выбор может привести к сбою в работе модели!",
|
||||||
|
|||||||
@ -1592,6 +1592,10 @@
|
|||||||
"models.translate_model_prompt_title": "翻译模型提示词",
|
"models.translate_model_prompt_title": "翻译模型提示词",
|
||||||
"models.quick_assistant_model": "快捷助手模型",
|
"models.quick_assistant_model": "快捷助手模型",
|
||||||
"models.quick_assistant_model_description": "快捷助手使用的默认模型",
|
"models.quick_assistant_model_description": "快捷助手使用的默认模型",
|
||||||
|
"models.quick_assistant_selection": "选择助手",
|
||||||
|
"models.quick_assistant_default_tag": "默认",
|
||||||
|
"models.use_model": "默认模型",
|
||||||
|
"models.use_assistant": "使用助手",
|
||||||
"moresetting": "更多设置",
|
"moresetting": "更多设置",
|
||||||
"moresetting.check.confirm": "确认勾选",
|
"moresetting.check.confirm": "确认勾选",
|
||||||
"moresetting.check.warn": "请慎重勾选此选项,勾选错误会导致模型无法正常使用!!!",
|
"moresetting.check.warn": "请慎重勾选此选项,勾选错误会导致模型无法正常使用!!!",
|
||||||
|
|||||||
@ -1589,6 +1589,10 @@
|
|||||||
"models.translate_model_prompt_title": "翻譯模型提示詞",
|
"models.translate_model_prompt_title": "翻譯模型提示詞",
|
||||||
"models.quick_assistant_model": "快捷助手模型",
|
"models.quick_assistant_model": "快捷助手模型",
|
||||||
"models.quick_assistant_model_description": "快捷助手使用的預設模型",
|
"models.quick_assistant_model_description": "快捷助手使用的預設模型",
|
||||||
|
"models.quick_assistant_selection": "選擇助手",
|
||||||
|
"models.quick_assistant_default_tag": "預設",
|
||||||
|
"models.use_model": "預設模型",
|
||||||
|
"models.use_assistant": "使用助手",
|
||||||
"moresetting": "更多設定",
|
"moresetting": "更多設定",
|
||||||
"moresetting.check.confirm": "確認勾選",
|
"moresetting.check.confirm": "確認勾選",
|
||||||
"moresetting.check.warn": "請謹慎勾選此選項,勾選錯誤會導致模型無法正常使用!!!",
|
"moresetting.check.warn": "請謹慎勾選此選項,勾選錯誤會導致模型無法正常使用!!!",
|
||||||
@ -1957,7 +1961,7 @@
|
|||||||
},
|
},
|
||||||
"opacity": {
|
"opacity": {
|
||||||
"title": "透明度",
|
"title": "透明度",
|
||||||
"description": "設置視窗的默認透明度,100%為完全不透明"
|
"description": "設置視窗的預設透明度,100%為完全不透明"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
|||||||
@ -1,37 +1,35 @@
|
|||||||
import { RedoOutlined } from '@ant-design/icons'
|
import { RedoOutlined } from '@ant-design/icons'
|
||||||
|
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||||
import { isEmbeddingModel } from '@renderer/config/models'
|
import { isEmbeddingModel } from '@renderer/config/models'
|
||||||
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
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 { useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { getModelUniqId, hasModel } from '@renderer/services/ModelService'
|
import { getModelUniqId, hasModel } from '@renderer/services/ModelService'
|
||||||
|
import { useAppSelector } from '@renderer/store'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
|
import { setQuickAssistantId } from '@renderer/store/llm'
|
||||||
import { setTranslateModelPrompt } from '@renderer/store/settings'
|
import { setTranslateModelPrompt } from '@renderer/store/settings'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import { Button, Select, Tooltip } from 'antd'
|
import { Button, Select, Tooltip } from 'antd'
|
||||||
import { find, sortBy } from 'lodash'
|
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 { FC, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { SettingContainer, SettingDescription, SettingGroup, SettingTitle } from '..'
|
import { SettingContainer, SettingDescription, SettingGroup, SettingTitle } from '..'
|
||||||
import DefaultAssistantSettings from './DefaultAssistantSettings'
|
import DefaultAssistantSettings from './DefaultAssistantSettings'
|
||||||
import TopicNamingModalPopup from './TopicNamingModalPopup'
|
import TopicNamingModalPopup from './TopicNamingModalPopup'
|
||||||
|
|
||||||
const ModelSettings: FC = () => {
|
const ModelSettings: FC = () => {
|
||||||
const {
|
const { defaultModel, topicNamingModel, translateModel, setDefaultModel, setTopicNamingModel, setTranslateModel } =
|
||||||
defaultModel,
|
useDefaultModel()
|
||||||
topicNamingModel,
|
const { defaultAssistant } = useDefaultAssistant()
|
||||||
translateModel,
|
const { assistants } = useAssistants()
|
||||||
quickAssistantModel,
|
|
||||||
setDefaultModel,
|
|
||||||
setTopicNamingModel,
|
|
||||||
setTranslateModel,
|
|
||||||
setQuickAssistantModel
|
|
||||||
} = useDefaultModel()
|
|
||||||
const { providers } = useProviders()
|
const { providers } = useProviders()
|
||||||
const allModels = providers.map((p) => p.models).flat()
|
const allModels = providers.map((p) => p.models).flat()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -39,6 +37,7 @@ const ModelSettings: FC = () => {
|
|||||||
const { translateModelPrompt } = useSettings()
|
const { translateModelPrompt } = useSettings()
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
const { quickAssistantId } = useAppSelector((state) => state.llm)
|
||||||
|
|
||||||
const selectOptions = providers
|
const selectOptions = providers
|
||||||
.filter((p) => p.models.length > 0)
|
.filter((p) => p.models.length > 0)
|
||||||
@ -68,11 +67,6 @@ const ModelSettings: FC = () => {
|
|||||||
[translateModel]
|
[translateModel]
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultQuickAssistantModel = useMemo(
|
|
||||||
() => (hasModel(quickAssistantModel) ? getModelUniqId(quickAssistantModel) : undefined),
|
|
||||||
[quickAssistantModel]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onUpdateTranslateModel = async () => {
|
const onUpdateTranslateModel = async () => {
|
||||||
const prompt = await PromptPopup.show({
|
const prompt = await PromptPopup.show({
|
||||||
title: t('settings.models.translate_model_prompt_title'),
|
title: t('settings.models.translate_model_prompt_title'),
|
||||||
@ -163,27 +157,118 @@ const ModelSettings: FC = () => {
|
|||||||
<SettingDescription>{t('settings.models.translate_model_description')}</SettingDescription>
|
<SettingDescription>{t('settings.models.translate_model_description')}</SettingDescription>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme}>
|
||||||
<SettingTitle style={{ marginBottom: 12 }}>
|
<HStack alignItems="center" style={{ marginBottom: 12 }}>
|
||||||
<HStack alignItems="center" gap={10}>
|
<SettingTitle>
|
||||||
<Rocket size={18} color="var(--color-text)" />
|
<HStack alignItems="center" gap={10}>
|
||||||
{t('settings.models.quick_assistant_model')}
|
<Rocket size={18} color="var(--color-text)" />
|
||||||
</HStack>
|
{t('settings.models.quick_assistant_model')}
|
||||||
</SettingTitle>
|
<Tooltip title={t('selection.settings.user_modal.model.tooltip')} arrow>
|
||||||
<HStack alignItems="center">
|
<QuestionIcon size={12} />
|
||||||
<Select
|
</Tooltip>
|
||||||
value={defaultQuickAssistantModel}
|
<Spacer />
|
||||||
defaultValue={defaultQuickAssistantModel}
|
</HStack>
|
||||||
style={{ width: 360 }}
|
<HStack alignItems="center" gap={0}>
|
||||||
onChange={(value) => setQuickAssistantModel(find(allModels, JSON.parse(value)) as Model)}
|
<StyledButton
|
||||||
options={selectOptions}
|
type={!quickAssistantId ? 'primary' : 'default'}
|
||||||
showSearch
|
onClick={() => dispatch(setQuickAssistantId(null))}
|
||||||
placeholder={t('settings.models.empty')}
|
selected={!quickAssistantId}>
|
||||||
/>
|
{t('settings.models.use_model')}
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
type={quickAssistantId ? 'primary' : 'default'}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(setQuickAssistantId(defaultAssistant.id))
|
||||||
|
}}
|
||||||
|
selected={!!quickAssistantId}>
|
||||||
|
{t('settings.models.use_assistant')}
|
||||||
|
</StyledButton>
|
||||||
|
</HStack>
|
||||||
|
</SettingTitle>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
{!quickAssistantId ? null : (
|
||||||
|
<HStack alignItems="center" style={{ marginTop: 12 }}>
|
||||||
|
<Select
|
||||||
|
value={quickAssistantId}
|
||||||
|
style={{ width: 360 }}
|
||||||
|
onChange={(value) => dispatch(setQuickAssistantId(value))}
|
||||||
|
placeholder={t('settings.models.quick_assistant_selection')}>
|
||||||
|
{assistants.map((a) => (
|
||||||
|
<Select.Option key={a.id} value={a.id}>
|
||||||
|
<AssistantItem>
|
||||||
|
<ModelAvatar model={a.model || defaultModel} size={18} />
|
||||||
|
<AssistantName>{a.name}</AssistantName>
|
||||||
|
<Spacer />
|
||||||
|
{a.id === defaultAssistant.id && (
|
||||||
|
<DefaultTag isCurrent={true}>{t('settings.models.quick_assistant_default_tag')}</DefaultTag>
|
||||||
|
)}
|
||||||
|
</AssistantItem>
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
<SettingDescription>{t('settings.models.quick_assistant_model_description')}</SettingDescription>
|
<SettingDescription>{t('settings.models.quick_assistant_model_description')}</SettingDescription>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
export default ModelSettings
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export interface LlmState {
|
|||||||
defaultModel: Model
|
defaultModel: Model
|
||||||
topicNamingModel: Model
|
topicNamingModel: Model
|
||||||
translateModel: Model
|
translateModel: Model
|
||||||
quickAssistantModel: Model
|
quickAssistantId: string | null
|
||||||
settings: LlmSettings
|
settings: LlmSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,7 +514,7 @@ const initialState: LlmState = {
|
|||||||
defaultModel: SYSTEM_MODELS.defaultModel[0],
|
defaultModel: SYSTEM_MODELS.defaultModel[0],
|
||||||
topicNamingModel: SYSTEM_MODELS.defaultModel[1],
|
topicNamingModel: SYSTEM_MODELS.defaultModel[1],
|
||||||
translateModel: SYSTEM_MODELS.defaultModel[2],
|
translateModel: SYSTEM_MODELS.defaultModel[2],
|
||||||
quickAssistantModel: SYSTEM_MODELS.defaultModel[3],
|
quickAssistantId: null,
|
||||||
providers: INITIAL_PROVIDERS,
|
providers: INITIAL_PROVIDERS,
|
||||||
settings: {
|
settings: {
|
||||||
ollama: {
|
ollama: {
|
||||||
@ -621,8 +621,9 @@ const llmSlice = createSlice({
|
|||||||
setTranslateModel: (state, action: PayloadAction<{ model: Model }>) => {
|
setTranslateModel: (state, action: PayloadAction<{ model: Model }>) => {
|
||||||
state.translateModel = action.payload.model
|
state.translateModel = action.payload.model
|
||||||
},
|
},
|
||||||
setQuickAssistantModel: (state, action: PayloadAction<{ model: Model }>) => {
|
|
||||||
state.quickAssistantModel = action.payload.model
|
setQuickAssistantId: (state, action: PayloadAction<string | null>) => {
|
||||||
|
state.quickAssistantId = action.payload
|
||||||
},
|
},
|
||||||
setOllamaKeepAliveTime: (state, action: PayloadAction<number>) => {
|
setOllamaKeepAliveTime: (state, action: PayloadAction<number>) => {
|
||||||
state.settings.ollama.keepAliveTime = action.payload
|
state.settings.ollama.keepAliveTime = action.payload
|
||||||
@ -661,7 +662,7 @@ export const {
|
|||||||
setDefaultModel,
|
setDefaultModel,
|
||||||
setTopicNamingModel,
|
setTopicNamingModel,
|
||||||
setTranslateModel,
|
setTranslateModel,
|
||||||
setQuickAssistantModel,
|
setQuickAssistantId,
|
||||||
setOllamaKeepAliveTime,
|
setOllamaKeepAliveTime,
|
||||||
setLMStudioKeepAliveTime,
|
setLMStudioKeepAliveTime,
|
||||||
setGPUStackKeepAliveTime,
|
setGPUStackKeepAliveTime,
|
||||||
|
|||||||
@ -1462,8 +1462,6 @@ const migrateConfig = {
|
|||||||
searchMessageShortcut.shortcut = [isMac ? 'Command' : 'Ctrl', 'Shift', 'F']
|
searchMessageShortcut.shortcut = [isMac ? 'Command' : 'Ctrl', 'Shift', 'F']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Quick assistant model
|
|
||||||
state.llm.quickAssistantModel = state.llm.defaultModel || SYSTEM_MODELS.silicon[1]
|
|
||||||
return state
|
return state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return state
|
return state
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { getDefaultModel } from '@renderer/services/AssistantService'
|
|
||||||
import { Assistant } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -11,11 +10,9 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ChatWindow: FC<Props> = ({ route, assistant }) => {
|
const ChatWindow: FC<Props> = ({ route, assistant }) => {
|
||||||
// const { defaultAssistant } = useDefaultAssistant()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Main className="bubble">
|
<Main className="bubble">
|
||||||
<Messages assistant={{ ...assistant, model: getDefaultModel() }} route={route} />
|
<Messages assistant={{ ...assistant }} route={route} />
|
||||||
</Main>
|
</Main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import { useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssista
|
|||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { fetchChatCompletion } from '@renderer/services/ApiService'
|
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 { getAssistantMessage, getUserMessage } from '@renderer/services/MessagesService'
|
||||||
import store from '@renderer/store'
|
import store, { useAppSelector } from '@renderer/store'
|
||||||
import { upsertManyBlocks } from '@renderer/store/messageBlock'
|
import { upsertManyBlocks } from '@renderer/store/messageBlock'
|
||||||
import { updateOneBlock, upsertOneBlock } from '@renderer/store/messageBlock'
|
import { updateOneBlock, upsertOneBlock } from '@renderer/store/messageBlock'
|
||||||
import { newMessagesActions } from '@renderer/store/newMessage'
|
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 { Chunk, ChunkType } from '@renderer/types/chunk'
|
||||||
import { AssistantMessageStatus } from '@renderer/types/newMessage'
|
import { AssistantMessageStatus } from '@renderer/types/newMessage'
|
||||||
import { MessageBlockStatus } from '@renderer/types/newMessage'
|
import { MessageBlockStatus } from '@renderer/types/newMessage'
|
||||||
@ -37,14 +37,14 @@ const HomeWindow: FC = () => {
|
|||||||
const [isFirstMessage, setIsFirstMessage] = useState(true)
|
const [isFirstMessage, setIsFirstMessage] = useState(true)
|
||||||
const [clipboardText, setClipboardText] = useState('')
|
const [clipboardText, setClipboardText] = useState('')
|
||||||
const [selectedText, setSelectedText] = useState('')
|
const [selectedText, setSelectedText] = useState('')
|
||||||
|
const [currentAssistant, setCurrentAssistant] = useState<Assistant>({} as Assistant)
|
||||||
const [text, setText] = useState('')
|
const [text, setText] = useState('')
|
||||||
const [lastClipboardText, setLastClipboardText] = useState<string | null>(null)
|
const [lastClipboardText, setLastClipboardText] = useState<string | null>(null)
|
||||||
const textChange = useState(() => {})[1]
|
const textChange = useState(() => {})[1]
|
||||||
const { defaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant } = useDefaultAssistant()
|
||||||
const topic = defaultAssistant.topics[0]
|
const topic = defaultAssistant.topics[0]
|
||||||
const { defaultModel, quickAssistantModel } = useDefaultModel()
|
const { defaultModel } = useDefaultModel()
|
||||||
// 如果 quickAssistantModel 未設定,則使用 defaultModel
|
const model = currentAssistant.model || defaultModel
|
||||||
const model = quickAssistantModel || defaultModel
|
|
||||||
const { language, readClipboardAtStartup, windowStyle } = useSettings()
|
const { language, readClipboardAtStartup, windowStyle } = useSettings()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -54,6 +54,8 @@ const HomeWindow: FC = () => {
|
|||||||
|
|
||||||
const content = isFirstMessage ? (referenceText === text ? text : `${referenceText}\n\n${text}`).trim() : text.trim()
|
const content = isFirstMessage ? (referenceText === text ? text : `${referenceText}\n\n${text}`).trim() : text.trim()
|
||||||
|
|
||||||
|
const { quickAssistantId } = useAppSelector((state) => state.llm)
|
||||||
|
|
||||||
const readClipboard = useCallback(async () => {
|
const readClipboard = useCallback(async () => {
|
||||||
if (!readClipboardAtStartup) return
|
if (!readClipboardAtStartup) return
|
||||||
|
|
||||||
@ -158,16 +160,36 @@ const HomeWindow: FC = () => {
|
|||||||
setText(e.target.value)
|
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(
|
const onSendMessage = useCallback(
|
||||||
async (prompt?: string) => {
|
async (prompt?: string) => {
|
||||||
if (isEmpty(content)) {
|
if (isEmpty(content)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const topic = currentAssistant.topics[0]
|
||||||
const messageParams = {
|
const messageParams = {
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: prompt ? `${prompt}\n\n${content}` : content,
|
content: [prompt, content].filter(Boolean).join('\n\n'),
|
||||||
assistant: defaultAssistant,
|
assistant: currentAssistant,
|
||||||
topic,
|
topic,
|
||||||
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
status: 'success'
|
status: 'success'
|
||||||
@ -178,7 +200,7 @@ const HomeWindow: FC = () => {
|
|||||||
store.dispatch(newMessagesActions.addMessage({ topicId, message: userMessage }))
|
store.dispatch(newMessagesActions.addMessage({ topicId, message: userMessage }))
|
||||||
store.dispatch(upsertManyBlocks(blocks))
|
store.dispatch(upsertManyBlocks(blocks))
|
||||||
|
|
||||||
const assistant = getDefaultAssistant()
|
const assistant = currentAssistant
|
||||||
let blockId: string | null = null
|
let blockId: string | null = null
|
||||||
let blockContent: string = ''
|
let blockContent: string = ''
|
||||||
|
|
||||||
@ -187,7 +209,7 @@ const HomeWindow: FC = () => {
|
|||||||
|
|
||||||
fetchChatCompletion({
|
fetchChatCompletion({
|
||||||
messages: [userMessage],
|
messages: [userMessage],
|
||||||
assistant: { ...assistant, model: quickAssistantModel || getDefaultModel(), settings: { streamOutput: true } },
|
assistant: { ...assistant, settings: { streamOutput: true } },
|
||||||
onChunkReceived: (chunk: Chunk) => {
|
onChunkReceived: (chunk: Chunk) => {
|
||||||
if (chunk.type === ChunkType.TEXT_DELTA) {
|
if (chunk.type === ChunkType.TEXT_DELTA) {
|
||||||
blockContent += chunk.text
|
blockContent += chunk.text
|
||||||
@ -224,7 +246,7 @@ const HomeWindow: FC = () => {
|
|||||||
setIsFirstMessage(false)
|
setIsFirstMessage(false)
|
||||||
setText('') // ✅ 清除输入框内容
|
setText('') // ✅ 清除输入框内容
|
||||||
},
|
},
|
||||||
[content, defaultAssistant, topic, quickAssistantModel]
|
[content, currentAssistant, topic]
|
||||||
)
|
)
|
||||||
|
|
||||||
const clearClipboard = () => {
|
const clearClipboard = () => {
|
||||||
@ -277,7 +299,11 @@ const HomeWindow: FC = () => {
|
|||||||
text={text}
|
text={text}
|
||||||
model={model}
|
model={model}
|
||||||
referenceText={referenceText}
|
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}
|
handleKeyDown={handleKeyDown}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
ref={inputBarRef}
|
ref={inputBarRef}
|
||||||
@ -290,7 +316,7 @@ const HomeWindow: FC = () => {
|
|||||||
<ClipboardPreview referenceText={referenceText} clearClipboard={clearClipboard} t={t} />
|
<ClipboardPreview referenceText={referenceText} clearClipboard={clearClipboard} t={t} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ChatWindow route={route} assistant={defaultAssistant} />
|
<ChatWindow route={route} assistant={currentAssistant ?? defaultAssistant} />
|
||||||
<Divider style={{ margin: '10px 0' }} />
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
<Footer route={route} onExit={() => setRoute('home')} />
|
<Footer route={route} onExit={() => setRoute('home')} />
|
||||||
</Container>
|
</Container>
|
||||||
@ -316,7 +342,9 @@ const HomeWindow: FC = () => {
|
|||||||
placeholder={
|
placeholder={
|
||||||
referenceText && route === 'home'
|
referenceText && route === 'home'
|
||||||
? t('miniwindow.input.placeholder.title')
|
? t('miniwindow.input.placeholder.title')
|
||||||
: t('miniwindow.input.placeholder.empty', { model: model.name })
|
: quickAssistantId
|
||||||
|
? t('miniwindow.input.placeholder.empty', { model: currentAssistant.name })
|
||||||
|
: t('miniwindow.input.placeholder.empty', { model: model.name })
|
||||||
}
|
}
|
||||||
handleKeyDown={handleKeyDown}
|
handleKeyDown={handleKeyDown}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user