From ce01d75d7c87474207dfc96429eb0c25fe651e01 Mon Sep 17 00:00:00 2001 From: MakiWinster Date: Wed, 11 Feb 2026 02:35:26 +0800 Subject: [PATCH 1/3] feat: add prompt configuration for AI settings --- i18n/en_US.yaml | 4 +- i18n/zh_CN.yaml | 5 +- ui/src/common/interface.ts | 4 ++ ui/src/pages/Admin/AiSettings/index.tsx | 72 ++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 9a0d198b3..97f067bb6 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -2354,6 +2354,9 @@ ui: model: label: Model msg: Model is required + prompt: + label: Prompt + text: Shows the prompt for the current language. Edit and save to apply. add_success: AI settings updated successfully. conversations: topic: Topic @@ -2483,4 +2486,3 @@ ui: copied: Copied external_content_warning: External images/media are not displayed. - diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index f16ed9fad..26ff0b202 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -1796,7 +1796,7 @@ ui: security: 安全 files: 文件 apikeys: API 密钥 - intelligence: 智力 + intelligence: 智能 ai_assistant: AI 助手 ai_settings: AI 设置 mcp: MCP @@ -2318,6 +2318,9 @@ ui: model: label: 模型 msg: 模型是必需的 + prompt: + label: 提示词 + text: 显示当前语言环境的提示词,可在此修改并保存。 add_success: AI 设置更新成功。 conversations: topic: 主题 diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 308726e80..5c850458a 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -834,6 +834,10 @@ export interface AiConfig { api_key: string; model: string; }>; + prompt_config?: { + zh_cn: string; + en_us: string; + }; } export interface AiProviderItem { diff --git a/ui/src/pages/Admin/AiSettings/index.tsx b/ui/src/pages/Admin/AiSettings/index.tsx index 2270aa5c5..9224ed4c8 100644 --- a/ui/src/pages/Admin/AiSettings/index.tsx +++ b/ui/src/pages/Admin/AiSettings/index.tsx @@ -32,8 +32,19 @@ import { handleFormError } from '@/utils'; import { useToast } from '@/hooks'; import * as Type from '@/common/interface'; +const getPromptByLang = ( + promptConfig: Type.AiConfig['prompt_config'] | undefined, + lang: string, +) => { + if (!promptConfig) { + return ''; + } + const isZh = lang?.toLowerCase().startsWith('zh'); + return isZh ? promptConfig.zh_cn || '' : promptConfig.en_us || ''; +}; + const Index = () => { - const { t } = useTranslation('translation', { + const { t, i18n } = useTranslation('translation', { keyPrefix: 'admin.ai_settings', }); const toast = useToast(); @@ -68,11 +79,18 @@ const Index = () => { isInvalid: false, errorMsg: '', }, + prompt: { + value: '', + isInvalid: false, + errorMsg: '', + }, }); const [apiHostPlaceholder, setApiHostPlaceholder] = useState(''); const [modelsData, setModels] = useState<{ id: string }[]>([]); const [isChecking, setIsChecking] = useState(false); + const isZhLang = i18n.language?.toLowerCase().startsWith('zh'); + const getCurrentProviderData = (provider) => { const findHistoryProvider = historyConfigRef.current?.ai_providers.find( @@ -227,6 +245,14 @@ const Index = () => { enabled: formData.enabled.value, chosen_provider: formData.provider.value, ai_providers: newProviders, + prompt_config: { + zh_cn: isZhLang + ? formData.prompt.value + : historyConfigRef.current?.prompt_config?.zh_cn || '', + en_us: isZhLang + ? historyConfigRef.current?.prompt_config?.en_us || '' + : formData.prompt.value, + }, }; saveAiConfig(params) .then(() => { @@ -295,6 +321,11 @@ const Index = () => { isInvalid: false, errorMsg: '', }, + prompt: { + value: getPromptByLang(aiConfig.prompt_config, i18n.language), + isInvalid: false, + errorMsg: '', + }, }); }; @@ -322,6 +353,22 @@ const Index = () => { } }, [aiProviders, formData]); + useEffect(() => { + if (!historyConfigRef.current) { + return; + } + setFormData((prev) => ({ + ...prev, + prompt: { + value: getPromptByLang( + historyConfigRef.current?.prompt_config, + i18n.language, + ), + isInvalid: false, + errorMsg: '', + }, + })); + }, [i18n.language]); return (

{t('ai_settings', { keyPrefix: 'nav_menus' })}

@@ -477,6 +524,29 @@ const Index = () => {
{formData.model.errorMsg}
+ + {t('prompt.label')} + + handleValueChange({ + prompt: { + value: e.target.value, + errorMsg: '', + isInvalid: false, + }, + }) + } + /> + {t('prompt.text')} + + {formData.prompt.errorMsg} + + + From a379e1ca8cb3dbc3917dda18a6b656686e319211 Mon Sep 17 00:00:00 2001 From: MakiWinster Date: Wed, 11 Feb 2026 12:01:16 +0800 Subject: [PATCH 2/3] fix: move prompt settings to AI Assistant Move prompt configuration from "AI Settings" to "AI Assistant" > "Settings". Added tab-like switcher under AI Assistant title: Conversations Settings Currently only Prompt setting is available. --- i18n/en_US.yaml | 4 +- i18n/zh_CN.yaml | 5 +- ui/src/pages/Admin/AiAssistant/index.tsx | 287 ++++++++++++++++++----- ui/src/pages/Admin/AiSettings/index.tsx | 72 +----- 4 files changed, 241 insertions(+), 127 deletions(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 97f067bb6..23ef11930 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -2359,6 +2359,9 @@ ui: text: Shows the prompt for the current language. Edit and save to apply. add_success: AI settings updated successfully. conversations: + tabs: + conversations: Conversations + settings: Settings topic: Topic helpful: Helpful unhelpful: Unhelpful @@ -2485,4 +2488,3 @@ ui: copy: Copy to clipboard copied: Copied external_content_warning: External images/media are not displayed. - diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index 26ff0b202..902915d09 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -2323,6 +2323,9 @@ ui: text: 显示当前语言环境的提示词,可在此修改并保存。 add_success: AI 设置更新成功。 conversations: + tabs: + conversations: 对话 + settings: 设置 topic: 主题 helpful: 有帮助 unhelpful: 没有帮助 @@ -2449,5 +2452,3 @@ ui: copy: 复制到剪贴板 copied: 已复制 external_content_warning: 外部图像/媒体未显示。 - - diff --git a/ui/src/pages/Admin/AiAssistant/index.tsx b/ui/src/pages/Admin/AiAssistant/index.tsx index 7fc3e9d9b..74c59dc7a 100644 --- a/ui/src/pages/Admin/AiAssistant/index.tsx +++ b/ui/src/pages/Admin/AiAssistant/index.tsx @@ -17,24 +17,53 @@ * under the License. */ -import { useState } from 'react'; -import { Table, Button } from 'react-bootstrap'; +import { FormEvent, useEffect, useRef, useState } from 'react'; +import { Table, Button, Nav, Form, Collapse } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; import { BaseUserCard, FormatTime, Pagination, Empty } from '@/components'; -import { useQueryAdminConversationList } from '@/services'; +import { useToast } from '@/hooks'; +import { + getAiConfig, + saveAiConfig, + useQueryAdminConversationList, +} from '@/services'; +import * as Type from '@/common/interface'; import DetailModal from './components/DetailModal'; import Action from './components/Action'; +const getPromptByLang = ( + promptConfig: Type.AiConfig['prompt_config'] | undefined, + lang: string, +) => { + if (!promptConfig) { + return ''; + } + const isZh = lang?.toLowerCase().startsWith('zh'); + return isZh ? promptConfig.zh_cn || '' : promptConfig.en_us || ''; +}; + const Index = () => { - const { t } = useTranslation('translation', { + const { t, i18n } = useTranslation('translation', { keyPrefix: 'admin.conversations', }); + const toast = useToast(); + const historyConfigRef = useRef(); const [urlSearchParams] = useSearchParams(); const curPage = Number(urlSearchParams.get('page') || '1'); const PAGE_SIZE = 20; + const [activeTab, setActiveTab] = useState<'conversations' | 'settings'>( + 'conversations', + ); + const [expandPrompt, setExpandPrompt] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [promptForm, setPromptForm] = useState({ + value: '', + isInvalid: false, + errorMsg: '', + }); const [detailModal, setDetailModal] = useState({ visible: false, id: '', @@ -47,6 +76,84 @@ const Index = () => { page: curPage, page_size: PAGE_SIZE, }); + const isZhLang = i18n.language?.toLowerCase().startsWith('zh'); + + const getAiConfigData = async () => { + const aiConfig = await getAiConfig(); + historyConfigRef.current = aiConfig; + setPromptForm({ + value: getPromptByLang(aiConfig.prompt_config, i18n.language), + isInvalid: false, + errorMsg: '', + }); + }; + + const handleSavePrompt = (evt: FormEvent) => { + evt.preventDefault(); + if (!historyConfigRef.current || isSaving) { + return; + } + setIsSaving(true); + setPromptForm((prev) => ({ + ...prev, + isInvalid: false, + errorMsg: '', + })); + + const params: Type.AiConfig = { + enabled: historyConfigRef.current.enabled || false, + chosen_provider: historyConfigRef.current.chosen_provider || '', + ai_providers: historyConfigRef.current.ai_providers || [], + prompt_config: { + zh_cn: isZhLang + ? promptForm.value + : historyConfigRef.current.prompt_config?.zh_cn || '', + en_us: isZhLang + ? historyConfigRef.current.prompt_config?.en_us || '' + : promptForm.value, + }, + }; + + saveAiConfig(params) + .then(() => { + historyConfigRef.current = params; + toast.onShow({ + msg: t('update', { keyPrefix: 'toast' }), + variant: 'success', + }); + }) + .catch((err) => { + setPromptForm((prev) => ({ + ...prev, + isInvalid: true, + errorMsg: err?.message || '', + })); + }) + .finally(() => { + setIsSaving(false); + }); + }; + + useEffect(() => { + if (activeTab === 'settings') { + getAiConfigData(); + } + }, [activeTab]); + + useEffect(() => { + if (!historyConfigRef.current || activeTab !== 'settings') { + return; + } + setPromptForm((prev) => ({ + ...prev, + value: getPromptByLang( + historyConfigRef.current?.prompt_config, + i18n.language, + ), + isInvalid: false, + errorMsg: '', + })); + }, [i18n.language, activeTab]); const handleShowDetailModal = (data) => { setDetailModal({ @@ -65,60 +172,130 @@ const Index = () => { return (

{t('ai_assistant', { keyPrefix: 'nav_menus' })}

- - - - - - - - - - - - {conversations?.list.map((item) => { - return ( - - - - - - + + + {activeTab === 'conversations' && ( + <> +
{t('topic')}{t('helpful')}{t('unhelpful')}{t('created')} - {t('action')} -
- - {item.helpful_count}{item.unhelpful_count} -
- - -
-
- -
+ + + + + + + - ); - })} - -
{t('topic')}{t('helpful')}{t('unhelpful')}{t('created')} + {t('action')} +
- {!isLoading && Number(conversations?.count) <= 0 && ( - {t('empty')} + + + {conversations?.list.map((item) => { + return ( + + + + + {item.helpful_count} + {item.unhelpful_count} + +
+ + +
+ + + + + + ); + })} + + + {!isLoading && Number(conversations?.count) <= 0 && ( + {t('empty')} + )} +
+ +
+ )} -
- -
+ {activeTab === 'settings' && ( +
+
+ + +
+
+
+ {t('prompt.text', { keyPrefix: 'admin.ai_settings' })} +
+ + + setPromptForm({ + value: e.target.value, + isInvalid: false, + errorMsg: '', + }) + } + /> + + {promptForm.errorMsg} + + + +
+
+
+
+
+ )} { - if (!promptConfig) { - return ''; - } - const isZh = lang?.toLowerCase().startsWith('zh'); - return isZh ? promptConfig.zh_cn || '' : promptConfig.en_us || ''; -}; - const Index = () => { - const { t, i18n } = useTranslation('translation', { + const { t } = useTranslation('translation', { keyPrefix: 'admin.ai_settings', }); const toast = useToast(); @@ -79,18 +68,11 @@ const Index = () => { isInvalid: false, errorMsg: '', }, - prompt: { - value: '', - isInvalid: false, - errorMsg: '', - }, }); const [apiHostPlaceholder, setApiHostPlaceholder] = useState(''); const [modelsData, setModels] = useState<{ id: string }[]>([]); const [isChecking, setIsChecking] = useState(false); - const isZhLang = i18n.language?.toLowerCase().startsWith('zh'); - const getCurrentProviderData = (provider) => { const findHistoryProvider = historyConfigRef.current?.ai_providers.find( @@ -246,12 +228,8 @@ const Index = () => { chosen_provider: formData.provider.value, ai_providers: newProviders, prompt_config: { - zh_cn: isZhLang - ? formData.prompt.value - : historyConfigRef.current?.prompt_config?.zh_cn || '', - en_us: isZhLang - ? historyConfigRef.current?.prompt_config?.en_us || '' - : formData.prompt.value, + zh_cn: historyConfigRef.current?.prompt_config?.zh_cn || '', + en_us: historyConfigRef.current?.prompt_config?.en_us || '', }, }; saveAiConfig(params) @@ -321,11 +299,6 @@ const Index = () => { isInvalid: false, errorMsg: '', }, - prompt: { - value: getPromptByLang(aiConfig.prompt_config, i18n.language), - isInvalid: false, - errorMsg: '', - }, }); }; @@ -353,22 +326,6 @@ const Index = () => { } }, [aiProviders, formData]); - useEffect(() => { - if (!historyConfigRef.current) { - return; - } - setFormData((prev) => ({ - ...prev, - prompt: { - value: getPromptByLang( - historyConfigRef.current?.prompt_config, - i18n.language, - ), - isInvalid: false, - errorMsg: '', - }, - })); - }, [i18n.language]); return (

{t('ai_settings', { keyPrefix: 'nav_menus' })}

@@ -524,29 +481,6 @@ const Index = () => {
{formData.model.errorMsg}
- - {t('prompt.label')} - - handleValueChange({ - prompt: { - value: e.target.value, - errorMsg: '', - isInvalid: false, - }, - }) - } - /> - {t('prompt.text')} - - {formData.prompt.errorMsg} - - -
From 09196d076b21c587a5d218c9dfc103040920a429 Mon Sep 17 00:00:00 2001 From: MakiWinster Date: Wed, 11 Feb 2026 18:03:26 +0800 Subject: [PATCH 3/3] style: align prompt UI design with original layout --- ui/src/pages/Admin/AiAssistant/index.tsx | 72 ++++++++++-------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/ui/src/pages/Admin/AiAssistant/index.tsx b/ui/src/pages/Admin/AiAssistant/index.tsx index 74c59dc7a..aa1e75cde 100644 --- a/ui/src/pages/Admin/AiAssistant/index.tsx +++ b/ui/src/pages/Admin/AiAssistant/index.tsx @@ -18,7 +18,7 @@ */ import { FormEvent, useEffect, useRef, useState } from 'react'; -import { Table, Button, Nav, Form, Collapse } from 'react-bootstrap'; +import { Table, Button, Nav, Form } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; @@ -57,7 +57,6 @@ const Index = () => { const [activeTab, setActiveTab] = useState<'conversations' | 'settings'>( 'conversations', ); - const [expandPrompt, setExpandPrompt] = useState(false); const [isSaving, setIsSaving] = useState(false); const [promptForm, setPromptForm] = useState({ value: '', @@ -251,49 +250,36 @@ const Index = () => { {activeTab === 'settings' && (
-
- - -
- -
- {t('prompt.text', { keyPrefix: 'admin.ai_settings' })} -
- - - setPromptForm({ - value: e.target.value, - isInvalid: false, - errorMsg: '', - }) - } - /> - - {promptForm.errorMsg} - - - - + + + setPromptForm({ + value: e.target.value, + isInvalid: false, + errorMsg: '', + }) + } + /> +
+ {t('prompt.text', { keyPrefix: 'admin.ai_settings' })}
- -
+ + {promptForm.errorMsg} + +
+ +
)}