mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-11 02:08:51 +02:00
Add files via upload
This commit is contained in:
4 files changed
+390
-114
No files matched your search
+106
-1
@@ -85,6 +85,7 @@
|
|||||||
"agentsManagement": "Agent management",
|
"agentsManagement": "Agent management",
|
||||||
"roles": "Roles",
|
"roles": "Roles",
|
||||||
"rolesManagement": "Roles Management",
|
"rolesManagement": "Roles Management",
|
||||||
|
"workflows": "Graph Orchestration",
|
||||||
"settings": "System settings",
|
"settings": "System settings",
|
||||||
"hitl": "Human-in-the-loop",
|
"hitl": "Human-in-the-loop",
|
||||||
"c2": "C2",
|
"c2": "C2",
|
||||||
@@ -2915,7 +2916,111 @@
|
|||||||
"mcpDisabledBadgeTitle": "Off in MCP Management; check only expresses role linkage—turn on in MCP to run",
|
"mcpDisabledBadgeTitle": "Off in MCP Management; check only expresses role linkage—turn on in MCP to run",
|
||||||
"roleFilterOnBanner": "These tools are checked and linked to this role (independent of MCP-wide enable).",
|
"roleFilterOnBanner": "These tools are checked and linked to this role (independent of MCP-wide enable).",
|
||||||
"roleFilterOffBanner": "These tools are unchecked and not linked to this role.",
|
"roleFilterOffBanner": "These tools are unchecked and not linked to this role.",
|
||||||
"checkboxLinkTitle": "Check to link this tool to this role"
|
"checkboxLinkTitle": "Check to link this tool to this role",
|
||||||
|
"bindWorkflow": "Bind graph workflow",
|
||||||
|
"bindWorkflowHint": "When a workflow is selected, conversations with this role automatically run the bound graph; workflow fields are configured freely in the graph JSON.",
|
||||||
|
"workflowPolicy": "Workflow trigger policy",
|
||||||
|
"workflowPolicyAuto": "Auto trigger",
|
||||||
|
"workflowPolicyOff": "Off",
|
||||||
|
"noWorkflowBind": "No workflow",
|
||||||
|
"workflowDisabledSuffix": " (disabled)"
|
||||||
|
},
|
||||||
|
"workflows": {
|
||||||
|
"title": "Graph Orchestration",
|
||||||
|
"newGraph": "New graph",
|
||||||
|
"processLibrary": "Process library",
|
||||||
|
"nodeLibrary": "Node library",
|
||||||
|
"emptyList": "No graph workflows yet",
|
||||||
|
"statusEnabled": "Enabled",
|
||||||
|
"statusDisabled": "Disabled",
|
||||||
|
"metaId": "ID",
|
||||||
|
"metaName": "Name",
|
||||||
|
"metaDescription": "Description",
|
||||||
|
"metaEnabled": "Enabled",
|
||||||
|
"namePlaceholder": "Basic Web scan",
|
||||||
|
"descriptionPlaceholder": "Optional",
|
||||||
|
"connect": "Connect",
|
||||||
|
"connecting": "Connecting",
|
||||||
|
"deleteSelected": "Delete selected",
|
||||||
|
"autoLayout": "Auto layout",
|
||||||
|
"canvasEmpty": "Drag nodes from the left onto the canvas, or click node buttons to add quickly",
|
||||||
|
"properties": "Properties",
|
||||||
|
"nodeProperties": "Node properties",
|
||||||
|
"edgeProperties": "Edge properties",
|
||||||
|
"deleteNode": "Delete node",
|
||||||
|
"deleteEdge": "Delete edge",
|
||||||
|
"propertyEmpty": "Select a node or edge to edit properties",
|
||||||
|
"propLabel": "Name",
|
||||||
|
"propType": "Type",
|
||||||
|
"customFields": "Custom fields",
|
||||||
|
"addField": "Add field",
|
||||||
|
"noCustomFields": "No custom fields",
|
||||||
|
"nodes": {
|
||||||
|
"start": "Start",
|
||||||
|
"tool": "Tool",
|
||||||
|
"agent": "Agent",
|
||||||
|
"condition": "Condition",
|
||||||
|
"hitl": "Approval",
|
||||||
|
"output": "Output",
|
||||||
|
"end": "End",
|
||||||
|
"default": "Node"
|
||||||
|
},
|
||||||
|
"edges": {
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"inputKeys": "Input variables",
|
||||||
|
"mcpTool": "MCP tool",
|
||||||
|
"selectTool": "Select a tool",
|
||||||
|
"toolDisabled": " (disabled)",
|
||||||
|
"argumentsTemplate": "Arguments template",
|
||||||
|
"timeoutSeconds": "Timeout (seconds)",
|
||||||
|
"optional": "Optional",
|
||||||
|
"agentMode": "Agent mode",
|
||||||
|
"inputSource": "Input source",
|
||||||
|
"nodeInstruction": "Node instruction",
|
||||||
|
"instructionPlaceholder": "Describe what this node should accomplish",
|
||||||
|
"outputKey": "Output variable name",
|
||||||
|
"conditionExpression": "Condition expression",
|
||||||
|
"conditionHint": "The node computes matched (true/false); outgoing edges define branches: first edge is \"Yes\", second is \"No\". You can also write <code>{{previous.matched}} == \"true\"</code> on the edge.",
|
||||||
|
"edgeCondition": "Edge condition",
|
||||||
|
"edgeConditionHintCondition": "{{previous.matched}} == \"true\" (Yes) or == \"false\" (No)",
|
||||||
|
"edgeConditionHintExample": "e.g. {{previous.output}} == \"ok\"",
|
||||||
|
"edgeBranchHint": "The first edge from a condition node defaults to the \"Yes\" branch, the second to \"No\"; you can customize conditions here.",
|
||||||
|
"hitlPrompt": "Approval prompt",
|
||||||
|
"hitlPromptPlaceholder": "Approve to continue",
|
||||||
|
"hitlReviewer": "Reviewer",
|
||||||
|
"outputSource": "Variable source",
|
||||||
|
"endTemplate": "End summary template"
|
||||||
|
},
|
||||||
|
"defaultHitlPrompt": "Please approve whether this step should continue",
|
||||||
|
"nodeFallback": "Node {{n}}",
|
||||||
|
"loadFailed": "Failed to load workflows",
|
||||||
|
"saveFailed": "Failed to save workflow",
|
||||||
|
"deleteFailed": "Failed to delete workflow",
|
||||||
|
"saved": "Workflow saved",
|
||||||
|
"deleted": "Workflow deleted",
|
||||||
|
"idNameRequired": "Workflow ID and name are required",
|
||||||
|
"selectToDelete": "Select a workflow to delete",
|
||||||
|
"confirmDelete": "Delete workflow {{id}}?",
|
||||||
|
"duplicateEdge": "An edge already exists between these two nodes",
|
||||||
|
"connectModeOn": "Connect mode: click source node then target node",
|
||||||
|
"connectModeOff": "Exited connect mode",
|
||||||
|
"validation": {
|
||||||
|
"needStart": "At least one Start node is required",
|
||||||
|
"needOutput": "At least one Output node is required",
|
||||||
|
"edgeSelfLoop": "Edge {{id}} cannot point to itself",
|
||||||
|
"edgeSourceMissing": "Edge {{id}} source node does not exist",
|
||||||
|
"edgeTargetMissing": "Edge {{id}} target node does not exist",
|
||||||
|
"startIncoming": "Start node {{label}} must not have incoming edges",
|
||||||
|
"outputOutgoing": "Output node {{label}} must not have outgoing edges",
|
||||||
|
"toolNeedsMcp": "Tool node {{label}} requires an MCP tool",
|
||||||
|
"conditionNeedsExpr": "Condition node {{label}} requires a condition expression",
|
||||||
|
"conditionNeedsOutEdge": "Condition node {{label}} needs at least one outgoing edge (Yes/No branch)",
|
||||||
|
"conditionTooManyEdges": "Condition node {{label}} should have at most two outgoing edges (Yes/No); configure edge conditions for a third and beyond",
|
||||||
|
"outputNeedsKey": "Output node {{label}} requires an output variable name"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"c2": {
|
"c2": {
|
||||||
"clipboardCopied": "Copied to clipboard",
|
"clipboardCopied": "Copied to clipboard",
|
||||||
|
|||||||
+106
-1
@@ -85,6 +85,7 @@
|
|||||||
"agentsManagement": "Agent管理",
|
"agentsManagement": "Agent管理",
|
||||||
"roles": "角色",
|
"roles": "角色",
|
||||||
"rolesManagement": "角色管理",
|
"rolesManagement": "角色管理",
|
||||||
|
"workflows": "图编排",
|
||||||
"settings": "系统设置",
|
"settings": "系统设置",
|
||||||
"hitl": "人机协同",
|
"hitl": "人机协同",
|
||||||
"c2": "C2",
|
"c2": "C2",
|
||||||
@@ -2903,7 +2904,111 @@
|
|||||||
"mcpDisabledBadgeTitle": "MCP 管理里该工具为关闭;勾选只表示想关联到本角色,实际调用需先在 MCP 中打开",
|
"mcpDisabledBadgeTitle": "MCP 管理里该工具为关闭;勾选只表示想关联到本角色,实际调用需先在 MCP 中打开",
|
||||||
"roleFilterOnBanner": "以下为「已勾选、关联到本角色」的工具(与 MCP 管理里全局开/关无关)。",
|
"roleFilterOnBanner": "以下为「已勾选、关联到本角色」的工具(与 MCP 管理里全局开/关无关)。",
|
||||||
"roleFilterOffBanner": "以下为「未勾选、未关联到本角色」的工具。",
|
"roleFilterOffBanner": "以下为「未勾选、未关联到本角色」的工具。",
|
||||||
"checkboxLinkTitle": "勾选表示本角色关联使用该工具"
|
"checkboxLinkTitle": "勾选表示本角色关联使用该工具",
|
||||||
|
"bindWorkflow": "绑定图编排流程",
|
||||||
|
"bindWorkflowHint": "选中流程后,对话页使用该角色会自动触发绑定图;流程字段由图定义 JSON 自由配置。",
|
||||||
|
"workflowPolicy": "流程触发策略",
|
||||||
|
"workflowPolicyAuto": "自动触发",
|
||||||
|
"workflowPolicyOff": "关闭",
|
||||||
|
"noWorkflowBind": "不绑定流程",
|
||||||
|
"workflowDisabledSuffix": "(已禁用)"
|
||||||
|
},
|
||||||
|
"workflows": {
|
||||||
|
"title": "图编排",
|
||||||
|
"newGraph": "新建图",
|
||||||
|
"processLibrary": "流程库",
|
||||||
|
"nodeLibrary": "节点库",
|
||||||
|
"emptyList": "暂无图编排流程",
|
||||||
|
"statusEnabled": "启用",
|
||||||
|
"statusDisabled": "禁用",
|
||||||
|
"metaId": "ID",
|
||||||
|
"metaName": "名称",
|
||||||
|
"metaDescription": "描述",
|
||||||
|
"metaEnabled": "启用",
|
||||||
|
"namePlaceholder": "基础 Web 扫描",
|
||||||
|
"descriptionPlaceholder": "可选",
|
||||||
|
"connect": "连线",
|
||||||
|
"connecting": "连线中",
|
||||||
|
"deleteSelected": "删除选中",
|
||||||
|
"autoLayout": "自动布局",
|
||||||
|
"canvasEmpty": "从左侧拖拽节点到画布,或点击节点按钮快速添加",
|
||||||
|
"properties": "属性",
|
||||||
|
"nodeProperties": "节点属性",
|
||||||
|
"edgeProperties": "连线属性",
|
||||||
|
"deleteNode": "删除节点",
|
||||||
|
"deleteEdge": "删除连线",
|
||||||
|
"propertyEmpty": "选择一个节点或连线后编辑属性",
|
||||||
|
"propLabel": "名称",
|
||||||
|
"propType": "类型",
|
||||||
|
"customFields": "自定义字段",
|
||||||
|
"addField": "添加字段",
|
||||||
|
"noCustomFields": "暂无自定义字段",
|
||||||
|
"nodes": {
|
||||||
|
"start": "开始",
|
||||||
|
"tool": "工具",
|
||||||
|
"agent": "Agent",
|
||||||
|
"condition": "条件",
|
||||||
|
"hitl": "审批",
|
||||||
|
"output": "输出",
|
||||||
|
"end": "结束",
|
||||||
|
"default": "节点"
|
||||||
|
},
|
||||||
|
"edges": {
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"inputKeys": "输入变量",
|
||||||
|
"mcpTool": "MCP 工具",
|
||||||
|
"selectTool": "请选择工具",
|
||||||
|
"toolDisabled": "(未启用)",
|
||||||
|
"argumentsTemplate": "参数模板",
|
||||||
|
"timeoutSeconds": "超时秒数",
|
||||||
|
"optional": "可选",
|
||||||
|
"agentMode": "Agent 模式",
|
||||||
|
"inputSource": "输入来源",
|
||||||
|
"nodeInstruction": "节点指令",
|
||||||
|
"instructionPlaceholder": "描述该节点要完成的任务",
|
||||||
|
"outputKey": "输出变量名",
|
||||||
|
"conditionExpression": "条件表达式",
|
||||||
|
"conditionHint": "节点会计算 matched(true/false),由出边决定分支:第一条线为「是」,第二条为「否」;也可在连线上写 <code>{{previous.matched}} == \"true\"</code>。",
|
||||||
|
"edgeCondition": "连线条件",
|
||||||
|
"edgeConditionHintCondition": "{{previous.matched}} == \"true\"(是)或 == \"false\"(否)",
|
||||||
|
"edgeConditionHintExample": "例如: {{previous.output}} == \"ok\"",
|
||||||
|
"edgeBranchHint": "从条件节点连出的第一条线默认为「是」分支,第二条为「否」分支;也可在此自定义条件。",
|
||||||
|
"hitlPrompt": "审批提示",
|
||||||
|
"hitlPromptPlaceholder": "请审批是否继续",
|
||||||
|
"hitlReviewer": "审批方",
|
||||||
|
"outputSource": "变量来源",
|
||||||
|
"endTemplate": "结束摘要模板"
|
||||||
|
},
|
||||||
|
"defaultHitlPrompt": "请审批该步骤是否继续执行",
|
||||||
|
"nodeFallback": "节点 {{n}}",
|
||||||
|
"loadFailed": "加载工作流失败",
|
||||||
|
"saveFailed": "保存工作流失败",
|
||||||
|
"deleteFailed": "删除工作流失败",
|
||||||
|
"saved": "工作流已保存",
|
||||||
|
"deleted": "工作流已删除",
|
||||||
|
"idNameRequired": "工作流 ID 和名称不能为空",
|
||||||
|
"selectToDelete": "请选择要删除的工作流",
|
||||||
|
"confirmDelete": "确定删除工作流 {{id}}?",
|
||||||
|
"duplicateEdge": "这两个节点之间已经有连线",
|
||||||
|
"connectModeOn": "连线模式:依次点击源节点和目标节点",
|
||||||
|
"connectModeOff": "已退出连线模式",
|
||||||
|
"validation": {
|
||||||
|
"needStart": "至少需要一个开始节点",
|
||||||
|
"needOutput": "至少需要一个输出节点",
|
||||||
|
"edgeSelfLoop": "连线 {{id}} 不能指向自身",
|
||||||
|
"edgeSourceMissing": "连线 {{id}} 的源节点不存在",
|
||||||
|
"edgeTargetMissing": "连线 {{id}} 的目标节点不存在",
|
||||||
|
"startIncoming": "开始节点 {{label}} 不应有入边",
|
||||||
|
"outputOutgoing": "输出节点 {{label}} 不应有出边",
|
||||||
|
"toolNeedsMcp": "工具节点 {{label}} 需要选择 MCP 工具",
|
||||||
|
"conditionNeedsExpr": "条件节点 {{label}} 需要条件表达式",
|
||||||
|
"conditionNeedsOutEdge": "条件节点 {{label}} 至少需要一条出边(是/否分支)",
|
||||||
|
"conditionTooManyEdges": "条件节点 {{label}} 建议最多两条出边(是/否);第三条及以后需配置连线条件",
|
||||||
|
"outputNeedsKey": "输出节点 {{label}} 需要输出变量名"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"c2": {
|
"c2": {
|
||||||
"clipboardCopied": "已复制到剪贴板",
|
"clipboardCopied": "已复制到剪贴板",
|
||||||
|
|||||||
+133
-67
@@ -1,6 +1,18 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function _t(key, opts) {
|
||||||
|
if (typeof window.t === 'function') {
|
||||||
|
try {
|
||||||
|
var translated = window.t(key, opts);
|
||||||
|
if (typeof translated === 'string' && translated && translated !== key) {
|
||||||
|
return translated;
|
||||||
|
}
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
let workflows = [];
|
let workflows = [];
|
||||||
let currentWorkflowId = '';
|
let currentWorkflowId = '';
|
||||||
let cy = null;
|
let cy = null;
|
||||||
@@ -12,15 +24,24 @@
|
|||||||
let workflowToolOptions = [];
|
let workflowToolOptions = [];
|
||||||
let workflowToolsLoaded = false;
|
let workflowToolsLoaded = false;
|
||||||
|
|
||||||
const NODE_LABELS = {
|
const KNOWN_NODE_LABELS = {
|
||||||
start: '开始',
|
start: ['开始', 'Start'],
|
||||||
tool: '工具',
|
tool: ['工具', 'Tool'],
|
||||||
agent: 'Agent',
|
agent: ['Agent'],
|
||||||
condition: '条件',
|
condition: ['条件', 'Condition'],
|
||||||
hitl: '审批',
|
hitl: ['审批', 'Approval'],
|
||||||
output: '输出',
|
output: ['输出', 'Output'],
|
||||||
end: '结束'
|
end: ['结束', 'End']
|
||||||
};
|
};
|
||||||
|
const KNOWN_EDGE_LABELS = {
|
||||||
|
yes: ['是', 'Yes'],
|
||||||
|
no: ['否', 'No']
|
||||||
|
};
|
||||||
|
|
||||||
|
function wfNodeLabel(type) {
|
||||||
|
const key = type && KNOWN_NODE_LABELS[type] ? 'workflows.nodes.' + type : 'workflows.nodes.default';
|
||||||
|
return _t(key);
|
||||||
|
}
|
||||||
|
|
||||||
const AGENT_MODES = ['eino_single', 'deep', 'plan_execute', 'supervisor'];
|
const AGENT_MODES = ['eino_single', 'deep', 'plan_execute', 'supervisor'];
|
||||||
|
|
||||||
@@ -49,7 +70,7 @@
|
|||||||
case 'condition':
|
case 'condition':
|
||||||
return { expression: '{{previous.output}} != ""' };
|
return { expression: '{{previous.output}} != ""' };
|
||||||
case 'hitl':
|
case 'hitl':
|
||||||
return { prompt: '请审批该步骤是否继续执行', reviewer: 'human' };
|
return { prompt: _t('workflows.defaultHitlPrompt'), reviewer: 'human' };
|
||||||
case 'output':
|
case 'output':
|
||||||
return { output_key: 'result', source: '{{previous.output}}' };
|
return { output_key: 'result', source: '{{previous.output}}' };
|
||||||
case 'end':
|
case 'end':
|
||||||
@@ -85,7 +106,7 @@
|
|||||||
group: 'nodes',
|
group: 'nodes',
|
||||||
data: {
|
data: {
|
||||||
id: node.id || `node-${index + 1}`,
|
id: node.id || `node-${index + 1}`,
|
||||||
label: node.label || NODE_LABELS[node.type] || node.id || `节点 ${index + 1}`,
|
label: node.label || wfNodeLabel(node.type) || node.id || _t('workflows.nodeFallback', { n: index + 1 }),
|
||||||
type: node.type || 'tool',
|
type: node.type || 'tool',
|
||||||
config: configWithDefaults(node.type || 'tool', node.config)
|
config: configWithDefaults(node.type || 'tool', node.config)
|
||||||
},
|
},
|
||||||
@@ -237,7 +258,7 @@
|
|||||||
const response = await apiFetch(`/api/workflows?includeDisabled=${includeDisabled ? 'true' : 'false'}`);
|
const response = await apiFetch(`/api/workflows?includeDisabled=${includeDisabled ? 'true' : 'false'}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const err = await response.json().catch(() => ({}));
|
const err = await response.json().catch(() => ({}));
|
||||||
throw new Error(err.error || '加载工作流失败');
|
throw new Error(err.error || _t('workflows.loadFailed'));
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
workflows = data.workflows || [];
|
workflows = data.workflows || [];
|
||||||
@@ -273,13 +294,13 @@
|
|||||||
const list = document.getElementById('workflow-list');
|
const list = document.getElementById('workflow-list');
|
||||||
if (!list) return;
|
if (!list) return;
|
||||||
if (!workflows.length) {
|
if (!workflows.length) {
|
||||||
list.innerHTML = '<div class="empty-state">暂无图编排流程</div>';
|
list.innerHTML = '<div class="empty-state">' + esc(_t('workflows.emptyList')) + '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = workflows.map(wf => `
|
list.innerHTML = workflows.map(wf => `
|
||||||
<button type="button" class="workflow-list-item ${wf.id === currentWorkflowId ? 'is-active' : ''}" onclick="selectWorkflow(decodeURIComponent('${encodeURIComponent(wf.id)}'))">
|
<button type="button" class="workflow-list-item ${wf.id === currentWorkflowId ? 'is-active' : ''}" onclick="selectWorkflow(decodeURIComponent('${encodeURIComponent(wf.id)}'))">
|
||||||
<span class="workflow-list-title">${esc(wf.name || wf.id)}</span>
|
<span class="workflow-list-title">${esc(wf.name || wf.id)}</span>
|
||||||
<span class="workflow-list-meta">${esc(wf.id)} · v${wf.version || 1} · ${wf.enabled ? '启用' : '禁用'}</span>
|
<span class="workflow-list-meta">${esc(wf.id)} · v${wf.version || 1} · ${wf.enabled ? esc(_t('workflows.statusEnabled')) : esc(_t('workflows.statusDisabled'))}</span>
|
||||||
</button>
|
</button>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
@@ -347,7 +368,7 @@
|
|||||||
if (!selectedElement) {
|
if (!selectedElement) {
|
||||||
empty.hidden = false;
|
empty.hidden = false;
|
||||||
form.hidden = true;
|
form.hidden = true;
|
||||||
if (title) title.textContent = '属性';
|
if (title) title.textContent = _t('workflows.properties');
|
||||||
if (deleteBtn) deleteBtn.hidden = true;
|
if (deleteBtn) deleteBtn.hidden = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -355,10 +376,10 @@
|
|||||||
selectedElement.select();
|
selectedElement.select();
|
||||||
empty.hidden = true;
|
empty.hidden = true;
|
||||||
form.hidden = false;
|
form.hidden = false;
|
||||||
if (title) title.textContent = selectedElement.isNode() ? '节点属性' : '连线属性';
|
if (title) title.textContent = selectedElement.isNode() ? _t('workflows.nodeProperties') : _t('workflows.edgeProperties');
|
||||||
if (deleteBtn) {
|
if (deleteBtn) {
|
||||||
deleteBtn.hidden = false;
|
deleteBtn.hidden = false;
|
||||||
deleteBtn.textContent = selectedElement.isNode() ? '删除节点' : '删除连线';
|
deleteBtn.textContent = selectedElement.isNode() ? _t('workflows.deleteNode') : _t('workflows.deleteEdge');
|
||||||
}
|
}
|
||||||
const typeWrap = document.getElementById('workflow-prop-type-wrap');
|
const typeWrap = document.getElementById('workflow-prop-type-wrap');
|
||||||
const label = document.getElementById('workflow-prop-label');
|
const label = document.getElementById('workflow-prop-label');
|
||||||
@@ -410,30 +431,30 @@
|
|||||||
if (!ele.isNode()) {
|
if (!ele.isNode()) {
|
||||||
const sourceType = ele.source().data('type') || '';
|
const sourceType = ele.source().data('type') || '';
|
||||||
const edgeHint = sourceType === 'condition'
|
const edgeHint = sourceType === 'condition'
|
||||||
? '{{previous.matched}} == "true"(是)或 == "false"(否)'
|
? _t('workflows.config.edgeConditionHintCondition')
|
||||||
: '例如: {{previous.output}} == "ok"';
|
: _t('workflows.config.edgeConditionHintExample');
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
${typedField('workflow-edge-condition', '连线条件', cfg.condition || '', edgeHint)}
|
${typedField('workflow-edge-condition', _t('workflows.config.edgeCondition'), cfg.condition || '', edgeHint)}
|
||||||
${sourceType === 'condition' ? '<p class="workflow-config-hint">从条件节点连出的第一条线默认为「是」分支,第二条为「否」分支;也可在此自定义条件。</p>' : ''}
|
${sourceType === 'condition' ? '<p class="workflow-config-hint">' + esc(_t('workflows.config.edgeBranchHint')) + '</p>' : ''}
|
||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const type = ele.data('type') || 'tool';
|
const type = ele.data('type') || 'tool';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
wrap.innerHTML = typedField('workflow-start-input-keys', '输入变量', cfg.input_keys, 'message, projectId');
|
wrap.innerHTML = typedField('workflow-start-input-keys', _t('workflows.config.inputKeys'), cfg.input_keys, 'message, projectId');
|
||||||
break;
|
break;
|
||||||
case 'tool':
|
case 'tool':
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="workflow-tool-name">MCP 工具</label>
|
<label for="workflow-tool-name">${esc(_t('workflows.config.mcpTool'))}</label>
|
||||||
<select id="workflow-tool-name" onchange="updateWorkflowTypedConfig()">
|
<select id="workflow-tool-name" onchange="updateWorkflowTypedConfig()">
|
||||||
<option value="">请选择工具</option>
|
<option value="">${esc(_t('workflows.config.selectTool'))}</option>
|
||||||
${workflowToolOptions.map(tool => `<option value="${esc(tool.key)}" ${tool.key === cfg.tool_name ? 'selected' : ''}>${esc(tool.key)}${tool.enabled ? '' : '(未启用)'}</option>`).join('')}
|
${workflowToolOptions.map(tool => `<option value="${esc(tool.key)}" ${tool.key === cfg.tool_name ? 'selected' : ''}>${esc(tool.key)}${tool.enabled ? '' : esc(_t('workflows.config.toolDisabled'))}</option>`).join('')}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
${typedTextarea('workflow-tool-arguments', '参数模板', cfg.arguments, '{"target":"{{inputs.target}}"}')}
|
${typedTextarea('workflow-tool-arguments', _t('workflows.config.argumentsTemplate'), cfg.arguments, '{"target":"{{inputs.target}}"}')}
|
||||||
${typedField('workflow-tool-timeout', '超时秒数', cfg.timeout_seconds, '可选')}
|
${typedField('workflow-tool-timeout', _t('workflows.config.timeoutSeconds'), cfg.timeout_seconds, _t('workflows.config.optional'))}
|
||||||
`;
|
`;
|
||||||
if (!workflowToolsLoaded) {
|
if (!workflowToolsLoaded) {
|
||||||
loadWorkflowTools().then(() => {
|
loadWorkflowTools().then(() => {
|
||||||
@@ -444,27 +465,27 @@
|
|||||||
case 'agent':
|
case 'agent':
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="workflow-agent-mode">Agent 模式</label>
|
<label for="workflow-agent-mode">${esc(_t('workflows.config.agentMode'))}</label>
|
||||||
<select id="workflow-agent-mode" onchange="updateWorkflowTypedConfig()">
|
<select id="workflow-agent-mode" onchange="updateWorkflowTypedConfig()">
|
||||||
${AGENT_MODES.map(mode => `<option value="${mode}" ${mode === cfg.agent_mode ? 'selected' : ''}>${mode}</option>`).join('')}
|
${AGENT_MODES.map(mode => `<option value="${mode}" ${mode === cfg.agent_mode ? 'selected' : ''}>${mode}</option>`).join('')}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
${typedField('workflow-agent-input-source', '输入来源', cfg.input_source, '{{previous.output}}')}
|
${typedField('workflow-agent-input-source', _t('workflows.config.inputSource'), cfg.input_source, '{{previous.output}}')}
|
||||||
${typedTextarea('workflow-agent-instruction', '节点指令', cfg.instruction, '描述该节点要完成的任务')}
|
${typedTextarea('workflow-agent-instruction', _t('workflows.config.nodeInstruction'), cfg.instruction, _t('workflows.config.instructionPlaceholder'))}
|
||||||
${typedField('workflow-agent-output-key', '输出变量名', cfg.output_key, 'agent_result')}
|
${typedField('workflow-agent-output-key', _t('workflows.config.outputKey'), cfg.output_key, 'agent_result')}
|
||||||
`;
|
`;
|
||||||
break;
|
break;
|
||||||
case 'condition':
|
case 'condition':
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
${typedField('workflow-condition-expression', '条件表达式', cfg.expression, '{{previous.output}} != ""')}
|
${typedField('workflow-condition-expression', _t('workflows.config.conditionExpression'), cfg.expression, '{{previous.output}} != ""')}
|
||||||
<p class="workflow-config-hint">节点会计算 matched(true/false),由出边决定分支:第一条线为「是」,第二条为「否」;也可在连线上写 <code>{{previous.matched}} == "true"</code>。</p>
|
<p class="workflow-config-hint">${_t('workflows.config.conditionHint')}</p>
|
||||||
`;
|
`;
|
||||||
break;
|
break;
|
||||||
case 'hitl':
|
case 'hitl':
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
${typedTextarea('workflow-hitl-prompt', '审批提示', cfg.prompt, '请审批是否继续')}
|
${typedTextarea('workflow-hitl-prompt', _t('workflows.config.hitlPrompt'), cfg.prompt, _t('workflows.config.hitlPromptPlaceholder'))}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="workflow-hitl-reviewer">审批方</label>
|
<label for="workflow-hitl-reviewer">${esc(_t('workflows.config.hitlReviewer'))}</label>
|
||||||
<select id="workflow-hitl-reviewer" onchange="updateWorkflowTypedConfig()">
|
<select id="workflow-hitl-reviewer" onchange="updateWorkflowTypedConfig()">
|
||||||
<option value="human" ${cfg.reviewer === 'human' ? 'selected' : ''}>human</option>
|
<option value="human" ${cfg.reviewer === 'human' ? 'selected' : ''}>human</option>
|
||||||
<option value="audit_agent" ${cfg.reviewer === 'audit_agent' ? 'selected' : ''}>audit_agent</option>
|
<option value="audit_agent" ${cfg.reviewer === 'audit_agent' ? 'selected' : ''}>audit_agent</option>
|
||||||
@@ -474,12 +495,12 @@
|
|||||||
break;
|
break;
|
||||||
case 'output':
|
case 'output':
|
||||||
wrap.innerHTML = `
|
wrap.innerHTML = `
|
||||||
${typedField('workflow-output-key', '输出变量名', cfg.output_key, 'result')}
|
${typedField('workflow-output-key', _t('workflows.config.outputKey'), cfg.output_key, 'result')}
|
||||||
${typedField('workflow-output-source', '变量来源', cfg.source, '{{previous.output}}')}
|
${typedField('workflow-output-source', _t('workflows.config.outputSource'), cfg.source, '{{previous.output}}')}
|
||||||
`;
|
`;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
wrap.innerHTML = typedTextarea('workflow-end-template', '结束摘要模板', cfg.result_template, '{{outputs.result}}');
|
wrap.innerHTML = typedTextarea('workflow-end-template', _t('workflows.config.endTemplate'), cfg.result_template, '{{outputs.result}}');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
wrap.innerHTML = '';
|
wrap.innerHTML = '';
|
||||||
@@ -491,7 +512,7 @@
|
|||||||
if (!wrap) return;
|
if (!wrap) return;
|
||||||
const entries = Object.entries(config || {});
|
const entries = Object.entries(config || {});
|
||||||
if (!entries.length) {
|
if (!entries.length) {
|
||||||
wrap.innerHTML = '<div class="workflow-property-empty workflow-property-empty--compact">暂无自定义字段</div>';
|
wrap.innerHTML = '<div class="workflow-property-empty workflow-property-empty--compact">' + esc(_t('workflows.noCustomFields')) + '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
wrap.innerHTML = entries.map(([key, value], index) => `
|
wrap.innerHTML = entries.map(([key, value], index) => `
|
||||||
@@ -572,7 +593,7 @@
|
|||||||
const duplicate = cy.edges().some(edge => edge.source().id() === connectSourceId && edge.target().id() === node.id());
|
const duplicate = cy.edges().some(edge => edge.source().id() === connectSourceId && edge.target().id() === node.id());
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
if (typeof showNotification === 'function') {
|
if (typeof showNotification === 'function') {
|
||||||
showNotification('这两个节点之间已经有连线', 'warning');
|
showNotification(_t('workflows.duplicateEdge'), 'warning');
|
||||||
}
|
}
|
||||||
clearConnectSource();
|
clearConnectSource();
|
||||||
return;
|
return;
|
||||||
@@ -584,10 +605,10 @@
|
|||||||
if (sourceType === 'condition') {
|
if (sourceType === 'condition') {
|
||||||
const siblingCount = cy.edges().filter(edge => edge.source().id() === connectSourceId).length;
|
const siblingCount = cy.edges().filter(edge => edge.source().id() === connectSourceId).length;
|
||||||
if (siblingCount === 0) {
|
if (siblingCount === 0) {
|
||||||
edgeLabel = '是';
|
edgeLabel = _t('workflows.edges.yes');
|
||||||
edgeConfig = { condition: '{{previous.matched}} == "true"', branch: 'true' };
|
edgeConfig = { condition: '{{previous.matched}} == "true"', branch: 'true' };
|
||||||
} else if (siblingCount === 1) {
|
} else if (siblingCount === 1) {
|
||||||
edgeLabel = '否';
|
edgeLabel = _t('workflows.edges.no');
|
||||||
edgeConfig = { condition: '{{previous.matched}} == "false"', branch: 'false' };
|
edgeConfig = { condition: '{{previous.matched}} == "false"', branch: 'false' };
|
||||||
} else {
|
} else {
|
||||||
edgeConfig = { condition: '' };
|
edgeConfig = { condition: '' };
|
||||||
@@ -619,7 +640,7 @@
|
|||||||
data: {
|
data: {
|
||||||
id: nextNodeId(type),
|
id: nextNodeId(type),
|
||||||
type,
|
type,
|
||||||
label: NODE_LABELS[type] || '节点',
|
label: wfNodeLabel(type),
|
||||||
config: defaultConfigForType(type)
|
config: defaultConfigForType(type)
|
||||||
},
|
},
|
||||||
position: position || { x: 180 + cy.nodes().length * 28, y: 160 + cy.nodes().length * 28 }
|
position: position || { x: 180 + cy.nodes().length * 28, y: 160 + cy.nodes().length * 28 }
|
||||||
@@ -631,7 +652,7 @@
|
|||||||
window.refreshWorkflows = async function () {
|
window.refreshWorkflows = async function () {
|
||||||
initCy();
|
initCy();
|
||||||
const list = document.getElementById('workflow-list');
|
const list = document.getElementById('workflow-list');
|
||||||
if (list) list.innerHTML = '<div class="loading-spinner">加载中...</div>';
|
if (list) list.innerHTML = '<div class="loading-spinner">' + esc(_t('common.loading')) + '</div>';
|
||||||
try {
|
try {
|
||||||
await loadWorkflows(true);
|
await loadWorkflows(true);
|
||||||
renderWorkflowList();
|
renderWorkflowList();
|
||||||
@@ -668,38 +689,38 @@
|
|||||||
const ids = new Set(nodes.map(node => node.id));
|
const ids = new Set(nodes.map(node => node.id));
|
||||||
const starts = nodes.filter(node => node.type === 'start');
|
const starts = nodes.filter(node => node.type === 'start');
|
||||||
const outputs = nodes.filter(node => node.type === 'output');
|
const outputs = nodes.filter(node => node.type === 'output');
|
||||||
if (!starts.length) errors.push('至少需要一个开始节点');
|
if (!starts.length) errors.push(_t('workflows.validation.needStart'));
|
||||||
if (!outputs.length) errors.push('至少需要一个输出节点');
|
if (!outputs.length) errors.push(_t('workflows.validation.needOutput'));
|
||||||
edges.forEach(edge => {
|
edges.forEach(edge => {
|
||||||
if (edge.source === edge.target) errors.push(`连线 ${edge.id} 不能指向自身`);
|
if (edge.source === edge.target) errors.push(_t('workflows.validation.edgeSelfLoop', { id: edge.id }));
|
||||||
if (!ids.has(edge.source)) errors.push(`连线 ${edge.id} 的源节点不存在`);
|
if (!ids.has(edge.source)) errors.push(_t('workflows.validation.edgeSourceMissing', { id: edge.id }));
|
||||||
if (!ids.has(edge.target)) errors.push(`连线 ${edge.id} 的目标节点不存在`);
|
if (!ids.has(edge.target)) errors.push(_t('workflows.validation.edgeTargetMissing', { id: edge.id }));
|
||||||
});
|
});
|
||||||
starts.forEach(node => {
|
starts.forEach(node => {
|
||||||
if (edges.some(edge => edge.target === node.id)) errors.push(`开始节点 ${node.label || node.id} 不应有入边`);
|
if (edges.some(edge => edge.target === node.id)) errors.push(_t('workflows.validation.startIncoming', { label: node.label || node.id }));
|
||||||
});
|
});
|
||||||
outputs.forEach(node => {
|
outputs.forEach(node => {
|
||||||
if (edges.some(edge => edge.source === node.id)) errors.push(`输出节点 ${node.label || node.id} 不应有出边`);
|
if (edges.some(edge => edge.source === node.id)) errors.push(_t('workflows.validation.outputOutgoing', { label: node.label || node.id }));
|
||||||
});
|
});
|
||||||
nodes.filter(node => node.type === 'tool').forEach(node => {
|
nodes.filter(node => node.type === 'tool').forEach(node => {
|
||||||
if (!String((node.config || {}).tool_name || '').trim()) {
|
if (!String((node.config || {}).tool_name || '').trim()) {
|
||||||
errors.push(`工具节点 ${node.label || node.id} 需要选择 MCP 工具`);
|
errors.push(_t('workflows.validation.toolNeedsMcp', { label: node.label || node.id }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nodes.filter(node => node.type === 'condition').forEach(node => {
|
nodes.filter(node => node.type === 'condition').forEach(node => {
|
||||||
if (!String((node.config || {}).expression || '').trim()) {
|
if (!String((node.config || {}).expression || '').trim()) {
|
||||||
errors.push(`条件节点 ${node.label || node.id} 需要条件表达式`);
|
errors.push(_t('workflows.validation.conditionNeedsExpr', { label: node.label || node.id }));
|
||||||
}
|
}
|
||||||
const outEdges = edges.filter(edge => edge.source === node.id);
|
const outEdges = edges.filter(edge => edge.source === node.id);
|
||||||
if (outEdges.length === 0) {
|
if (outEdges.length === 0) {
|
||||||
errors.push(`条件节点 ${node.label || node.id} 至少需要一条出边(是/否分支)`);
|
errors.push(_t('workflows.validation.conditionNeedsOutEdge', { label: node.label || node.id }));
|
||||||
} else if (outEdges.length > 2) {
|
} else if (outEdges.length > 2) {
|
||||||
errors.push(`条件节点 ${node.label || node.id} 建议最多两条出边(是/否);第三条及以后需配置连线条件`);
|
errors.push(_t('workflows.validation.conditionTooManyEdges', { label: node.label || node.id }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nodes.filter(node => node.type === 'output').forEach(node => {
|
nodes.filter(node => node.type === 'output').forEach(node => {
|
||||||
if (!String((node.config || {}).output_key || '').trim()) {
|
if (!String((node.config || {}).output_key || '').trim()) {
|
||||||
errors.push(`输出节点 ${node.label || node.id} 需要输出变量名`);
|
errors.push(_t('workflows.validation.outputNeedsKey', { label: node.label || node.id }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return errors;
|
return errors;
|
||||||
@@ -712,7 +733,7 @@
|
|||||||
const description = document.getElementById('workflow-description').value.trim();
|
const description = document.getElementById('workflow-description').value.trim();
|
||||||
const enabled = document.getElementById('workflow-enabled').checked;
|
const enabled = document.getElementById('workflow-enabled').checked;
|
||||||
if (!id || !name) {
|
if (!id || !name) {
|
||||||
showNotification('工作流 ID 和名称不能为空', 'error');
|
showNotification(_t('workflows.idNameRequired'), 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const graph = elementsToGraph();
|
const graph = elementsToGraph();
|
||||||
@@ -730,12 +751,12 @@
|
|||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const err = await response.json().catch(() => ({}));
|
const err = await response.json().catch(() => ({}));
|
||||||
showNotification(err.error || '保存工作流失败', 'error');
|
showNotification(err.error || _t('workflows.saveFailed'), 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
currentWorkflowId = data.workflow && data.workflow.id ? data.workflow.id : id;
|
currentWorkflowId = data.workflow && data.workflow.id ? data.workflow.id : id;
|
||||||
showNotification('工作流已保存', 'success');
|
showNotification(_t('workflows.saved'), 'success');
|
||||||
await refreshWorkflows();
|
await refreshWorkflows();
|
||||||
if (typeof loadWorkflowOptionsForRoleModal === 'function') {
|
if (typeof loadWorkflowOptionsForRoleModal === 'function') {
|
||||||
await loadWorkflowOptionsForRoleModal();
|
await loadWorkflowOptionsForRoleModal();
|
||||||
@@ -745,18 +766,18 @@
|
|||||||
window.deleteCurrentWorkflow = async function () {
|
window.deleteCurrentWorkflow = async function () {
|
||||||
const id = currentWorkflowId || document.getElementById('workflow-id').value.trim();
|
const id = currentWorkflowId || document.getElementById('workflow-id').value.trim();
|
||||||
if (!id) {
|
if (!id) {
|
||||||
showNotification('请选择要删除的工作流', 'warning');
|
showNotification(_t('workflows.selectToDelete'), 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!confirm(`确定删除工作流 ${id}?`)) return;
|
if (!confirm(_t('workflows.confirmDelete', { id: id }))) return;
|
||||||
const response = await apiFetch(`/api/workflows/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
const response = await apiFetch(`/api/workflows/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const err = await response.json().catch(() => ({}));
|
const err = await response.json().catch(() => ({}));
|
||||||
showNotification(err.error || '删除工作流失败', 'error');
|
showNotification(err.error || _t('workflows.deleteFailed'), 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentWorkflowId = '';
|
currentWorkflowId = '';
|
||||||
showNotification('工作流已删除', 'success');
|
showNotification(_t('workflows.deleted'), 'success');
|
||||||
newWorkflowDraft();
|
newWorkflowDraft();
|
||||||
await refreshWorkflows();
|
await refreshWorkflows();
|
||||||
};
|
};
|
||||||
@@ -795,10 +816,10 @@
|
|||||||
const btn = document.getElementById('workflow-connect-btn');
|
const btn = document.getElementById('workflow-connect-btn');
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.classList.toggle('active', connectMode);
|
btn.classList.toggle('active', connectMode);
|
||||||
btn.textContent = connectMode ? '连线中' : '连线';
|
btn.textContent = connectMode ? _t('workflows.connecting') : _t('workflows.connect');
|
||||||
}
|
}
|
||||||
if (typeof showNotification === 'function') {
|
if (typeof showNotification === 'function') {
|
||||||
showNotification(connectMode ? '连线模式:依次点击源节点和目标节点' : '已退出连线模式', 'info');
|
showNotification(connectMode ? _t('workflows.connectModeOn') : _t('workflows.connectModeOff'), 'info');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -834,7 +855,7 @@
|
|||||||
selectedElement.data('type', type);
|
selectedElement.data('type', type);
|
||||||
if (type !== prevType) {
|
if (type !== prevType) {
|
||||||
selectedElement.data('config', defaultConfigForType(type));
|
selectedElement.data('config', defaultConfigForType(type));
|
||||||
selectedElement.data('label', label || NODE_LABELS[type] || '节点');
|
selectedElement.data('label', label || wfNodeLabel(type));
|
||||||
document.getElementById('workflow-prop-label').value = selectedElement.data('label') || '';
|
document.getElementById('workflow-prop-label').value = selectedElement.data('label') || '';
|
||||||
renderTypedConfig(selectedElement);
|
renderTypedConfig(selectedElement);
|
||||||
renderCustomFields({});
|
renderCustomFields({});
|
||||||
@@ -883,9 +904,54 @@
|
|||||||
const select = document.getElementById('role-workflow-id');
|
const select = document.getElementById('role-workflow-id');
|
||||||
if (!select) return;
|
if (!select) return;
|
||||||
const current = selectedId !== undefined ? selectedId : select.value;
|
const current = selectedId !== undefined ? selectedId : select.value;
|
||||||
select.innerHTML = '<option value="">不绑定流程</option>' + workflows.map(wf => (
|
select.innerHTML = '<option value="">' + esc(_t('roleModal.noWorkflowBind')) + '</option>' + workflows.map(wf => (
|
||||||
`<option value="${esc(wf.id)}">${esc(wf.name || wf.id)}${wf.enabled ? '' : '(已禁用)'}</option>`
|
`<option value="${esc(wf.id)}">${esc(wf.name || wf.id)}${wf.enabled ? '' : esc(_t('roleModal.workflowDisabledSuffix'))}</option>`
|
||||||
)).join('');
|
)).join('');
|
||||||
select.value = current || '';
|
select.value = current || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function refreshCanvasLabels() {
|
||||||
|
if (!cy) return;
|
||||||
|
cy.nodes().forEach(function (node) {
|
||||||
|
const type = node.data('type') || 'tool';
|
||||||
|
const label = node.data('label') || '';
|
||||||
|
const known = KNOWN_NODE_LABELS[type] || [];
|
||||||
|
if (known.indexOf(label) !== -1) {
|
||||||
|
node.data('label', wfNodeLabel(type));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cy.edges().forEach(function (edge) {
|
||||||
|
const label = edge.data('label') || '';
|
||||||
|
if (KNOWN_EDGE_LABELS.yes.indexOf(label) !== -1) {
|
||||||
|
edge.data('label', _t('workflows.edges.yes'));
|
||||||
|
} else if (KNOWN_EDGE_LABELS.no.indexOf(label) !== -1) {
|
||||||
|
edge.data('label', _t('workflows.edges.no'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshWorkflowsI18n() {
|
||||||
|
const page = document.getElementById('page-workflows');
|
||||||
|
if (page && typeof window.applyTranslations === 'function') {
|
||||||
|
window.applyTranslations(page);
|
||||||
|
}
|
||||||
|
const connectBtn = document.getElementById('workflow-connect-btn');
|
||||||
|
if (connectBtn) {
|
||||||
|
connectBtn.textContent = connectMode ? _t('workflows.connecting') : _t('workflows.connect');
|
||||||
|
}
|
||||||
|
refreshCanvasLabels();
|
||||||
|
renderWorkflowList();
|
||||||
|
if (selectedElement && selectedElement.length) {
|
||||||
|
selectWorkflowElement(selectedElement);
|
||||||
|
} else {
|
||||||
|
selectWorkflowElement(null);
|
||||||
|
}
|
||||||
|
if (typeof loadWorkflowOptionsForRoleModal === 'function') {
|
||||||
|
loadWorkflowOptionsForRoleModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('languagechange', function () {
|
||||||
|
refreshWorkflowsI18n();
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
+45
-45
@@ -202,13 +202,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-page="workflows">
|
<div class="nav-item" data-page="workflows">
|
||||||
<div class="nav-item-content" data-title="图编排" onclick="switchPage('workflows')">
|
<div class="nav-item-content" data-title="图编排" onclick="switchPage('workflows')" data-i18n="nav.workflows" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<rect x="3" y="3" width="8" height="8" rx="2"/>
|
<rect x="3" y="3" width="8" height="8" rx="2"/>
|
||||||
<path d="M7 11v4a2 2 0 0 0 2 2h4"/>
|
<path d="M7 11v4a2 2 0 0 0 2 2h4"/>
|
||||||
<rect x="13" y="13" width="8" height="8" rx="2"/>
|
<rect x="13" y="13" width="8" height="8" rx="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>图编排</span>
|
<span data-i18n="nav.workflows">图编排</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-page="projects">
|
<div class="nav-item" data-page="projects">
|
||||||
@@ -2550,85 +2550,85 @@
|
|||||||
<!-- 图编排页面 -->
|
<!-- 图编排页面 -->
|
||||||
<div id="page-workflows" class="page">
|
<div id="page-workflows" class="page">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>图编排</h2>
|
<h2 data-i18n="workflows.title">图编排</h2>
|
||||||
<div class="page-header-actions">
|
<div class="page-header-actions">
|
||||||
<button class="btn-secondary" onclick="refreshWorkflows()">刷新</button>
|
<button class="btn-secondary" onclick="refreshWorkflows()" data-i18n="common.refresh">刷新</button>
|
||||||
<button class="btn-primary" onclick="newWorkflowDraft()">新建图</button>
|
<button class="btn-primary" onclick="newWorkflowDraft()" data-i18n="workflows.newGraph">新建图</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="page-content workflow-page-content">
|
<div class="page-content workflow-page-content">
|
||||||
<aside class="workflow-sidebar">
|
<aside class="workflow-sidebar">
|
||||||
<div class="workflow-panel">
|
<div class="workflow-panel">
|
||||||
<div class="workflow-panel-header">
|
<div class="workflow-panel-header">
|
||||||
<h3>流程库</h3>
|
<h3 data-i18n="workflows.processLibrary">流程库</h3>
|
||||||
</div>
|
</div>
|
||||||
<div id="workflow-list" class="workflow-list">
|
<div id="workflow-list" class="workflow-list">
|
||||||
<div class="loading-spinner">加载中...</div>
|
<div class="loading-spinner" data-i18n="common.loading">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workflow-panel">
|
<div class="workflow-panel">
|
||||||
<div class="workflow-panel-header">
|
<div class="workflow-panel-header">
|
||||||
<h3>节点库</h3>
|
<h3 data-i18n="workflows.nodeLibrary">节点库</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="workflow-node-palette">
|
<div class="workflow-node-palette">
|
||||||
<button type="button" draggable="true" data-node-type="start" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('start')">开始</button>
|
<button type="button" draggable="true" data-node-type="start" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('start')" data-i18n="workflows.nodes.start">开始</button>
|
||||||
<button type="button" draggable="true" data-node-type="tool" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('tool')">工具</button>
|
<button type="button" draggable="true" data-node-type="tool" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('tool')" data-i18n="workflows.nodes.tool">工具</button>
|
||||||
<button type="button" draggable="true" data-node-type="agent" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('agent')">Agent</button>
|
<button type="button" draggable="true" data-node-type="agent" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('agent')" data-i18n="workflows.nodes.agent">Agent</button>
|
||||||
<button type="button" draggable="true" data-node-type="condition" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('condition')">条件</button>
|
<button type="button" draggable="true" data-node-type="condition" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('condition')" data-i18n="workflows.nodes.condition">条件</button>
|
||||||
<button type="button" draggable="true" data-node-type="hitl" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('hitl')">审批</button>
|
<button type="button" draggable="true" data-node-type="hitl" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('hitl')" data-i18n="workflows.nodes.hitl">审批</button>
|
||||||
<button type="button" draggable="true" data-node-type="output" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('output')">输出</button>
|
<button type="button" draggable="true" data-node-type="output" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('output')" data-i18n="workflows.nodes.output">输出</button>
|
||||||
<button type="button" draggable="true" data-node-type="end" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('end')">结束</button>
|
<button type="button" draggable="true" data-node-type="end" ondragstart="workflowPaletteDragStart(event)" onclick="addWorkflowNodeFromPalette('end')" data-i18n="workflows.nodes.end">结束</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<main class="workflow-main">
|
<main class="workflow-main">
|
||||||
<section class="workflow-meta-bar">
|
<section class="workflow-meta-bar">
|
||||||
<div class="workflow-meta-fields">
|
<div class="workflow-meta-fields">
|
||||||
<label>ID <input type="text" id="workflow-id" placeholder="web-scan-basic" autocomplete="off"></label>
|
<label><span data-i18n="workflows.metaId">ID</span> <input type="text" id="workflow-id" placeholder="web-scan-basic" autocomplete="off"></label>
|
||||||
<label>名称 <input type="text" id="workflow-name" placeholder="基础 Web 扫描" autocomplete="off"></label>
|
<label><span data-i18n="workflows.metaName">名称</span> <input type="text" id="workflow-name" data-i18n="workflows.namePlaceholder" data-i18n-attr="placeholder" placeholder="基础 Web 扫描" autocomplete="off"></label>
|
||||||
<label>描述 <input type="text" id="workflow-description" placeholder="可选" autocomplete="off"></label>
|
<label><span data-i18n="workflows.metaDescription">描述</span> <input type="text" id="workflow-description" data-i18n="workflows.descriptionPlaceholder" data-i18n-attr="placeholder" placeholder="可选" autocomplete="off"></label>
|
||||||
<label class="workflow-enabled-toggle"><input type="checkbox" id="workflow-enabled" checked> 启用</label>
|
<label class="workflow-enabled-toggle"><input type="checkbox" id="workflow-enabled" checked> <span data-i18n="workflows.metaEnabled">启用</span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="workflow-toolbar">
|
<div class="workflow-toolbar">
|
||||||
<button class="btn-secondary btn-small" type="button" onclick="toggleWorkflowConnectMode()" id="workflow-connect-btn">连线</button>
|
<button class="btn-secondary btn-small" type="button" onclick="toggleWorkflowConnectMode()" id="workflow-connect-btn" data-i18n="workflows.connect">连线</button>
|
||||||
<button class="btn-secondary btn-small" type="button" onclick="deleteWorkflowSelection()">删除选中</button>
|
<button class="btn-secondary btn-small" type="button" onclick="deleteWorkflowSelection()" data-i18n="workflows.deleteSelected">删除选中</button>
|
||||||
<button class="btn-secondary btn-small" type="button" onclick="layoutWorkflowGraph()">自动布局</button>
|
<button class="btn-secondary btn-small" type="button" onclick="layoutWorkflowGraph()" data-i18n="workflows.autoLayout">自动布局</button>
|
||||||
<button class="btn-secondary btn-small" onclick="deleteCurrentWorkflow()">删除</button>
|
<button class="btn-secondary btn-small" onclick="deleteCurrentWorkflow()" data-i18n="common.delete">删除</button>
|
||||||
<button class="btn-primary btn-small" onclick="saveWorkflowDraft()">保存</button>
|
<button class="btn-primary btn-small" onclick="saveWorkflowDraft()" data-i18n="common.save">保存</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="workflow-canvas-wrap" ondragover="workflowCanvasDragOver(event)" ondrop="workflowCanvasDrop(event)">
|
<section class="workflow-canvas-wrap" ondragover="workflowCanvasDragOver(event)" ondrop="workflowCanvasDrop(event)">
|
||||||
<div id="workflow-canvas"></div>
|
<div id="workflow-canvas"></div>
|
||||||
<div id="workflow-canvas-empty" class="workflow-canvas-empty">从左侧拖拽节点到画布,或点击节点按钮快速添加</div>
|
<div id="workflow-canvas-empty" class="workflow-canvas-empty" data-i18n="workflows.canvasEmpty">从左侧拖拽节点到画布,或点击节点按钮快速添加</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<aside class="workflow-properties">
|
<aside class="workflow-properties">
|
||||||
<div class="workflow-panel-header">
|
<div class="workflow-panel-header">
|
||||||
<h3 id="workflow-property-title">属性</h3>
|
<h3 id="workflow-property-title" data-i18n="workflows.properties">属性</h3>
|
||||||
<button type="button" id="workflow-property-delete-btn" class="btn-secondary btn-small" onclick="deleteWorkflowSelection()" hidden>删除</button>
|
<button type="button" id="workflow-property-delete-btn" class="btn-secondary btn-small" onclick="deleteWorkflowSelection()" hidden data-i18n="common.delete">删除</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="workflow-property-empty" class="workflow-property-empty">选择一个节点或连线后编辑属性</div>
|
<div id="workflow-property-empty" class="workflow-property-empty" data-i18n="workflows.propertyEmpty">选择一个节点或连线后编辑属性</div>
|
||||||
<div id="workflow-property-form" class="workflow-property-form" hidden>
|
<div id="workflow-property-form" class="workflow-property-form" hidden>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="workflow-prop-label">名称</label>
|
<label for="workflow-prop-label" data-i18n="workflows.propLabel">名称</label>
|
||||||
<input type="text" id="workflow-prop-label" class="form-input" oninput="updateWorkflowSelectedProperty()">
|
<input type="text" id="workflow-prop-label" class="form-input" oninput="updateWorkflowSelectedProperty()">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" id="workflow-prop-type-wrap">
|
<div class="form-group" id="workflow-prop-type-wrap">
|
||||||
<label for="workflow-prop-type">类型</label>
|
<label for="workflow-prop-type" data-i18n="workflows.propType">类型</label>
|
||||||
<select id="workflow-prop-type" onchange="updateWorkflowSelectedProperty()">
|
<select id="workflow-prop-type" onchange="updateWorkflowSelectedProperty()">
|
||||||
<option value="start">开始</option>
|
<option value="start" data-i18n="workflows.nodes.start">开始</option>
|
||||||
<option value="tool">工具</option>
|
<option value="tool" data-i18n="workflows.nodes.tool">工具</option>
|
||||||
<option value="agent">Agent</option>
|
<option value="agent" data-i18n="workflows.nodes.agent">Agent</option>
|
||||||
<option value="condition">条件</option>
|
<option value="condition" data-i18n="workflows.nodes.condition">条件</option>
|
||||||
<option value="hitl">审批</option>
|
<option value="hitl" data-i18n="workflows.nodes.hitl">审批</option>
|
||||||
<option value="output">输出</option>
|
<option value="output" data-i18n="workflows.nodes.output">输出</option>
|
||||||
<option value="end">结束</option>
|
<option value="end" data-i18n="workflows.nodes.end">结束</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="workflow-typed-config" class="workflow-typed-config"></div>
|
<div id="workflow-typed-config" class="workflow-typed-config"></div>
|
||||||
<div class="workflow-custom-fields-head">
|
<div class="workflow-custom-fields-head">
|
||||||
<span>自定义字段</span>
|
<span data-i18n="workflows.customFields">自定义字段</span>
|
||||||
<button type="button" class="btn-secondary btn-small" onclick="addWorkflowCustomField()">添加字段</button>
|
<button type="button" class="btn-secondary btn-small" onclick="addWorkflowCustomField()" data-i18n="workflows.addField">添加字段</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="workflow-custom-fields" class="workflow-custom-fields"></div>
|
<div id="workflow-custom-fields" class="workflow-custom-fields"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4661,17 +4661,17 @@
|
|||||||
<small class="form-hint" data-i18n="roleModal.userPromptHint">此提示词会追加到用户消息前,用于指导AI的行为。注意:这不会修改系统提示词。</small>
|
<small class="form-hint" data-i18n="roleModal.userPromptHint">此提示词会追加到用户消息前,用于指导AI的行为。注意:这不会修改系统提示词。</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="role-workflow-id">绑定图编排流程</label>
|
<label for="role-workflow-id" data-i18n="roleModal.bindWorkflow">绑定图编排流程</label>
|
||||||
<select id="role-workflow-id">
|
<select id="role-workflow-id">
|
||||||
<option value="">不绑定流程</option>
|
<option value="" data-i18n="roleModal.noWorkflowBind">不绑定流程</option>
|
||||||
</select>
|
</select>
|
||||||
<small class="form-hint">选中流程后,对话页使用该角色会自动触发绑定图;流程字段由图定义 JSON 自由配置。</small>
|
<small class="form-hint" data-i18n="roleModal.bindWorkflowHint">选中流程后,对话页使用该角色会自动触发绑定图;流程字段由图定义 JSON 自由配置。</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="role-workflow-policy">流程触发策略</label>
|
<label for="role-workflow-policy" data-i18n="roleModal.workflowPolicy">流程触发策略</label>
|
||||||
<select id="role-workflow-policy">
|
<select id="role-workflow-policy">
|
||||||
<option value="auto">自动触发</option>
|
<option value="auto" data-i18n="roleModal.workflowPolicyAuto">自动触发</option>
|
||||||
<option value="off">关闭</option>
|
<option value="off" data-i18n="roleModal.workflowPolicyOff">关闭</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" id="role-tools-section">
|
<div class="form-group" id="role-tools-section">
|
||||||
|
|||||||
Reference in new issue
Block a user