From 98195e19db9440349ba204fe41bb3b541d1f24b1 Mon Sep 17 00:00:00 2001 From: Ethan-Zhang Date: Wed, 12 Nov 2025 14:30:50 +0800 Subject: [PATCH 1/2] =?UTF-8?q?Fix:=20=E5=B7=A5=E4=BD=9C=E6=B5=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/paths/llm.ts | 29 -- src/apis/paths/type.ts | 34 ++- src/components/dialoguePanel/DialogueFlow.vue | 53 +++- src/i18n/lang/en.ts | 25 +- src/i18n/lang/zh-cn.ts | 14 + src/store/conversation.ts | 58 +++- src/views/createapp/components/appConfig.vue | 2 +- src/views/createapp/components/workFlow.vue | 57 +++- .../workFlowConfig/ApiCallDrawer.vue | 261 +++++++++--------- .../workFlowConfig/ChoiceBranchDrawer.vue | 31 ++- .../workFlowConfig/LLMNodeDrawer.vue | 107 +++++-- .../workFlowConfig/PluginListPanel.vue | 12 +- .../workFlowConfig/RAGNodeDrawer.vue | 115 +++++--- .../components/workFlowConfig/useDnD.js | 8 +- .../dialogue/components/DialogueSession.vue | 6 +- 15 files changed, 551 insertions(+), 261 deletions(-) diff --git a/src/apis/paths/llm.ts b/src/apis/paths/llm.ts index ca170f96..b27c0e52 100644 --- a/src/apis/paths/llm.ts +++ b/src/apis/paths/llm.ts @@ -50,39 +50,10 @@ 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 78d4f98b..d0346645 100644 --- a/src/apis/paths/type.ts +++ b/src/apis/paths/type.ts @@ -223,10 +223,40 @@ export interface AddedModalList { openaiBaseUrl?: string; openaiApiKey: string; modelName: string; - maxTokens: string; - type?: string; // 模型类型: chat, embedding, reranker等 + maxTokens: number; + isEditable?: boolean; + type?: string[] | string; // 模型类型: chat, embedding, reranker等 + provider?: string; + // 基础能力字段 + supportsStreaming?: boolean; + supportsFunctionCalling?: boolean; + supportsTools?: boolean; + supportsToolChoice?: boolean; + // 思维链相关 supportsThinking?: boolean; canToggleThinking?: boolean; + supportsReasoningContent?: boolean; + supportsEnableThinking?: boolean; + supportsThinkingBudget?: boolean; + // 输出格式 + supportsJsonMode?: boolean; + supportsStructuredOutput?: boolean; + supportsResponseFormat?: boolean; + // 模型参数 + supportsTemperature?: boolean; + supportsTopP?: boolean; + supportsTopK?: boolean; + supportsFrequencyPenalty?: boolean; + supportsPresencePenalty?: boolean; + supportsMinP?: boolean; + // 其他功能 + supportsEnableSearch?: boolean; + supportsContext?: boolean; + supportsExtraBody?: boolean; + supportsStreamOptions?: boolean; + // 其他信息 + maxTokensParam?: string; + notes?: string; } /** * teamKnowledgeList, 获取teamKnowledgeList列表 diff --git a/src/components/dialoguePanel/DialogueFlow.vue b/src/components/dialoguePanel/DialogueFlow.vue index 046a08f3..c4ae8c45 100644 --- a/src/components/dialoguePanel/DialogueFlow.vue +++ b/src/components/dialoguePanel/DialogueFlow.vue @@ -145,17 +145,20 @@ watch( taskId.value = props.flowdata?.taskId; } else { let newContentList = props.flowdata?.data; - for (const newContent of newContentList) { - if (!(newContent instanceof Array)) { - continue; - } - for (const item of newContent) { - let input = item.data.input; - const isDuplicate = contents.value[0].data[0].some( - (it) => it.id === item.id, - ); - if (!isDuplicate && input) { - contents.value[0].data[0].push(item); + // 🔑 修复:添加防御性检查,确保 newContentList 是可迭代的数组 + if (newContentList && Array.isArray(newContentList)) { + for (const newContent of newContentList) { + if (!(newContent instanceof Array)) { + continue; + } + for (const item of newContent) { + let input = item.data.input; + const isDuplicate = contents.value[0].data[0].some( + (it) => it.id === item.id, + ); + if (!isDuplicate && input) { + contents.value[0].data[0].push(item); + } } } } @@ -228,6 +231,10 @@ watch(
{{ props.flowdata.title }} + + + {{ $t('flow.cancelled') }} +
@@ -300,6 +307,11 @@ watch(
+ {{ secItem.title }}
@@ -338,7 +350,8 @@ watch( -
+ +
-
+
{{ item.message }} @@ -633,4 +646,18 @@ watch( .border-grey { border: 1px solid var(--o-border-color); } + +// 🔑 新增:已取消标签样式 +.cancelled-label { + display: inline-block; + margin-left: 8px; + padding: 2px 8px; + font-size: 12px; + color: var(--o-text-color-secondary); + background-color: var(--o-fill-color-light); + border: 1px solid var(--o-border-color); + border-radius: 4px; + line-height: 16px; + font-weight: normal; +} diff --git a/src/i18n/lang/en.ts b/src/i18n/lang/en.ts index 14759c54..d667de96 100644 --- a/src/i18n/lang/en.ts +++ b/src/i18n/lang/en.ts @@ -502,12 +502,16 @@ export default { is_compress_label: 'Enable Compression', is_classify_by_doc_label: 'Classify by Document', is_related_surrounding_label: 'Related Context', - keyword_search: 'Keyword Search', - vector_search: 'Vector Search', - hybrid_search: 'Hybrid Search', - doc2chunk_search: 'Document Chunk Search', - doc2chunk_bfs_search: 'Document Chunk Search (BFS)', - enhanced_by_llm_search: 'LLM Enhanced Search', + keyword_search: 'keyword', + vector_search: 'vector', + keyword_and_vector_search: 'keyword_and_vector', + hybrid_search: 'keyword_and_vector', + dynamic_weighted_keyword_and_vector_search: 'dynamic_weighted_keyword_and_vector', + dynamic_weighted_keyword_search: 'dynamic_weighted_keyword', + query_extend_search: 'query_extend', + doc2chunk_search: 'doc2chunk', + doc2chunk_bfs_search: 'doc2chunk_bfs', + enhanced_by_llm_search: 'enhanced_by_llm', query_knowledge_base_failed: 'Failed to query knowledge base', no_knowledge_base_available: 'No knowledge base available', }, @@ -555,6 +559,15 @@ export default { exit_loop: 'Exit Loop', loop_subflow: 'Loop Subflow', loop_condition: 'Loop Condition', + // System nodes + llm: 'LLM', + rag: 'RAG', + code: 'Code', + mcp: 'MCP', + // API nodes + api: 'API', + sql: 'SQL', + graph: 'Graph', }, // File Extractor Configuration file_extractor: { diff --git a/src/i18n/lang/zh-cn.ts b/src/i18n/lang/zh-cn.ts index a2005fb2..ec0e8014 100644 --- a/src/i18n/lang/zh-cn.ts +++ b/src/i18n/lang/zh-cn.ts @@ -534,7 +534,11 @@ export default { is_related_surrounding_label: '是否关联上下文', keyword_search: '关键词检索', vector_search: '向量化检索', + keyword_and_vector_search: '关键字向量混合检索', hybrid_search: '混合检索', + dynamic_weighted_keyword_and_vector_search: '动态权重关键字向量混合检索', + dynamic_weighted_keyword_search: '动态权重关键字检索', + query_extend_search: '查询扩写检索', doc2chunk_search: '文档分块检索', doc2chunk_bfs_search: '文档分块检索(bfs)', enhanced_by_llm_search: '大模型增强检索', @@ -545,6 +549,7 @@ export default { flow_start: '工作流进行中', flow_end: '工作流结束', flow_cancel: '取消运行', + cancelled: '已取消', // 🔑 新增:已取消状态标签 flow_risk: '风险提示', flow_params_error: '缺少参数', flow_pause: '工作流暂停', @@ -585,6 +590,15 @@ export default { exit_loop: '退出循环', loop_subflow: '循环子工作流', loop_condition: '循环终止条件', + // 系统节点 + llm: '大模型', + rag: '知识库', + code: '代码执行', + mcp: 'MCP工具', + // API节点 + api: 'API', + sql: 'SQL', + graph: '图数据库', }, // 文件提取器配置 file_extractor: { diff --git a/src/store/conversation.ts b/src/store/conversation.ts index 0b0f11ed..d867f736 100644 --- a/src/store/conversation.ts +++ b/src/store/conversation.ts @@ -353,8 +353,9 @@ export const useSessionStore = defineStore('conversation', () => { switch (eventType) { case 'text.add': // 🔑 只在text.add事件时检查暂停状态 - if (isPaused.value || !isAnswerGenerating.value) { - // 手动暂停输出或已停止生成,跳过文本添加 + // 🔑 修复:移除 isAnswerGenerating 检查,避免 chat 套件在工作流结束后的 text.add 事件被跳过 + if (isPaused.value) { + // 手动暂停输出,跳过文本添加 break; } // console.log('📝 [Store] 收到 text.add 事件'); // 太多了,注释掉避免刷屏 @@ -814,6 +815,20 @@ export const useSessionStore = defineStore('conversation', () => { ] as RobotConversationItem; targetItem.message[0] += '暂停生成'; targetItem.isFinish = true; + + // 🔑 修复:如果有工作流数据,需要更新工作流状态为取消(保持完整的数据结构) + if (targetItem.flowdata) { + targetItem.flowdata = { + id: targetItem.flowdata.id, + title: i18n.global.t('flow.flow_cancel'), + progress: targetItem.flowdata.progress, + status: 'cancelled', // 🔑 修复:应该是 'cancelled'(双l) + display: true, + data: targetItem.flowdata.data || [], // 🔑 确保 data 始终是数组,避免 "not iterable" 错误 + taskId: undefined, // 🔑 清空 taskId,避免显示等待确认的按钮 + }; + } + cancel(); const resp = await api.stopGeneration(currentTaskId.value || ''); if (resp?.[1]?.code === 200) { @@ -1009,11 +1024,28 @@ export const useSessionStore = defineStore('conversation', () => { }; // 看有没有取消的 let isCancelled = false; + let hasWaitingStep = false; // 🔑 新增:标记是否有waiting的步骤 + for (let i = 0; i < record.steps.length; i++) { + // 🔑 修复:当流程已经终止(cancelled/success/error),且步骤还在waiting状态时 + // 将步骤状态改为cancelled,避免显示loading图标 + let stepStatus = record.steps[i].stepStatus; + const isFlowTerminated = ['cancelled', 'success', 'error'].includes(record.flowStatus); + const isStepWaiting = stepStatus === 'waiting'; + + // 🔑 记录原始的waiting状态 + if (isStepWaiting) { + hasWaitingStep = true; + } + + if (isFlowTerminated && isStepWaiting) { + stepStatus = 'cancelled'; + } + flowData.data[0].push({ id: record.steps[i].stepId, title: record.steps[i].stepName, - status: record.steps[i].stepStatus, + status: stepStatus, data: { input: record.steps[i].input, output: record.steps[i].output, @@ -1024,9 +1056,13 @@ export const useSessionStore = defineStore('conversation', () => { isCancelled = true; } } - if (isCancelled) { + + // 🔑 修复:如果有步骤被取消,或者流程标记为success但还有waiting的步骤(说明被中断) + // 则将整体流程状态改为cancelled + if (isCancelled || (record.flowStatus === 'success' && hasWaitingStep)) { flowData.status = 'cancelled'; } + return flowData; }; @@ -1067,6 +1103,20 @@ export const useSessionStore = defineStore('conversation', () => { const lastItem = conversationList.value[conversationList.value.length - 1] as RobotConversationItem; if (lastItem && lastItem.belong === 'robot' && !lastItem.isFinish) { lastItem.isFinish = true; + + // 🔑 修复:如果有工作流数据,需要更新工作流状态为取消(保持完整的数据结构) + if (lastItem.flowdata) { + lastItem.flowdata = { + id: lastItem.flowdata.id, + title: i18n.global.t('flow.flow_cancel'), + progress: lastItem.flowdata.progress, + status: 'cancelled', // 🔑 修复:应该是 'cancelled'(双l) + display: true, + data: lastItem.flowdata.data || [], // 🔑 确保 data 始终是数组,避免 "not iterable" 错误 + taskId: undefined, // 🔑 清空 taskId,避免显示等待确认的按钮 + }; + } + // 添加中断标记 if (lastItem.message && Array.isArray(lastItem.message)) { lastItem.message[lastItem.currentInd || 0] += ' [已中断]'; diff --git a/src/views/createapp/components/appConfig.vue b/src/views/createapp/components/appConfig.vue index 42de2e11..9b2b8eab 100644 --- a/src/views/createapp/components/appConfig.vue +++ b/src/views/createapp/components/appConfig.vue @@ -588,7 +588,7 @@ defineExpose({ {{ $t('main.addQuestion') }} diff --git a/src/views/createapp/components/workFlow.vue b/src/views/createapp/components/workFlow.vue index 04501fff..1cbc0696 100644 --- a/src/views/createapp/components/workFlow.vue +++ b/src/views/createapp/components/workFlow.vue @@ -276,8 +276,14 @@ onConnect((e) => { // 获取当前状态 const sourceStatus = sourceItem?.data?.status || 'default'; const targetStatus = targetItem?.data?.status || 'default'; + + // 🔑 重要:将sourceHandle保存为边的branchId,用于Choice节点的分支识别 + // 对于Choice节点,e.sourceHandle包含了分支ID(如if_xxx_0或else_xxx) + const branchId = e.sourceHandle || ''; + addEdges({ ...e, + branchId: branchId, // 显式设置branchId data: { sourceStatus, targetStatus, @@ -1270,8 +1276,38 @@ const queryAllFlowServices = () => { }) .then((res) => { const services = res[1]?.result.services || []; - apiServiceList.value = services; - allApiServiceList.value = services; + + // 为节点添加国际化支持 + const servicesWithI18n = services.map(service => { + if (service.nodeMetaDatas && Array.isArray(service.nodeMetaDatas)) { + service.nodeMetaDatas = service.nodeMetaDatas.map(node => { + // 判断是否为自定义API/插件节点: + // 1. service.type === 'plugin' 表示这是插件类型的服务 + // 2. 或者 service.serviceId 存在且不等于 callId(排除内置节点) + const isCustomPlugin = service.type === 'plugin' || + (service.serviceId && service.serviceId !== node.callId); + + // 只对内置节点类型进行国际化,保留自定义API节点的原始name + if (!isCustomPlugin) { + // 内置节点:根据callId获取国际化的节点名称 + const nodeKey = node.callId?.toLowerCase(); + const i18nKey = `flow.node_names.${nodeKey}`; + + // 如果有对应的翻译,使用翻译;否则保持原名称 + if (i18n.global.te(i18nKey)) { + node.name = i18n.global.t(i18nKey); + } + } + // else: 自定义API节点,保持接口返回的原始name + + return node; + }); + } + return service; + }); + + apiServiceList.value = servicesWithI18n; + allApiServiceList.value = servicesWithI18n; apiLoading.value = false; }); }; @@ -1797,14 +1833,22 @@ const redrageFlow = (nodesList, edgesList, notesList = []) => { setEdges(newEdgeList); // 同步更新组件的edges ref edges.value = newEdgeList; - // 回显节点和边后,判断各节点连接状态 - // 使用 nextTick 确保DOM更新完成后再执行连接检查 + + // 🔑 重要:延时更新边的路径,确保Choice节点的Handle已经完全渲染 + // VueFlow 需要时间来识别新渲染的Handle,特别是Choice节点的动态Handle nextTick(() => { + // 第一次更新 try { nodeAndLineConnection(); } catch (error: any) { console.error('nodeAndLineConnection执行失败:', error); } + + // 🔑 额外延时更新,确保Choice节点的Handle DOM完全就绪 + setTimeout(() => { + // 强制VueFlow重新计算所有边的路径 + setEdges([...newEdgeList]); + }, 150); }); }; @@ -2789,9 +2833,10 @@ const saveFlow = async (updateNodeParameter?, debug?) => { }); // 处理边 const updateEdges = currentEdges.map((item) => { - let branchId = item.sourceHandle; + // 🔑 优先使用item.branchId,其次是sourceHandle,最后根据源节点类型生成 + let branchId = item.branchId || item.sourceHandle; - // 如果没有sourceHandle,根据源节点类型生成默认的branchId + // 如果都没有,根据源节点类型生成默认的branchId if (!branchId) { const sourceNode = currentNodes.find(node => node.id === item.source); if (sourceNode) { diff --git a/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue b/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue index 37e61b85..5eff5c07 100644 --- a/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue +++ b/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue @@ -135,7 +135,6 @@
-
{{ content }}
+
{{ Array.isArray(content) ? content[0] : content }}
(false); + +// 🔑 初始化自动执行状态 +onMounted(() => { + // 从用户偏好中获取自动执行设置 + autoExecuteRef.value = getAutoExecutePreference(); + + // 再次确保全局收集器设置正确 + (window as any).currentConversationAttachments = currentConversationAttachments; +}); + +// 🔑 自动执行开关变化处理 +const autoExecuteChange = async (value: boolean) => { + autoExecuteRef.value = value; + await nextTick(); + // 更新用户偏好设置 + api.updateUserInfo({ autoExecute: value }); +}; + // markdown处理函数已移除,因为DialoguePanel会自己处理 const chatContainerRef = ref(null); @@ -370,12 +391,6 @@ function useConversations() { // 🔑 立即导出到全局,以便DialoguePanel可以访问 (window as any).currentConversationAttachments = currentConversationAttachments; - // 🔑 在组件挂载时确保全局收集器可用 - onMounted(() => { - // 再次确保全局收集器设置正确 - (window as any).currentConversationAttachments = currentConversationAttachments; - }); - // 添加消息防抖机制 let messageQueue: StreamChunk[] = []; let processingTimer: NodeJS.Timeout | null = null; @@ -862,7 +877,10 @@ const handleSendMessage = async ( undefined, undefined, undefined, - true, + true, // isDebug + undefined, // llmId + undefined, // enableThinking + autoExecuteRef.value, // 🔑 传递自动执行状态 ); }; @@ -1018,7 +1036,7 @@ watch( :groupId="getItem(item as any, 'groupId') || ''" :type="(item as any).belong" :inputParams="getItem(item as any, 'params') || {}" - :content="Array.isArray((item as any).message) ? (item as any).message : [(item as any).message]" + :content="(item as any).message" :echartsObj="getItem(item as any, 'echartsObj')" :recordList=" (item as any).belong === 'robot' && (item as any).messageList ? (item as any).messageList.getRecordIdList() : [] @@ -1055,30 +1073,61 @@ watch(
-
-