diff --git a/package.json b/package.json index 01ef828d991fdcd83ff2d9ef176f085572a530b0..7d6b043d1def995fd8fa205dada45fd11c1250e7 100644 --- a/package.json +++ b/package.json @@ -123,12 +123,13 @@ "axios": "1.7.9", "codemirror": "^6.0.1", "dayjs": "1.11.9", + "dompurify": "^3.3.0", "echarts": "^5.6.0", "element-plus": "2.8.0", "highlight.js": "11.10.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", - "marked": "^15.0.8", + "marked": "^15.0.12", "marked-highlight": "^2.2.1", "monaco-editor": "^0.52.2", "monaco-yaml": "^5.3.1", @@ -153,7 +154,9 @@ "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-typescript": "^12.1.2", + "@types/dompurify": "^3.0.5", "@types/estree": "1.0.7", + "@types/marked": "^5.0.2", "@types/minimist": "^1.2.5", "@types/mockjs": "^1.0.10", "@types/node": "^22.14.0", diff --git a/src/apis/paths/api.ts b/src/apis/paths/api.ts index 95dd5fae5c99954fe28296b65d4b0706f9325b7c..b9db2c05d66861839bda69770f32301808d700af 100644 --- a/src/apis/paths/api.ts +++ b/src/apis/paths/api.ts @@ -1,5 +1,5 @@ // Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. -import { post, get, del, put } from 'src/apis/server'; +import { post, get, del, put, witchainDGet } from 'src/apis/server'; import type { FcResponse } from 'src/apis/server'; import { QueryApiListParamsType, CreateOrUpdateApiParamsType } from './type'; /** @@ -83,10 +83,22 @@ export const changeSingleApiCollect = (params: { favorited: params.favorited, }); }; + +/** + * 获取检索方法列表 + * @returns + */ +export const getSearchMethods = (): Promise< + [any, FcResponse | undefined] +> => { + return witchainDGet('/witchaind/api/other/search_method'); +}; + export const apiApi = { queryApiList, createOrUpdateApi, querySingleApiData, deleteSingleApiData, changeSingleApiCollect, + getSearchMethods, }; diff --git a/src/apis/paths/llm.ts b/src/apis/paths/llm.ts index 923fc5a16022bb4671d7bc62fbdfeb97f6d17dfb..ca170f96b634b965c19529c99d3ba52b3be69ae4 100644 --- a/src/apis/paths/llm.ts +++ b/src/apis/paths/llm.ts @@ -2,19 +2,11 @@ import { get, post, del, put } from '../server'; import { AddedModalList } from './type'; /** * 获取用户的模型列表 + * @param modelType 模型类型,可选值:'chat' | 'embedding' | 'reranker' 等 * @returns */ -const getLLMList = () => { - return get< - { - id: string; - icon: string; - openaiBaseUrl: string; - openaiApiKey: string; - modelName: string; - maxTokens: number; - }[] - >('/api/llm'); +const getLLMList = (modelType?: string) => { + return get('/api/llm', modelType ? { modelType } : {}); }; /** @@ -58,10 +50,39 @@ const getRerankerConfig = () => { }[]>('/api/llm/reranker'); }; +/** + * 获取指定模型的能力配置 + * @param llmId 模型ID + * @returns + */ +const getModelCapabilities = (llmId: string) => { + return get<{ + provider: string; + modelName: string; + modelType: string; + supportsTemperature: boolean; + supportsTopP: boolean; + supportsTopK: boolean; + supportsFrequencyPenalty: boolean; + supportsPresencePenalty: boolean; + supportsMinP: boolean; + supportsThinking: boolean; + canToggleThinking: boolean; + supportsEnableSearch: boolean; + supportsFunctionCalling: boolean; + supportsJsonMode: boolean; + supportsStructuredOutput: boolean; + supportsContext: boolean; + maxTokensParam: string; + notes: string; + }>('/api/llm/capabilities', { llmId }); +}; + export const llmApi = { getAddedModels, getLLMList, updateLLMList, getEmbeddingConfig, getRerankerConfig, + getModelCapabilities, }; diff --git a/src/apis/paths/model.ts b/src/apis/paths/model.ts index d01a43801c91b8447fdd2cda5c560b0f166ed6e1..e6d011fa67d20d51b2d9bdaff882694f52f7e492 100644 --- a/src/apis/paths/model.ts +++ b/src/apis/paths/model.ts @@ -24,6 +24,8 @@ const getUserModelList = () => { modelName: string; maxTokens: number; isEditable?: boolean; + type?: string | string[]; + provider?: string; }[] >('/api/llm'); }; @@ -51,6 +53,9 @@ const getModelProviderList = () => { icon: string; url: string; description: string; + aliasZh?: string; + aliasEn?: string; + type?: string; }[] >('/api/llm/provider'); }; @@ -82,7 +87,7 @@ const createOrUpdateModel = (params: { openaiApiKey: string; modelName: string; maxTokens: number; - type?: 'chat' | 'image' | 'video' | 'speech' | 'embedding' | 'reranker'; + type?: string | string[]; }) => { return put('/api/llm', params, { llmId: params.llmId }); }; diff --git a/src/apis/paths/type.ts b/src/apis/paths/type.ts index 0f0d832e4af0a5f3af8db01ba9626103f249552d..78d4f98b93d4112ca2d744a52806d81f7ab391a9 100644 --- a/src/apis/paths/type.ts +++ b/src/apis/paths/type.ts @@ -224,6 +224,7 @@ export interface AddedModalList { openaiApiKey: string; modelName: string; maxTokens: string; + type?: string; // 模型类型: chat, embedding, reranker等 supportsThinking?: boolean; canToggleThinking?: boolean; } diff --git a/src/components/CodeMonacoEditor.vue b/src/components/CodeMonacoEditor.vue index a1b07f9d5ed3e23b639d9e1be86345543e51c357..9894939859ee56771515ce7d637d6223f175419f 100644 --- a/src/components/CodeMonacoEditor.vue +++ b/src/components/CodeMonacoEditor.vue @@ -116,6 +116,12 @@ const symbols = ref([]) const isFullScreen = ref(false) let editor: monaco.editor.IStandaloneCodeEditor | null = null +// 检测当前主题 +const getCurrentTheme = (): 'light' | 'dark' => { + const bodyTheme = document.body.getAttribute('theme') + return bodyTheme === 'dark' ? 'dark' : 'light' +} + // 计算容器高度 - 弹性变化 const editorHeight = ref(props.minHeight) @@ -213,7 +219,7 @@ const initMonacoEditor = async () => { // 配置Worker - 不设置worker,使用默认配置 // Monaco Editor会自动处理worker加载 - // 定义Atom One Dark Pro主题 + // 定义Atom One Dark Pro主题(暗色) monaco.editor.defineTheme('atom-one-dark-pro', { base: 'vs-dark', inherit: true, @@ -261,11 +267,63 @@ const initMonacoEditor = async () => { } }) + // 定义Atom One Light主题(浅色) + monaco.editor.defineTheme('atom-one-light', { + base: 'vs', + inherit: true, + rules: [ + { token: 'comment', foreground: 'A0A1A7', fontStyle: 'italic' }, + { token: 'keyword', foreground: 'A626A4' }, + { token: 'operator', foreground: '0184BC' }, + { token: 'namespace', foreground: 'E45649' }, + { token: 'type', foreground: 'C18401' }, + { token: 'struct', foreground: 'C18401' }, + { token: 'class', foreground: 'C18401' }, + { token: 'interface', foreground: 'C18401' }, + { token: 'parameter', foreground: '986801' }, + { token: 'variable', foreground: 'E45649' }, + { token: 'property', foreground: 'E45649' }, + { token: 'enumMember', foreground: '986801' }, + { token: 'function', foreground: '4078F2' }, + { token: 'member', foreground: '4078F2' }, + { token: 'macro', foreground: '4078F2' }, + { token: 'label', foreground: 'E45649' }, + { token: 'string', foreground: '50A14F' }, + { token: 'number', foreground: '986801' }, + { token: 'regexp', foreground: '50A14F' }, + { token: 'delimiter', foreground: '383A42' }, + ], + colors: { + 'editor.background': '#FAFAFA', + 'editor.foreground': '#383A42', + 'editorCursor.foreground': '#526FFF', + 'editor.lineHighlightBackground': '#F0F0F0', + 'editorLineNumber.foreground': '#9D9D9F', + 'editorLineNumber.activeForeground': '#383A42', + 'editor.selectionBackground': '#E5E5E6', + 'editor.selectionHighlightBackground': '#E5E5E6', + 'editorBracketMatch.background': '#E5E5E6', + 'editorBracketMatch.border': '#526FFF', + 'editorGutter.background': '#FAFAFA', + 'editorGutter.addedBackground': '#109868', + 'editorGutter.deletedBackground': '#946EE5', + 'editorGutter.modifiedBackground': '#C18401', + 'editorScrollbar.shadow': '#E5E5E6', + 'scrollbarSlider.background': '#D0D0D080', + 'scrollbarSlider.hoverBackground': '#B0B0B080', + 'scrollbarSlider.activeBackground': '#90909080' + } + }) + + // 根据当前主题选择Monaco主题 + const currentTheme = getCurrentTheme() + const monacoTheme = currentTheme === 'dark' ? 'atom-one-dark-pro' : 'atom-one-light' + // 创建编辑器 editor = monaco.editor.create(editorContainer.value, { value: props.modelValue || codeTemplates.value[currentLanguage.value] || '', language: currentLanguage.value, - theme: 'atom-one-dark-pro', + theme: monacoTheme, automaticLayout: true, tabSize: 2, insertSpaces: true, @@ -473,6 +531,15 @@ const copyCode = async () => { } } +// 监听主题变化并更新Monaco主题 +const updateMonacoTheme = () => { + if (!editor) return + + const currentTheme = getCurrentTheme() + const monacoTheme = currentTheme === 'dark' ? 'atom-one-dark-pro' : 'atom-one-light' + editor.updateOptions({ theme: monacoTheme }) +} + // 切换全屏 const toggleFullScreen = () => { isFullScreen.value = !isFullScreen.value @@ -558,24 +625,40 @@ const addFullscreenStyles = () => { visibility: visible !important; opacity: 1 !important; pointer-events: auto !important; - background: #282c34 !important; margin: 0 !important; padding: 0 !important; border: none !important; border-radius: 0 !important; } + /* 根据主题设置背景色 */ + body[theme='dark'] .code-monaco-editor.fullscreen { + background: #282c34 !important; + } + + body[theme='light'] .code-monaco-editor.fullscreen { + background: #fafafa !important; + } + /* 确保工具栏在正确位置 */ .code-monaco-editor.fullscreen .editor-toolbar { position: relative !important; z-index: 1000000 !important; - background: #1e2127 !important; visibility: visible !important; opacity: 1 !important; width: 100% !important; padding: 12px 16px !important; } + /* 根据主题设置工具栏背景色 */ + body[theme='dark'] .code-monaco-editor.fullscreen .editor-toolbar { + background: #1e2127 !important; + } + + body[theme='light'] .code-monaco-editor.fullscreen .editor-toolbar { + background: #f6f8fa !important; + } + /* 确保编辑器容器占满剩余空间 */ .code-monaco-editor.fullscreen .editor-container { width: 100% !important; @@ -583,9 +666,17 @@ const addFullscreenStyles = () => { position: relative !important; visibility: visible !important; opacity: 1 !important; + } + + /* 根据主题设置编辑器容器背景色 */ + body[theme='dark'] .code-monaco-editor.fullscreen .editor-container { background: #282c34 !important; } + body[theme='light'] .code-monaco-editor.fullscreen .editor-container { + background: #fafafa !important; + } + /* 确保Monaco编辑器本身可见并占满容器 */ .code-monaco-editor.fullscreen .monaco-editor { width: 100% !important; @@ -686,6 +777,19 @@ onMounted(async () => { // 添加键盘事件监听 document.addEventListener('keydown', handleKeyDown) + + // 监听主题变化 + const observer = new MutationObserver(() => { + updateMonacoTheme() + }) + + observer.observe(document.body, { + attributes: true, + attributeFilter: ['theme'] + }) + + // 保存observer引用以便后续清理 + ;(window as any).__monacoThemeObserver = observer }) onBeforeUnmount(() => { @@ -697,6 +801,12 @@ onBeforeUnmount(() => { // 移除键盘事件监听 document.removeEventListener('keydown', handleKeyDown) + // 清理主题监听器 + if ((window as any).__monacoThemeObserver) { + ;(window as any).__monacoThemeObserver.disconnect() + delete (window as any).__monacoThemeObserver + } + // 恢复页面滚动 document.body.style.overflow = '' document.body.classList.remove('monaco-editor-fullscreen') @@ -738,6 +848,12 @@ defineExpose({ overflow: hidden; background: #282c34; + // 浅色主题样式 + body[theme='light'] & { + border: 1px solid #d0d7de; + background: #fafafa; + } + &.fullscreen { position: fixed !important; top: 0 !important; @@ -778,6 +894,12 @@ defineExpose({ background: #21252b; border-bottom: 1px solid #3e4451; + // 浅色主题样式 + body[theme='light'] & { + background: #f6f8fa; + border-bottom: 1px solid #d0d7de; + } + .fullscreen & { padding: 12px 16px; background: #1e2127; @@ -807,6 +929,10 @@ defineExpose({ color: #abb2bf; font-size: 12px; font-weight: 500; + + body[theme='light'] & { + color: #57606a; + } } } @@ -824,8 +950,17 @@ defineExpose({ border: 1px solid transparent !important; // 透明边框便于添加悬停效果 background: transparent !important; + // 浅色主题样式 + body[theme='light'] & { + color: #24292f !important; + } + span { color: #ffffff !important; + + body[theme='light'] & { + color: #24292f !important; + } } &:hover { @@ -835,20 +970,39 @@ defineExpose({ transform: translateY(-1px) !important; // 轻微上移效果 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important; // 添加阴影 + body[theme='light'] & { + color: #0969da !important; + background: rgba(9, 105, 218, 0.1) !important; + border-color: rgba(9, 105, 218, 0.2) !important; + } + span { color: #61afef !important; + + body[theme='light'] & { + color: #0969da !important; + } } } &:active { transform: translateY(0) !important; // 点击时回到原位 background: rgba(97, 175, 239, 0.25) !important; + + body[theme='light'] & { + background: rgba(9, 105, 218, 0.15) !important; + } } &:focus { outline: none !important; border-color: #61afef !important; box-shadow: 0 0 0 2px rgba(97, 175, 239, 0.2) !important; + + body[theme='light'] & { + border-color: #0969da !important; + box-shadow: 0 0 0 2px rgba(9, 105, 218, 0.2) !important; + } } } } @@ -859,7 +1013,6 @@ defineExpose({ // 修复导航选择框的文字颜色 :deep(.el-select) { .el-select__wrapper:not(.is-disabled) .el-select__selected-item { - color: #ffffff !important; font-weight: 500; } @@ -892,20 +1045,32 @@ defineExpose({ width: 100%; transition: height 0.2s ease-in-out; - :deep(.monaco-editor) { + :deep(.monaco-editor) { width: 100% !important; height: 100% !important; .margin { background-color: #282c34; + + body[theme='light'] & { + background-color: #fafafa; + } } .monaco-editor-background { background-color: #282c34; + + body[theme='light'] & { + background-color: #fafafa; + } } .current-line { background-color: #2c313c; + + body[theme='light'] & { + background-color: #f0f0f0; + } } .view-lines { @@ -970,13 +1135,26 @@ defineExpose({ background-color: #3e4451; border: 1px solid #5c6370; + body[theme='light'] & { + background-color: #ffffff; + border: 1px solid #d0d7de; + } + .el-input__inner { color: #ffffff !important; // 使用白色文字确保在暗色背景下清晰可见 font-weight: 500; + + body[theme='light'] & { + color: #24292f !important; + } } .el-select__suffix { color: #abb2bf; + + body[theme='light'] & { + color: #57606a; + } } } @@ -984,26 +1162,47 @@ defineExpose({ .el-select__wrapper:not(.is-disabled) .el-select__selected-item { color: #ffffff !important; font-weight: 500; + + body[theme='light'] & { + color: #24292f !important; + } } // 修复输入框中的选中文字 .el-select__wrapper .el-select__selected-item { color: #ffffff !important; font-weight: 500; + + body[theme='light'] & { + color: #24292f !important; + } } // 修复占位符文字颜色 .el-select__wrapper .el-select__placeholder { color: #8b949e !important; + + body[theme='light'] & { + color: #6e7781 !important; + } } &:hover .el-input__wrapper { border-color: #61afef; + + body[theme='light'] & { + border-color: #0969da; + } } &.is-focus .el-input__wrapper { border-color: #61afef; box-shadow: 0 0 0 2px rgba(97, 175, 239, 0.2); + + body[theme='light'] & { + border-color: #0969da; + box-shadow: 0 0 0 2px rgba(9, 105, 218, 0.2); + } } } } @@ -1014,18 +1213,38 @@ defineExpose({ border: 1px solid #3e4451; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + body[theme='light'] & { + background-color: #ffffff; + border: 1px solid #d0d7de; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + .el-select-dropdown__item { color: #ffffff !important; // 使用白色文字 font-weight: 500; + body[theme='light'] & { + color: #24292f !important; + } + &:hover { background-color: #3e4451; color: #61afef !important; + + body[theme='light'] & { + background-color: #f6f8fa; + color: #0969da !important; + } } &.selected { background-color: #61afef; color: #ffffff !important; + + body[theme='light'] & { + background-color: #0969da; + color: #ffffff !important; + } } } } @@ -1036,14 +1255,26 @@ defineExpose({ .el-select__selected-item { color: #ffffff !important; font-weight: 500; + + body[theme='light'] & { + color: #24292f !important; + } } .el-select__placeholder { color: #8b949e !important; + + body[theme='light'] & { + color: #6e7781 !important; + } } .el-input__inner { color: #ffffff !important; + + body[theme='light'] & { + color: #24292f !important; + } } } @@ -1051,6 +1282,10 @@ defineExpose({ .el-select__selected-item { color: #ffffff !important; font-weight: 500; + + body[theme='light'] & { + color: #24292f !important; + } } } } diff --git a/src/i18n/lang/en.ts b/src/i18n/lang/en.ts index 3c1944ec97ad67e7df71c4c13c0a11dd6b3847a8..41813cd52b9d05b1cf2d9e0a7db40fbaa8955f2f 100644 --- a/src/i18n/lang/en.ts +++ b/src/i18n/lang/en.ts @@ -39,6 +39,7 @@ export default { speech: 'Speech', embedding: 'Embedding', reranker: 'Reranker', + function_call: 'Function Call', }, }, settings: { @@ -65,10 +66,15 @@ export default { reasoning_model_preference: 'Reasoning Model Preference', embedding_model_preference: 'Embedding Model Preference', reranker_preference: 'Reranker Preference', + function_call_model_preference: 'Function Call Model Preference', + search_method_preference: 'Search Method Preference', chain_of_thought_preference: 'Chain of Thought Preference', auto_execute_preference: 'Auto Execute Preference', save_success: 'Saved successfully', save_failed: 'Save failed', + unknown_provider: 'Unknown Provider', + public_api: 'Public', + private_api: 'Private', }, thinking: { enable_thinking: 'Enable thinking', @@ -221,8 +227,12 @@ export default { appDescription_input: 'Enter an agent description.', modelSelected: 'Model', modelSelected_input: 'Select a model.', + model_context_tip: 'This model will be used for context understanding, question rewriting, and memory storage.', enable_thinking: 'Chain of Thought', enable_thinking_tip: 'Enable thinking', + thinking_warning_title: 'Chain of Thought Warning', + thinking_warning_message: 'The selected model is only used for context understanding, question rewriting, and memory storage. Enabling Chain of Thought will significantly increase processing time for these operations, which may severely impact the overall response speed and user experience of the application. It is recommended to enable this only when deep reasoning is required.', + thinking_warning_confirm: 'Confirm Enable', multi_Dialogue: 'Multi-turn Conversation', multi_Dialogue_select: 'Number of Turns', ability_Configuration: 'Capabilities', @@ -630,6 +640,12 @@ export default { mb: 'MB', cores: 'cores', code_editor: 'Code Editor', + view_code_spec: 'View Code Specification', + code_spec_title: '{lang} Code Security Specification', + health_checking: 'Checking...', + health_unknown: 'Unknown', + health_normal: 'Service Normal', + health_error: 'Service Error', at_least_one_operation: 'At least one variable operation must be retained', operation_select_variable: 'Operation {index}: Please select variable', operation_select_type: 'Operation {index}: Please select operation type', @@ -1385,6 +1401,8 @@ export default { admin: 'Administrator', regular_user: 'Regular User', preferences_settings: 'Preferences Settings', + enabled: 'Enabled', + disabled: 'Disabled', auto_execute_enabled: 'Enabled', auto_execute_disabled: 'Disabled', chain_of_thought_enabled: 'Enabled', @@ -1392,6 +1410,8 @@ export default { reasoning_model: 'Reasoning Model', embedding_model: 'Embedding Model', reranker_model: 'Reranker Model', + function_call_model: 'Function Call Model', + search_method: 'Search Method', default_model: 'Default', not_logged_in: 'Not Logged In', login_prompt: 'Please log in to use full features', diff --git a/src/i18n/lang/zh-cn.ts b/src/i18n/lang/zh-cn.ts index 5fff83298726d8420705b87292fe24e72091ac90..c62cc9ea999e713bf4675d4ab897e54835db2aa7 100644 --- a/src/i18n/lang/zh-cn.ts +++ b/src/i18n/lang/zh-cn.ts @@ -38,6 +38,7 @@ export default { speech: '语音', embedding: '嵌入', reranker: '重排', + function_call: '函数调用', }, }, settings: { @@ -64,10 +65,15 @@ export default { reasoning_model_preference: '推理模型偏好', embedding_model_preference: 'Embedding模型偏好', reranker_preference: 'Reranker偏好', + function_call_model_preference: '函数调用模型偏好', + search_method_preference: '检索方法偏好', chain_of_thought_preference: '思维链偏好', auto_execute_preference: '自动执行偏好', save_success: '保存成功', save_failed: '保存失败', + unknown_provider: '未知供应商', + public_api: '公网', + private_api: '私有', }, thinking: { enable_thinking: '启用思维链', @@ -220,8 +226,12 @@ export default { appDescription_input: '请输入智能体描述', modelSelected: '模型选择', modelSelected_input: '请选择模型', + model_context_tip: '此模型将用于理解上下文、问题改写和记忆存储等功能', enable_thinking: '思维链', enable_thinking_tip: '开启思维链', + thinking_warning_title: '思维链警告', + thinking_warning_message: '当前选择的模型仅在理解上下文、问题改写和记忆存储环节使用。开启思维链会显著增加这些环节的处理时间,可能严重影响应用的整体响应速度和用户体验。建议仅在需要深度推理时开启。', + thinking_warning_confirm: '确认开启', multi_Dialogue: '多轮对话', multi_Dialogue_select: '请选择对话轮次', ability_Configuration: '能力配置', @@ -660,6 +670,12 @@ export default { mb: 'MB', cores: '核心', code_editor: '代码编辑', + view_code_spec: '查阅代码规范', + code_spec_title: '{lang} 代码安全规范', + health_checking: '检查中...', + health_unknown: '未知', + health_normal: '服务正常', + health_error: '服务异常', at_least_one_operation: '至少需要保留一个变量操作', operation_select_variable: '第 {index} 个操作:请选择变量', operation_select_type: '第 {index} 个操作:请选择操作类型', @@ -1415,6 +1431,8 @@ export default { admin: '管理员', regular_user: '普通用户', preferences_settings: '偏好设置', + enabled: '已开启', + disabled: '已关闭', auto_execute_enabled: '已开启', auto_execute_disabled: '已关闭', chain_of_thought_enabled: '已开启', @@ -1422,6 +1440,8 @@ export default { reasoning_model: '推理模型', embedding_model: '嵌入模型', reranker_model: '重排序模型', + function_call_model: '函数调用模型', + search_method: '检索方法', default_model: '默认', not_logged_in: '未登录', login_prompt: '请登录以使用完整功能', diff --git a/src/store/conversation.ts b/src/store/conversation.ts index 2e0cd5beee5c7e46484fe12781f2f2f243c3d5f4..c015c6686761dd330a0e83ac49e9d2a6804d9703 100644 --- a/src/store/conversation.ts +++ b/src/store/conversation.ts @@ -305,12 +305,6 @@ export const useSessionStore = defineStore('conversation', () => { msgData: Record, conversationItem: RobotConversationItem, ) => { - // 🔑 关键修复:检查暂停状态和生成状态,双重保险 - if (isPaused.value || !isAnswerGenerating.value) { - // 手动暂停输出或已停止生成 - isAnswerGenerating.value = false; - return; - } const rawMsgData = msgData.data as string; if (rawMsgData === '[DONE]') { dataTransfers.dataDone(conversationItem, !!params.type); @@ -325,12 +319,19 @@ export const useSessionStore = defineStore('conversation', () => { // 这里json解析 const message = JSON.parse(rawMsgData || '{}'); const eventType = message['event']; + if ('metadata' in message) { conversationItem.metadata = message.metadata; } if ('event' in message) { switch (eventType) { case 'text.add': + // 🔑 只在text.add事件时检查暂停状态 + if (isPaused.value || !isAnswerGenerating.value) { + // 手动暂停输出或已停止生成,跳过文本添加 + break; + } + // console.log('📝 [Store] 收到 text.add 事件'); // 太多了,注释掉避免刷屏 currentMessage.value = message; dataTransfers.textAdd(conversationItem, message); break; diff --git a/src/utils/userPreferences.ts b/src/utils/userPreferences.ts index a6dbfe469a9c45d717713114b74ae09d7556bb7e..59a7f372c49d0392a508f9fd57b59cb515f39f6d 100644 --- a/src/utils/userPreferences.ts +++ b/src/utils/userPreferences.ts @@ -14,6 +14,8 @@ export interface UserPreferences { reasoningModelPreference?: ModelPreference; embeddingModelPreference?: ModelPreference; rerankerPreference?: ModelPreference; + functionCallModelPreference?: ModelPreference; // 新增函数调用模型偏好 + searchMethodPreference?: string; // 新增检索方法偏好 chainOfThoughtPreference?: boolean; autoExecutePreference?: boolean; } @@ -133,6 +135,16 @@ export function getPreferredRerankerModel( + options: T[] +): T | undefined { + const preferences = getUserPreferences(); + return findPreferredOption(options, preferences.functionCallModelPreference); +} + /** * 获取思维链偏好设置 */ @@ -149,6 +161,14 @@ export function getAutoExecutePreference(): boolean { return preferences.autoExecutePreference ?? false; // 默认关闭 } +/** + * 获取检索方法偏好设置 + */ +export function getSearchMethodPreference(): string | undefined { + const preferences = getUserPreferences(); + return preferences.searchMethodPreference; +} + /** * 监听用户偏好设置变化 */ diff --git a/src/views/createapp/components/AgentAppConfig.vue b/src/views/createapp/components/AgentAppConfig.vue index d2fab03c8dcf07a54649b6dc29e661f46206da98..f1ce96f3607a93c985255e665370b1c6dadaf1d2 100644 --- a/src/views/createapp/components/AgentAppConfig.vue +++ b/src/views/createapp/components/AgentAppConfig.vue @@ -13,7 +13,7 @@ import type { Mcp } from './McpDrawer.vue'; import CustomLoading from '../../customLoading/index.vue'; import i18n from '@/i18n'; import defaultIcon from '@/assets/svgs/defaultIcon.webp'; -import { getPreferredReasoningModel, getChainOfThoughtPreference } from '@/utils/userPreferences'; +import { getPreferredReasoningModel, getChainOfThoughtPreference, getPreferredFunctionCallModel } from '@/utils/userPreferences'; const { t } = i18n.global; const route = useRoute(); @@ -185,12 +185,12 @@ async function queryUserList() { } async function queryModelList() { - const [, res] = await api.getAddedModels('chat'); + const [, res] = await api.getAddedModels('function_call'); if (res) { modelOptions.value = res.result; if (modelOptions.value.length > 0) { - // 优先使用用户偏好的推理模型 - const preferredModel = getPreferredReasoningModel(modelOptions.value); + // 优先使用用户偏好的函数调用模型 + const preferredModel = getPreferredFunctionCallModel(modelOptions.value); if (preferredModel) { createAppForm.model = preferredModel.llmId; } else { diff --git a/src/views/createapp/components/appConfig.vue b/src/views/createapp/components/appConfig.vue index 143a7e1c6cde84572ae92995e8ed0d8d1de790a7..ea33d465d5f5c68b7e07061bd894ec3f5f14ee3d 100644 --- a/src/views/createapp/components/appConfig.vue +++ b/src/views/createapp/components/appConfig.vue @@ -1,5 +1,5 @@ diff --git a/src/views/createapp/components/workFlow.vue b/src/views/createapp/components/workFlow.vue index b9edae3780f2eebe3e7da8dc6e23b0cde5d2625b..04501fff0f0c3cbddb1bc685d1a47764c53bae61 100644 --- a/src/views/createapp/components/workFlow.vue +++ b/src/views/createapp/components/workFlow.vue @@ -433,6 +433,7 @@ const editCommonDrawer = (name, desc, nodeId, yamlCode) => { nodeName.value = name; nodeDesc.value = desc; selectedNodeId.value = nodeId; + nodeYamlId.value = nodeId; // 同时设置 nodeYamlId,供各种抽屉组件使用 }; // 编辑yaml @@ -1222,6 +1223,14 @@ const handleZommOnScroll = () => { flowZoom.value = newZoom; }; +// 监听viewport实时变化(包括缩放),解决在节点/连线上滚动时缩放数字不更新的问题 +const handleViewportChange = (viewport) => { + if (viewport?.zoom !== undefined) { + const newZoom = Number(viewport.zoom.toFixed(2)); + flowZoom.value = newZoom; + } +}; + async function layoutGraph(direction) { nodes.value = layout(getNodes.value, getEdges.value, direction); setViewport({ @@ -1653,6 +1662,58 @@ const redrageFlow = (nodesList, edgesList, notesList = []) => { } } }; + } else if (node.callId === 'LLM') { + // LLM节点特殊处理:强制固定output_parameters结构 + newNode.type = 'custom'; + const fixedParameters = { + input_parameters: node.parameters?.input_parameters || {}, + output_parameters: { + reply: { + type: 'string', + description: '定义LLM工具调用的输出' + } + } + }; + newNode.data = { + ...newNode.data, + parameters: fixedParameters + }; + } else if (node.callId === 'RAG') { + // RAG节点特殊处理:强制固定output_parameters结构 + newNode.type = 'custom'; + const fixedParameters = { + input_parameters: node.parameters?.input_parameters || {}, + output_parameters: { + question: { + type: 'string', + description: '定义LLM工具调用的输出容' + }, + corpus: { + type: 'array', + description: '知识库的语料列表' + } + } + }; + newNode.data = { + ...newNode.data, + parameters: fixedParameters + }; + } else if (node.callId === 'MCP') { + // MCP节点特殊处理:强制固定output_parameters结构 + newNode.type = 'custom'; + const fixedParameters = { + input_parameters: node.parameters?.input_parameters || {}, + output_parameters: { + message: { + type: 'string', + description: 'MCP Server的自然语言输出' + } + } + }; + newNode.data = { + ...newNode.data, + parameters: fixedParameters + }; } else { newNode.type = 'custom'; } @@ -2769,7 +2830,67 @@ const saveFlow = async (updateNodeParameter?, debug?) => { if (updateNodeParameter) { updateNodes.forEach((item) => { if (item.stepId === updateNodeParameter.id) { - if (item.type === 'Code') { + if (item.callId === 'RAG') { + // RAG 知识库节点 - 固定output_parameters结构 + if (!item.parameters) { + item.parameters = {}; + } + if (updateNodeParameter.inputStream) { + item.parameters.input_parameters = updateNodeParameter.inputStream; + } + // 强制固定RAG节点的output_parameters结构 + item.parameters.output_parameters = { + question: { + type: 'string', + description: '定义LLM工具调用的输出容' + }, + corpus: { + type: 'array', + description: '知识库的语料列表' + } + }; + } else if (item.callId === 'LLM') { + // LLM 节点 - 固定output_parameters结构 + if (!item.parameters) { + item.parameters = {}; + } + if (updateNodeParameter.inputStream) { + item.parameters.input_parameters = updateNodeParameter.inputStream; + } + // 强制固定LLM节点的output_parameters结构 + item.parameters.output_parameters = { + reply: { + type: 'string', + description: '定义LLM工具调用的输出', + } + }; + } else if (item.callId === 'MCP') { + // MCP 节点 - 固定output_parameters结构 + if (!item.parameters) { + item.parameters = {}; + } + if (updateNodeParameter.inputStream) { + item.parameters.input_parameters = updateNodeParameter.inputStream; + } + // 强制固定MCP节点的output_parameters结构 + item.parameters.output_parameters = { + message: { + type: 'string', + description: 'MCP Server的自然语言输出' + } + }; + } else if (item.callId === 'API') { + // API 节点 + if (!item.parameters) { + item.parameters = {}; + } + if (updateNodeParameter.inputStream) { + item.parameters.input_parameters = updateNodeParameter.inputStream; + } + if (updateNodeParameter.parameters?.output_parameters) { + item.parameters.output_parameters = updateNodeParameter.parameters.output_parameters; + } + } else if (item.type === 'Code') { item.parameters.input_parameters = updateNodeParameter.parameters.input_parameters; item.parameters.output_parameters = updateNodeParameter.parameters.output_parameters; item.parameters.code = updateNodeParameter.parameters.code; @@ -2971,6 +3092,7 @@ defineExpose({ @edges-change="edgesChange" @nodes-change="nodesChange" @paneScroll="handleZommOnScroll" + @viewportChange="handleViewportChange" @viewportChangeEnd="viewportChangeEndFunc" @mouseup="cancelConnectStatus" @pane-context-menu="handleContextMenu" diff --git a/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue b/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue index e268dd7d9f09165833e62b6390de64093a924823..37e61b854b18584f589b1ca2ab7e557983f8267d 100644 --- a/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue +++ b/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue @@ -983,12 +983,6 @@ justify-content: flex-end; gap: 12px; padding: 16px; - border-top: 1px solid #ebeef5; - - // 深色模式适配 - body[theme='dark'] & { - border-top: 1px solid var(--el-border-color-lighter); - } } } diff --git a/src/views/createapp/components/workFlowConfig/CodeNodeDrawer.vue b/src/views/createapp/components/workFlowConfig/CodeNodeDrawer.vue index aed04e7c427fc45ef1be74855ecc54b50bd057cd..f62d47a7ee557640cba1d2a38fe787838f9d8bdc 100644 --- a/src/views/createapp/components/workFlowConfig/CodeNodeDrawer.vue +++ b/src/views/createapp/components/workFlowConfig/CodeNodeDrawer.vue @@ -209,6 +209,33 @@ {{ $t('flow.node_config.code_editor') }} +
+ + +
+ + + + + {{ healthStatusText }} +
+
+ + + + + {{ $t('flow.node_config.view_code_spec') }} + +
@@ -237,6 +264,21 @@
+ + + +
+ +
@@ -244,13 +286,39 @@ import { ref, watch, computed } from 'vue' import { ElMessage } from 'element-plus' import { IconCaretRight } from '@computing/opendesign-icons' -import { Plus, Delete } from '@element-plus/icons-vue' +import { + Plus, + Delete, + Document, + Loading, + SuccessFilled, + CircleCloseFilled, + QuestionFilled +} from '@element-plus/icons-vue' import { getSrcIcon } from '../types' import CodeMonacoEditor from '@/components/CodeMonacoEditor.vue' import VariableChooser from '@/components/VariableChooser.vue' import { useI18n } from 'vue-i18n' +import { marked } from 'marked' +import DOMPurify from 'dompurify' -const { t } = useI18n() +const { t, locale } = useI18n() + +// Markdown渲染函数 +const renderMarkdown = (content: string): string => { + if (!content) return '' + try { + // 使用 marked.parse() 方法(同步方式) + const html = marked.parse(content, { + breaks: true, // 支持换行符 + gfm: true // 启用 GitHub 风格的 Markdown + }) + return DOMPurify.sanitize(html as string) + } catch (error) { + console.error('Markdown渲染失败:', error) + return content + } +} interface Variable { variableName?: string // 自定义变量名 @@ -293,6 +361,12 @@ const drawerVisible = ref(false) const activeName = ref(['basic', 'code', 'variables']) const variableTab = ref('input') const saving = ref(false) +const specDialogVisible = ref(false) +const specContent = ref('') +const specLanguage = ref('python') +const loadingSpec = ref(false) +const sandboxHealthy = ref(null) +const checkingHealth = ref(false) // 节点配置 const nodeConfig = ref({ @@ -718,6 +792,68 @@ const handleLanguageChange = (language: string) => { } } +// 查阅代码规范 +const viewCodeSpec = async () => { + try { + loadingSpec.value = true + specLanguage.value = nodeConfig.value.codeType + + // 获取当前语言设置(zh 或 en) + const currentLocale = locale.value || 'zh' + + // 调用API获取规范,传递语言参数 + const response = await fetch(`/api/sandbox/code-spec/${nodeConfig.value.codeType}?lang=${currentLocale}`) + if (!response.ok) { + throw new Error('获取代码规范失败') + } + + const result = await response.json() + if (result.success && result.data) { + specContent.value = result.data.content + specDialogVisible.value = true + } else { + ElMessage.error(result.message || '获取代码规范失败') + } + } catch (error) { + console.error('获取代码规范失败:', error) + ElMessage.error('获取代码规范失败,请稍后重试') + } finally { + loadingSpec.value = false + } +} + +// 检查沙箱服务健康状态 +const checkSandboxHealth = async () => { + try { + checkingHealth.value = true + const response = await fetch('/api/sandbox/health') + if (!response.ok) { + sandboxHealthy.value = false + return + } + + const result = await response.json() + sandboxHealthy.value = result.success === true + } catch (error) { + console.error('检查沙箱服务健康状态失败:', error) + sandboxHealthy.value = false + } finally { + checkingHealth.value = false + } +} + +// 计算健康状态的显示文本和样式 +const healthStatusText = computed(() => { + if (checkingHealth.value) return t('flow.node_config.health_checking') + if (sandboxHealthy.value === null) return t('flow.node_config.health_unknown') + return sandboxHealthy.value ? t('flow.node_config.health_normal') : t('flow.node_config.health_error') +}) + +const healthStatusClass = computed(() => { + if (sandboxHealthy.value === null) return 'status-unknown' + return sandboxHealthy.value ? 'status-healthy' : 'status-unhealthy' +}) + const addInputVariable = () => { inputVariables.value.push({ variableName: '', @@ -875,6 +1011,10 @@ const saveNode = async () => { // 监听器 watch(() => props.visible, (newVal) => { drawerVisible.value = newVal + // 当抽屉打开时检查沙箱服务健康状态 + if (newVal) { + checkSandboxHealth() + } }, { immediate: true }) watch(drawerVisible, (newVal) => { @@ -992,35 +1132,89 @@ watch(inputVariables, () => { background: #1f2329; }; - .code-content { - :deep(.el-collapse-item__header) { - padding: 16px 24px; - font-weight: 500; - background: var(--el-fill-color-extra-light); - border-bottom: 1px solid var(--el-border-color-lighter); - color: var(--el-text-color-primary); - - body[theme='dark'] & { - background: var(--o-bash-bg, #2a2f37); - border-bottom-color: var(--el-border-color); - color: #e4e8ee; - } + .code-content { + :deep(.el-collapse-item__header) { + padding: 16px 24px; + font-weight: 500; + background: var(--el-fill-color-extra-light); + color: var(--el-text-color-primary); + + body[theme='dark'] & { + background: var(--o-bash-bg, #2a2f37); + border-bottom-color: var(--el-border-color); + color: #e4e8ee; } + } - :deep(.el-collapse-item__wrap) { - border-bottom: none; - } + :deep(.el-collapse-item__wrap) { + border-bottom: none; + } - :deep(.el-collapse-item__content) { - padding: 20px 24px; - background: var(--el-bg-color); + :deep(.el-collapse-item__content) { + padding: 20px 24px; + background: var(--el-bg-color); + + body[theme='dark'] & { + background: #1f2329; + } + } + + // 代码编辑器标题栏操作区域 + .code-header-actions { + margin-left: auto; + margin-right: 8px; + display: flex; + align-items: center; + gap: 12px; + + .sandbox-health-status { + display: flex; + align-items: center; + gap: 6px; + padding: 0 12px; + height: 100%; + border-radius: 4px; + font-size: 12px; + transition: all 0.3s ease; - body[theme='dark'] & { - background: #1f2329; + .el-icon { + font-size: 14px; + } + + .status-text { + font-weight: 500; + } + + &.status-healthy { + color: #67c23a; + background: rgba(103, 194, 58, 0.1); + + body[theme='dark'] & { + background: rgba(103, 194, 58, 0.15); + } + } + + &.status-unhealthy { + color: #f56c6c; + background: rgba(245, 108, 108, 0.1); + + body[theme='dark'] & { + background: rgba(245, 108, 108, 0.15); + } + } + + &.status-unknown { + color: #909399; + background: rgba(144, 147, 153, 0.1); + + body[theme='dark'] & { + background: rgba(144, 147, 153, 0.15); + } } } } } + } .basic-content, .execution-content { @@ -1247,4 +1441,274 @@ watch(inputVariables, () => { :deep(.transparent-modal) { background-color: transparent !important; } + +// 代码规范弹窗样式 +.code-spec-dialog { + :deep(.el-dialog__body) { + max-height: 70vh !important; + overflow-y: auto !important; + overflow-x: hidden !important; + padding: 20px !important; + + body[theme='dark'] & { + background: #1f2329; + } + } + + :deep(.spec-content) { + font-size: 14px; + line-height: 1.8; + color: var(--el-text-color-primary); + word-wrap: break-word; + + body[theme='dark'] & { + color: #e4e8ee; + } + + // Markdown样式 - 直接应用到标签上 + h1 { + font-size: 24px; + font-weight: 600; + margin: 20px 0 16px; + padding-bottom: 8px; + border-bottom: 1px solid var(--el-border-color-lighter); + } + + h2 { + font-size: 20px; + font-weight: 600; + margin: 18px 0 12px; + } + + h3 { + font-size: 16px; + font-weight: 600; + margin: 16px 0 10px; + } + + p { + margin: 8px 0; + } + + ul, ol { + padding-left: 24px; + margin: 8px 0; + } + + li { + margin: 4px 0; + } + + code { + background: var(--el-fill-color-extra-light); + padding: 2px 6px; + border-radius: 3px; + font-family: 'Courier New', monospace; + font-size: 13px; + + body[theme='dark'] & { + background: var(--o-bash-bg, #2a2f37); + } + } + + pre { + background: var(--el-fill-color-extra-light); + padding: 12px; + border-radius: 6px; + overflow-x: auto; + margin: 12px 0; + + body[theme='dark'] & { + background: var(--o-bash-bg, #2a2f37); + } + + code { + background: transparent; + padding: 0; + } + } + + blockquote { + border-left: 4px solid var(--el-color-primary); + padding-left: 16px; + margin: 12px 0; + color: var(--el-text-color-secondary); + + body[theme='dark'] & { + color: #d3dce9; + } + } + + table { + border-collapse: collapse; + width: 100%; + margin: 12px 0; + } + + th, td { + border: 1px solid var(--el-border-color-lighter); + padding: 8px 12px; + text-align: left; + + body[theme='dark'] & { + border-color: var(--el-border-color); + } + } + + th { + background: var(--el-fill-color-extra-light); + font-weight: 600; + + body[theme='dark'] & { + background: var(--o-bash-bg, #2a2f37); + } + } + + hr { + border: none; + border-top: 1px solid var(--el-border-color-lighter); + margin: 20px 0; + + body[theme='dark'] & { + border-top-color: var(--el-border-color); + } + } + } +} + + + + \ No newline at end of file diff --git a/src/views/createapp/components/workFlowConfig/LLMNodeDrawer.vue b/src/views/createapp/components/workFlowConfig/LLMNodeDrawer.vue index aaa14b3d97f52da1ccd6fc48a61dc5a989a58879..2351cbd738eab1109f35c628fef9235863ee75c8 100644 --- a/src/views/createapp/components/workFlowConfig/LLMNodeDrawer.vue +++ b/src/views/createapp/components/workFlowConfig/LLMNodeDrawer.vue @@ -76,57 +76,39 @@ :placeholder="$t('llmNode.model_placeholder')" style="width: 100%" > +
- - -
- - - - - - - - - - - - - -
- -