mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-04 03:27:54 +02:00
Add files via upload
This commit is contained in:
@@ -2975,10 +2975,13 @@
|
||||
"selectTool": "Select a tool",
|
||||
"toolDisabled": " (disabled)",
|
||||
"argumentsTemplate": "Arguments template",
|
||||
"argumentsStatic": "Tool arguments (JSON)",
|
||||
"timeoutSeconds": "Timeout (seconds)",
|
||||
"optional": "Optional",
|
||||
"agentMode": "Agent mode",
|
||||
"inputSource": "Input source",
|
||||
"inputBinding": "Input field binding",
|
||||
"inputBindingHint": "from = data source, field = field name (e.g. output, message)",
|
||||
"nodeInstruction": "Node instruction",
|
||||
"instructionPlaceholder": "Describe what this node should accomplish",
|
||||
"outputKey": "Output variable name",
|
||||
@@ -2991,7 +2994,15 @@
|
||||
"hitlPrompt": "Approval prompt",
|
||||
"hitlPromptPlaceholder": "Approve to continue",
|
||||
"hitlReviewer": "Reviewer",
|
||||
"hitlInteractiveHint": "The run pauses at this node; approve or reject via API or the monitor panel to continue.",
|
||||
"promptBinding": "Prompt field binding",
|
||||
"promptBindingHint": "When prompt text is empty, read approval text from the bound field",
|
||||
"outputSource": "Variable source",
|
||||
"sourceBinding": "Output field binding",
|
||||
"sourceBindingHint": "Write the bound field value to the output variable; static value below overrides when set",
|
||||
"staticValue": "Static output value",
|
||||
"resultBinding": "End summary binding",
|
||||
"resultBindingHint": "Field shown in the end node summary",
|
||||
"endTemplate": "End summary template"
|
||||
},
|
||||
"defaultHitlPrompt": "Please approve whether this step should continue",
|
||||
|
||||
@@ -2963,10 +2963,13 @@
|
||||
"selectTool": "请选择工具",
|
||||
"toolDisabled": "(未启用)",
|
||||
"argumentsTemplate": "参数模板",
|
||||
"argumentsStatic": "工具参数(JSON)",
|
||||
"timeoutSeconds": "超时秒数",
|
||||
"optional": "可选",
|
||||
"agentMode": "Agent 模式",
|
||||
"inputSource": "输入来源",
|
||||
"inputBinding": "输入字段绑定",
|
||||
"inputBindingHint": "from 选数据来源,field 为字段名(如 output、message)",
|
||||
"nodeInstruction": "节点指令",
|
||||
"instructionPlaceholder": "描述该节点要完成的任务",
|
||||
"outputKey": "输出变量名",
|
||||
@@ -2979,7 +2982,15 @@
|
||||
"hitlPrompt": "审批提示",
|
||||
"hitlPromptPlaceholder": "请审批是否继续",
|
||||
"hitlReviewer": "审批方",
|
||||
"hitlInteractiveHint": "运行时在审批节点暂停,需通过 API 或监控面板人工通过/拒绝后继续。",
|
||||
"promptBinding": "提示字段绑定",
|
||||
"promptBindingHint": "留空提示文案时,从绑定字段读取审批说明",
|
||||
"outputSource": "变量来源",
|
||||
"sourceBinding": "输出字段绑定",
|
||||
"sourceBindingHint": "将绑定字段的值写入输出变量;也可填写下方固定值覆盖",
|
||||
"staticValue": "固定输出值",
|
||||
"resultBinding": "结束摘要绑定",
|
||||
"resultBindingHint": "结束节点展示的摘要字段",
|
||||
"endTemplate": "结束摘要模板"
|
||||
},
|
||||
"defaultHitlPrompt": "请审批该步骤是否继续执行",
|
||||
|
||||
@@ -2572,6 +2572,10 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
itemTitle = '🤖 Agent 输出' + (label ? (' · ' + label) : '');
|
||||
} else if (eventType === 'workflow_hitl_checkpoint') {
|
||||
itemTitle = '🧑⚖️ 人工确认检查点';
|
||||
} else if (eventType === 'workflow_hitl_waiting') {
|
||||
itemTitle = '🧑⚖️ 工作流等待审批';
|
||||
} else if (eventType === 'workflow_paused') {
|
||||
itemTitle = '⏸️ 工作流已暂停';
|
||||
} else if (eventType === 'iteration') {
|
||||
const n = data.iteration || 1;
|
||||
if (data.orchestration === 'plan_execute' && data.einoScope === 'main') {
|
||||
|
||||
@@ -1910,6 +1910,26 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
break;
|
||||
}
|
||||
|
||||
case 'workflow_hitl_waiting': {
|
||||
const d = event.data || {};
|
||||
const hitlItemId = addTimelineItem(timeline, 'workflow_hitl_waiting', {
|
||||
title: '🧑⚖️ 工作流等待审批',
|
||||
message: event.message || '',
|
||||
data: d
|
||||
});
|
||||
renderInlineWorkflowHitlApproval(hitlItemId, d);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'workflow_paused': {
|
||||
addTimelineItem(timeline, 'workflow_paused', {
|
||||
title: '⏸️ 工作流已暂停',
|
||||
message: event.message || '',
|
||||
data: event.data || {}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'eino_trace_run':
|
||||
case 'eino_trace_start':
|
||||
case 'eino_trace_end':
|
||||
@@ -2834,6 +2854,75 @@ function renderInlineHitlApproval(itemId, data) {
|
||||
rejectBtn.onclick = function () { submit('reject'); };
|
||||
}
|
||||
|
||||
function renderInlineWorkflowHitlApproval(itemId, data) {
|
||||
const item = document.getElementById(itemId);
|
||||
if (!item || !data) return;
|
||||
const runId = data.workflowRunId || data.workflow_run_id;
|
||||
if (!runId) return;
|
||||
let contentEl = item.querySelector('.timeline-item-content');
|
||||
if (!contentEl) {
|
||||
contentEl = document.createElement('div');
|
||||
contentEl.className = 'timeline-item-content';
|
||||
item.appendChild(contentEl);
|
||||
}
|
||||
const existingPanel = contentEl.querySelector('.workflow-hitl-inline-approval');
|
||||
if (existingPanel) existingPanel.remove();
|
||||
|
||||
const label = data.label || data.nodeId || runId;
|
||||
const prompt = data.prompt || '';
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'workflow-hitl-inline-approval hitl-inline-approval';
|
||||
panel.innerHTML = `
|
||||
<div class="hitl-input-help"><strong>${escapeHtml(label)}</strong> 等待人工审批。</div>
|
||||
${prompt ? `<div class="hitl-input-help">${escapeHtml(prompt)}</div>` : ''}
|
||||
<div class="hitl-input-help">备注(可选)</div>
|
||||
<input class="hitl-config-input workflow-hitl-inline-comment" type="text" placeholder="审批意见">
|
||||
<div class="hitl-pending-actions">
|
||||
<button class="btn-secondary workflow-hitl-inline-reject">拒绝</button>
|
||||
<button class="btn-primary workflow-hitl-inline-approve">通过</button>
|
||||
</div>
|
||||
<div class="hitl-input-help workflow-hitl-inline-status"></div>
|
||||
`;
|
||||
contentEl.appendChild(panel);
|
||||
|
||||
const approveBtn = panel.querySelector('.workflow-hitl-inline-approve');
|
||||
const rejectBtn = panel.querySelector('.workflow-hitl-inline-reject');
|
||||
const commentInput = panel.querySelector('.workflow-hitl-inline-comment');
|
||||
const statusEl = panel.querySelector('.workflow-hitl-inline-status');
|
||||
|
||||
const setBusy = function (busy) {
|
||||
approveBtn.disabled = busy;
|
||||
rejectBtn.disabled = busy;
|
||||
};
|
||||
|
||||
const submit = async function (approved) {
|
||||
setBusy(true);
|
||||
const comment = String(commentInput.value || '').trim();
|
||||
try {
|
||||
const fetchFn = typeof apiFetch === 'function' ? apiFetch : fetch;
|
||||
const response = await fetchFn(`/api/workflows/runs/${encodeURIComponent(runId)}/resume`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ approved: approved, comment: comment })
|
||||
});
|
||||
const body = response && typeof response.json === 'function' ? await response.json() : null;
|
||||
if (!response || !response.ok) {
|
||||
statusEl.textContent = (body && body.error) ? body.error : '提交失败,请重试';
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = approved ? '已通过,工作流继续执行' : '已拒绝';
|
||||
panel.classList.add('hitl-inline-done');
|
||||
} catch (e) {
|
||||
statusEl.textContent = '提交失败:' + (e && e.message ? e.message : 'unknown error');
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
approveBtn.onclick = function () { submit(true); };
|
||||
rejectBtn.onclick = function () { submit(false); };
|
||||
}
|
||||
|
||||
function hitlEscapeAttrSelector(val) {
|
||||
const s = String(val);
|
||||
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
|
||||
|
||||
+55
-11
@@ -55,6 +55,45 @@
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
const BINDING_FROM_OPTIONS = ['previous', 'inputs', 'outputs'];
|
||||
|
||||
function bindingFromConfig(cfg, key, fallbackFrom, fallbackField) {
|
||||
const b = cfg && cfg[key];
|
||||
if (b && typeof b === 'object') {
|
||||
return {
|
||||
from: b.from || fallbackFrom,
|
||||
field: b.field || fallbackField
|
||||
};
|
||||
}
|
||||
return { from: fallbackFrom, field: fallbackField };
|
||||
}
|
||||
|
||||
function bindingFieldHtml(prefix, labelKey, binding, hintKey) {
|
||||
const from = binding.from || 'previous';
|
||||
const field = binding.field || 'output';
|
||||
const label = _t(labelKey);
|
||||
const hint = hintKey ? _t(hintKey) : '';
|
||||
const options = BINDING_FROM_OPTIONS.map(v =>
|
||||
`<option value="${esc(v)}" ${v === from ? 'selected' : ''}>${esc(v)}</option>`
|
||||
).join('');
|
||||
return `
|
||||
<div class="form-group">
|
||||
<label>${esc(label)}</label>
|
||||
<div class="workflow-binding-row" style="display:flex;gap:8px;">
|
||||
<select id="${prefix}-from" onchange="updateWorkflowTypedConfig()" style="flex:1;">${options}</select>
|
||||
<input type="text" id="${prefix}-field" value="${esc(field)}" placeholder="output" oninput="updateWorkflowTypedConfig()" style="flex:1;">
|
||||
</div>
|
||||
${hint ? '<p class="workflow-config-hint">' + hint + '</p>' : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function readBinding(prefix) {
|
||||
return {
|
||||
from: (document.getElementById(prefix + '-from') || {}).value || 'previous',
|
||||
field: (document.getElementById(prefix + '-field') || {}).value || 'output'
|
||||
};
|
||||
}
|
||||
|
||||
function defaultGraph() {
|
||||
return { nodes: [], edges: [], config: {} };
|
||||
}
|
||||
@@ -66,15 +105,15 @@
|
||||
case 'tool':
|
||||
return { tool_name: '', arguments: '{}', timeout_seconds: '' };
|
||||
case 'agent':
|
||||
return { agent_mode: 'eino_single', input_source: '{{previous.output}}', instruction: '', output_key: 'agent_result' };
|
||||
return { agent_mode: 'eino_single', input_binding: { from: 'previous', field: 'output' }, instruction: '', output_key: 'agent_result' };
|
||||
case 'condition':
|
||||
return { expression: '{{previous.output}} != ""' };
|
||||
case 'hitl':
|
||||
return { prompt: _t('workflows.defaultHitlPrompt'), reviewer: 'human' };
|
||||
return { prompt: _t('workflows.defaultHitlPrompt'), prompt_binding: { from: 'previous', field: 'output' }, reviewer: 'human' };
|
||||
case 'output':
|
||||
return { output_key: 'result', source: '{{previous.output}}' };
|
||||
return { output_key: 'result', source_binding: { from: 'previous', field: 'output' } };
|
||||
case 'end':
|
||||
return { result_template: '{{outputs.result}}' };
|
||||
return { result_binding: { from: 'outputs', field: 'result' } };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@@ -453,7 +492,7 @@
|
||||
${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>
|
||||
</div>
|
||||
${typedTextarea('workflow-tool-arguments', _t('workflows.config.argumentsTemplate'), cfg.arguments, '{"target":"{{inputs.target}}"}')}
|
||||
${typedTextarea('workflow-tool-arguments', _t('workflows.config.argumentsStatic'), cfg.arguments, '{"target":"example.com"}')}
|
||||
${typedField('workflow-tool-timeout', _t('workflows.config.timeoutSeconds'), cfg.timeout_seconds, _t('workflows.config.optional'))}
|
||||
`;
|
||||
if (!workflowToolsLoaded) {
|
||||
@@ -470,7 +509,7 @@
|
||||
${AGENT_MODES.map(mode => `<option value="${mode}" ${mode === cfg.agent_mode ? 'selected' : ''}>${mode}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
${typedField('workflow-agent-input-source', _t('workflows.config.inputSource'), cfg.input_source, '{{previous.output}}')}
|
||||
${bindingFieldHtml('workflow-agent-input', 'workflows.config.inputBinding', bindingFromConfig(cfg, 'input_binding', 'previous', 'output'), 'workflows.config.inputBindingHint')}
|
||||
${typedTextarea('workflow-agent-instruction', _t('workflows.config.nodeInstruction'), cfg.instruction, _t('workflows.config.instructionPlaceholder'))}
|
||||
${typedField('workflow-agent-output-key', _t('workflows.config.outputKey'), cfg.output_key, 'agent_result')}
|
||||
`;
|
||||
@@ -484,6 +523,8 @@
|
||||
case 'hitl':
|
||||
wrap.innerHTML = `
|
||||
${typedTextarea('workflow-hitl-prompt', _t('workflows.config.hitlPrompt'), cfg.prompt, _t('workflows.config.hitlPromptPlaceholder'))}
|
||||
${bindingFieldHtml('workflow-hitl-prompt-binding', 'workflows.config.promptBinding', bindingFromConfig(cfg, 'prompt_binding', 'previous', 'output'), 'workflows.config.promptBindingHint')}
|
||||
<p class="workflow-config-hint">${_t('workflows.config.hitlInteractiveHint')}</p>
|
||||
<div class="form-group">
|
||||
<label for="workflow-hitl-reviewer">${esc(_t('workflows.config.hitlReviewer'))}</label>
|
||||
<select id="workflow-hitl-reviewer" onchange="updateWorkflowTypedConfig()">
|
||||
@@ -496,11 +537,12 @@
|
||||
case 'output':
|
||||
wrap.innerHTML = `
|
||||
${typedField('workflow-output-key', _t('workflows.config.outputKey'), cfg.output_key, 'result')}
|
||||
${typedField('workflow-output-source', _t('workflows.config.outputSource'), cfg.source, '{{previous.output}}')}
|
||||
${bindingFieldHtml('workflow-output-source', 'workflows.config.sourceBinding', bindingFromConfig(cfg, 'source_binding', 'previous', 'output'), 'workflows.config.sourceBindingHint')}
|
||||
${typedField('workflow-output-static', _t('workflows.config.staticValue'), cfg.static_value || '', _t('workflows.config.optional'))}
|
||||
`;
|
||||
break;
|
||||
case 'end':
|
||||
wrap.innerHTML = typedTextarea('workflow-end-template', _t('workflows.config.endTemplate'), cfg.result_template, '{{outputs.result}}');
|
||||
wrap.innerHTML = bindingFieldHtml('workflow-end-result', 'workflows.config.resultBinding', bindingFromConfig(cfg, 'result_binding', 'outputs', 'result'), 'workflows.config.resultBindingHint');
|
||||
break;
|
||||
default:
|
||||
wrap.innerHTML = '';
|
||||
@@ -552,7 +594,7 @@
|
||||
case 'agent':
|
||||
return {
|
||||
agent_mode: (document.getElementById('workflow-agent-mode') || {}).value || 'eino_single',
|
||||
input_source: (document.getElementById('workflow-agent-input-source') || {}).value || '{{previous.output}}',
|
||||
input_binding: readBinding('workflow-agent-input'),
|
||||
instruction: (document.getElementById('workflow-agent-instruction') || {}).value || '',
|
||||
output_key: (document.getElementById('workflow-agent-output-key') || {}).value || 'agent_result'
|
||||
};
|
||||
@@ -561,15 +603,17 @@
|
||||
case 'hitl':
|
||||
return {
|
||||
prompt: (document.getElementById('workflow-hitl-prompt') || {}).value || '',
|
||||
prompt_binding: readBinding('workflow-hitl-prompt-binding'),
|
||||
reviewer: (document.getElementById('workflow-hitl-reviewer') || {}).value || 'human'
|
||||
};
|
||||
case 'output':
|
||||
return {
|
||||
output_key: (document.getElementById('workflow-output-key') || {}).value || 'result',
|
||||
source: (document.getElementById('workflow-output-source') || {}).value || ''
|
||||
source_binding: readBinding('workflow-output-source'),
|
||||
static_value: (document.getElementById('workflow-output-static') || {}).value || ''
|
||||
};
|
||||
case 'end':
|
||||
return { result_template: (document.getElementById('workflow-end-template') || {}).value || '' };
|
||||
return { result_binding: readBinding('workflow-end-result') };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user