From 16113c37a938a73043e894458cf4926c5f176402 Mon Sep 17 00:00:00 2001 From: Ethan-Zhang Date: Sun, 2 Nov 2025 02:10:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?Fix:=20=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E8=8A=82=E7=82=B9=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D&=E4=BB=A3=E7=A0=81=E6=8A=BD=E5=B1=89?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9C=8D=E5=8A=A1=E7=8A=B6=E6=80=81=E4=B8=8E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83=E6=89=8B=E5=86=8C=E6=9F=A5?= =?UTF-8?q?=E9=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 +- src/apis/paths/llm.ts | 43 +- src/apis/paths/type.ts | 1 + src/components/CodeMonacoEditor.vue | 247 +++++++- src/i18n/lang/en.ts | 10 + src/i18n/lang/zh-cn.ts | 10 + src/store/conversation.ts | 13 +- src/views/createapp/components/appConfig.vue | 203 ++++++- src/views/createapp/components/workFlow.vue | 56 +- .../workFlowConfig/ApiCallDrawer.vue | 6 - .../workFlowConfig/CodeNodeDrawer.vue | 510 +++++++++++++++- .../workFlowConfig/LLMNodeDrawer.vue | 572 +++++++++++++----- .../workFlowConfig/MCPNodeDrawer.vue | 20 +- .../workFlowConfig/RAGNodeDrawer.vue | 123 ++-- .../createapp/components/workFlowDebug.vue | 4 +- src/views/createapp/index.vue | 2 + 16 files changed, 1576 insertions(+), 249 deletions(-) diff --git a/package.json b/package.json index 01ef828d..7d6b043d 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/llm.ts b/src/apis/paths/llm.ts index 923fc5a1..ca170f96 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/type.ts b/src/apis/paths/type.ts index 0f0d832e..78d4f98b 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 a1b07f9d..98949398 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 3c1944ec..578e43c5 100644 --- a/src/i18n/lang/en.ts +++ b/src/i18n/lang/en.ts @@ -221,8 +221,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 +634,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', diff --git a/src/i18n/lang/zh-cn.ts b/src/i18n/lang/zh-cn.ts index 5fff8329..5ddee10d 100644 --- a/src/i18n/lang/zh-cn.ts +++ b/src/i18n/lang/zh-cn.ts @@ -220,8 +220,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 +664,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} 个操作:请选择操作类型', diff --git a/src/store/conversation.ts b/src/store/conversation.ts index 2e0cd5be..c015c668 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/views/createapp/components/appConfig.vue b/src/views/createapp/components/appConfig.vue index 143a7e1c..3f2d07d6 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 b9edae37..cbe653da 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({ @@ -2769,7 +2778,51 @@ const saveFlow = async (updateNodeParameter?, debug?) => { if (updateNodeParameter) { updateNodes.forEach((item) => { if (item.stepId === updateNodeParameter.id) { - if (item.type === 'Code') { + if (item.callId === 'RAG') { + // RAG 知识库节点 + 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.callId === 'LLM') { + // LLM 节点 + 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.callId === 'MCP') { + // MCP 节点 + 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.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 +3024,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 e268dd7d..37e61b85 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 aed04e7c..f62d47a7 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 aaa14b3d..2351cbd7 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%" > +
- - -
- - - - - - - - - - - - - -
- -