mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-18 08:57:18 +02:00
Add files via upload
This commit is contained in:
@@ -2707,16 +2707,23 @@ function finishProcessDetailsRender(messageElement, processDetails, isLazyNotLoa
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasPendingHitlInDetails = processDetails.some(d => d && d.eventType === 'hitl_interrupt');
|
const hasPendingHitlInDetails = processDetails.some(d => d && d.eventType === 'hitl_interrupt');
|
||||||
|
const hasPendingWorkflowHitl = processDetails.some(d => d && d.eventType === 'workflow_hitl_waiting');
|
||||||
const hasErrorOrCancelled = processDetails.some(d =>
|
const hasErrorOrCancelled = processDetails.some(d =>
|
||||||
d.eventType === 'error' || d.eventType === 'cancelled'
|
d.eventType === 'error' || d.eventType === 'cancelled'
|
||||||
);
|
);
|
||||||
if (hasErrorOrCancelled && !hasPendingHitlInDetails) {
|
if (hasErrorOrCancelled && !hasPendingHitlInDetails && !hasPendingWorkflowHitl) {
|
||||||
timeline.classList.remove('expanded');
|
timeline.classList.remove('expanded');
|
||||||
const processDetailBtn = messageElement.querySelector('.process-detail-btn');
|
const processDetailBtn = messageElement.querySelector('.process-detail-btn');
|
||||||
if (processDetailBtn) {
|
if (processDetailBtn) {
|
||||||
processDetailBtn.innerHTML = '<span>' + (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') + '</span>';
|
processDetailBtn.innerHTML = '<span>' + (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') + '</span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (hasPendingWorkflowHitl && messageElement && messageElement.id) {
|
||||||
|
const convId = typeof window.currentConversationId === 'string' ? window.currentConversationId : '';
|
||||||
|
if (convId && typeof window.restoreWorkflowHitlInlineForConversation === 'function') {
|
||||||
|
window.restoreWorkflowHitlInlineForConversation(convId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 懒加载折叠态:后台拉摘要,提示迭代规模而不加载全量详情 */
|
/** 懒加载折叠态:后台拉摘要,提示迭代规模而不加载全量详情 */
|
||||||
|
|||||||
+113
-10
@@ -678,14 +678,9 @@ async function followAgentRunAfterHitlDecision(conversationId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderHitlPendingList(items) {
|
function renderHitlPendingList(items) {
|
||||||
const container = document.getElementById('hitl-pending-list');
|
|
||||||
if (!container) return;
|
|
||||||
const list = Array.isArray(items) ? items : [];
|
const list = Array.isArray(items) ? items : [];
|
||||||
if (!list.length) {
|
if (!list.length) return '';
|
||||||
container.innerHTML = '<div class="empty-state">' + escapeHtml(hitlT('emptyState', 'No pending approvals')) + '</div>';
|
return list.map(function (item) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
container.innerHTML = list.map(function (item) {
|
|
||||||
const payloadObj = hitlParsePayloadObject(item.payload || '');
|
const payloadObj = hitlParsePayloadObject(item.payload || '');
|
||||||
const payload = String(item.payload || '');
|
const payload = String(item.payload || '');
|
||||||
const contextHtml = hitlRenderContextBlocks(payloadObj);
|
const contextHtml = hitlRenderContextBlocks(payloadObj);
|
||||||
@@ -722,6 +717,86 @@ function renderHitlPendingList(items) {
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hitlWorkflowPendingLabel(run) {
|
||||||
|
const pending = hitlParsePayloadObject(run.pending_hitl_json || run.pendingHitlJson || '');
|
||||||
|
const pendingHitl = pending.pendingHitl && typeof pending.pendingHitl === 'object' ? pending.pendingHitl : pending;
|
||||||
|
return pendingHitl.label || pendingHitl.nodeId || run.pending_hitl_node_id || run.pendingHitlNodeId || run.workflow_id || run.workflowId || run.id || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWorkflowHitlPendingList(runs) {
|
||||||
|
const list = Array.isArray(runs) ? runs : [];
|
||||||
|
if (!list.length) return '';
|
||||||
|
return list.map(function (run) {
|
||||||
|
const runId = String(run.id || '').trim();
|
||||||
|
const pending = hitlParsePayloadObject(run.pending_hitl_json || run.pendingHitlJson || '');
|
||||||
|
const pendingHitl = pending.pendingHitl && typeof pending.pendingHitl === 'object' ? pending.pendingHitl : pending;
|
||||||
|
const label = hitlWorkflowPendingLabel(run);
|
||||||
|
const prompt = String(pendingHitl.prompt || '').trim();
|
||||||
|
const convId = String(run.conversation_id || run.conversationId || '').trim();
|
||||||
|
const qRun = JSON.stringify(runId).replace(/"/g, '"');
|
||||||
|
const qConv = JSON.stringify(convId).replace(/"/g, '"');
|
||||||
|
const workflowLabel = hitlT('workflowPendingTitle', 'Workflow approval');
|
||||||
|
const openChatLabel = hitlT('openConversation', 'Open conversation');
|
||||||
|
return (
|
||||||
|
'<div class="hitl-pending-item hitl-pending-item--workflow">' +
|
||||||
|
'<div class="hitl-pending-item-header">' +
|
||||||
|
'<div class="hitl-pending-item-title">' +
|
||||||
|
'<span class="hitl-tool-badge">' + escapeHtml(workflowLabel) + '</span>' +
|
||||||
|
'<span class="hitl-mode-tag hitl-mode-tag--approval">' + escapeHtml(label) + '</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="hitl-pending-meta">' + escapeHtml(hitlT('conversationLabel', 'Conversation:')) + ' ' + escapeHtml(convId || '-') + '</div>' +
|
||||||
|
(prompt ? ('<div class="hitl-input-help">' + escapeHtml(prompt) + '</div>') : '') +
|
||||||
|
'<div class="hitl-input-help">' + escapeHtml(hitlT('commentHelp', 'Comment (optional): briefly note the approval reason.')) + '</div>' +
|
||||||
|
'<input id="workflow-hitl-comment-' + escapeHtml(runId) + '" class="hitl-config-input hitl-inline-comment" type="text" placeholder="' + escapeHtml(hitlT('commentPlaceholder', 'e.g. allow read-only command')) + '">' +
|
||||||
|
'<div class="hitl-pending-actions">' +
|
||||||
|
(convId ? ('<button class="btn-secondary" onclick="openHitlConversation(' + qConv + ')">' + escapeHtml(openChatLabel) + '</button>') : '') +
|
||||||
|
'<button class="btn-secondary" onclick="submitWorkflowHitlDecisionFromPage(' + qRun + ', false, ' + qConv + ')">' + escapeHtml(hitlT('reject', 'Reject')) + '</button>' +
|
||||||
|
'<button class="btn-primary" onclick="submitWorkflowHitlDecisionFromPage(' + qRun + ', true, ' + qConv + ')">' + escapeHtml(hitlT('approve', 'Approve')) + '</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitWorkflowHitlDecisionFromPage(runId, approved, conversationId) {
|
||||||
|
const rid = String(runId || '').trim();
|
||||||
|
if (!rid) return;
|
||||||
|
const commentEl = document.getElementById('workflow-hitl-comment-' + rid);
|
||||||
|
const comment = commentEl ? String(commentEl.value || '').trim() : '';
|
||||||
|
try {
|
||||||
|
if (typeof window.submitWorkflowHitlDecision === 'function') {
|
||||||
|
await window.submitWorkflowHitlDecision(rid, approved, comment);
|
||||||
|
} else {
|
||||||
|
const resp = await hitlApiFetch('/api/workflows/runs/' + encodeURIComponent(rid) + '/resume', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify({ approved: !!approved, comment: comment })
|
||||||
|
});
|
||||||
|
const body = await resp.json().catch(function () { return {}; });
|
||||||
|
if (!resp.ok) throw new Error((body && body.error) ? body.error : 'submit failed');
|
||||||
|
}
|
||||||
|
if (conversationId && typeof followAgentRunAfterHitlDecision === 'function') {
|
||||||
|
await followAgentRunAfterHitlDecision(conversationId);
|
||||||
|
}
|
||||||
|
await refreshHitlPending();
|
||||||
|
} catch (e) {
|
||||||
|
alert((e && e.message) ? e.message : hitlT('submitFailed', 'Submit failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openHitlConversation(conversationId) {
|
||||||
|
const cid = String(conversationId || '').trim();
|
||||||
|
if (!cid) return;
|
||||||
|
if (typeof switchPage === 'function') {
|
||||||
|
switchPage('chat');
|
||||||
|
}
|
||||||
|
if (typeof loadConversation === 'function') {
|
||||||
|
loadConversation(cid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshHitlPending() {
|
async function refreshHitlPending() {
|
||||||
const container = document.getElementById('hitl-pending-list');
|
const container = document.getElementById('hitl-pending-list');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@@ -739,7 +814,27 @@ async function refreshHitlPending() {
|
|||||||
}
|
}
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
const items = Array.isArray(data.items) ? data.items : [];
|
const items = Array.isArray(data.items) ? data.items : [];
|
||||||
hitlPendingTotal = typeof data.total === 'number' ? data.total : items.length;
|
let workflowRuns = [];
|
||||||
|
try {
|
||||||
|
const wfResp = await hitlApiFetch('/api/workflows/runs/pending', { credentials: 'same-origin' });
|
||||||
|
if (wfResp.ok) {
|
||||||
|
const wfData = await wfResp.json().catch(function () { return {}; });
|
||||||
|
workflowRuns = Array.isArray(wfData.runs) ? wfData.runs : [];
|
||||||
|
}
|
||||||
|
} catch (wfErr) {
|
||||||
|
console.warn('fetch workflow pending runs failed', wfErr);
|
||||||
|
}
|
||||||
|
const searchQ = q && q.value.trim() ? q.value.trim().toLowerCase() : '';
|
||||||
|
if (searchQ) {
|
||||||
|
workflowRuns = workflowRuns.filter(function (run) {
|
||||||
|
const conv = String(run.conversation_id || run.conversationId || '').toLowerCase();
|
||||||
|
const wfId = String(run.workflow_id || run.workflowId || '').toLowerCase();
|
||||||
|
const runId = String(run.id || '').toLowerCase();
|
||||||
|
const label = hitlWorkflowPendingLabel(run).toLowerCase();
|
||||||
|
return conv.indexOf(searchQ) >= 0 || wfId.indexOf(searchQ) >= 0 || runId.indexOf(searchQ) >= 0 || label.indexOf(searchQ) >= 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hitlPendingTotal = (typeof data.total === 'number' ? data.total : items.length) + workflowRuns.length;
|
||||||
const maxPage = Math.max(1, Math.ceil(hitlPendingTotal / hitlPendingPageSize));
|
const maxPage = Math.max(1, Math.ceil(hitlPendingTotal / hitlPendingPageSize));
|
||||||
if (hitlPendingPage > maxPage) {
|
if (hitlPendingPage > maxPage) {
|
||||||
hitlPendingPage = maxPage;
|
hitlPendingPage = maxPage;
|
||||||
@@ -753,7 +848,13 @@ async function refreshHitlPending() {
|
|||||||
}
|
}
|
||||||
hitlPendingCache = items;
|
hitlPendingCache = items;
|
||||||
hitlPendingLoaded = true;
|
hitlPendingLoaded = true;
|
||||||
renderHitlPendingList(items);
|
const workflowHtml = renderWorkflowHitlPendingList(workflowRuns);
|
||||||
|
const toolHtml = items.length ? renderHitlPendingList(items) : '';
|
||||||
|
if (!workflowHtml && !toolHtml) {
|
||||||
|
container.innerHTML = '<div class="empty-state">' + escapeHtml(hitlT('emptyState', 'No pending approvals')) + '</div>';
|
||||||
|
} else {
|
||||||
|
container.innerHTML = workflowHtml + (workflowHtml && toolHtml ? '<div class="hitl-pending-section-divider"></div>' : '') + (toolHtml || '');
|
||||||
|
}
|
||||||
renderHitlPendingPagination();
|
renderHitlPendingPagination();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
hitlPendingLoaded = false;
|
hitlPendingLoaded = false;
|
||||||
@@ -1325,7 +1426,7 @@ function refreshHitlLogsI18n() {
|
|||||||
|
|
||||||
function refreshHitlPendingI18n() {
|
function refreshHitlPendingI18n() {
|
||||||
if (!document.getElementById('hitl-pending-list') || !hitlPendingLoaded) return;
|
if (!document.getElementById('hitl-pending-list') || !hitlPendingLoaded) return;
|
||||||
renderHitlPendingList(hitlPendingCache);
|
refreshHitlPending();
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshHitlI18n() {
|
function refreshHitlI18n() {
|
||||||
@@ -1501,6 +1602,8 @@ window.onHitlLogsPageSizeChange = onHitlLogsPageSizeChange;
|
|||||||
window.onHitlPendingPageSizeChange = onHitlPendingPageSizeChange;
|
window.onHitlPendingPageSizeChange = onHitlPendingPageSizeChange;
|
||||||
window.submitHitlDecision = submitHitlDecision;
|
window.submitHitlDecision = submitHitlDecision;
|
||||||
window.submitHitlDecisionWithPayload = submitHitlDecisionWithPayload;
|
window.submitHitlDecisionWithPayload = submitHitlDecisionWithPayload;
|
||||||
|
window.submitWorkflowHitlDecisionFromPage = submitWorkflowHitlDecisionFromPage;
|
||||||
|
window.openHitlConversation = openHitlConversation;
|
||||||
window.dismissHitlItem = dismissHitlItem;
|
window.dismissHitlItem = dismissHitlItem;
|
||||||
window.followAgentRunAfterHitlDecision = followAgentRunAfterHitlDecision;
|
window.followAgentRunAfterHitlDecision = followAgentRunAfterHitlDecision;
|
||||||
|
|
||||||
|
|||||||
@@ -1921,6 +1921,24 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'workflow_hitl_resumed': {
|
||||||
|
addTimelineItem(timeline, 'workflow_hitl_resumed', {
|
||||||
|
title: '✅ 审批已通过',
|
||||||
|
message: event.message || '人工审批已通过,继续执行',
|
||||||
|
data: event.data || {}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'workflow_hitl_rejected': {
|
||||||
|
addTimelineItem(timeline, 'workflow_hitl_rejected', {
|
||||||
|
title: '❌ 审批已拒绝',
|
||||||
|
message: event.message || '',
|
||||||
|
data: event.data || {}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'workflow_paused': {
|
case 'workflow_paused': {
|
||||||
addTimelineItem(timeline, 'workflow_paused', {
|
addTimelineItem(timeline, 'workflow_paused', {
|
||||||
title: '⏸️ 工作流已暂停',
|
title: '⏸️ 工作流已暂停',
|
||||||
@@ -2687,6 +2705,16 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'done':
|
case 'done':
|
||||||
|
if (event.data && event.data.workflowStatus === 'awaiting_hitl') {
|
||||||
|
const waitingTitle = document.querySelector(`#${progressId} .progress-title`);
|
||||||
|
if (waitingTitle) {
|
||||||
|
waitingTitle.textContent = '⏸️ ' + (typeof window.t === 'function' ? window.t('chat.workflowAwaitingApproval') : '工作流等待审批');
|
||||||
|
}
|
||||||
|
if (progressTaskState.has(progressId)) {
|
||||||
|
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('chat.workflowAwaitingApproval') : '等待审批');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
// 清理流式输出状态
|
// 清理流式输出状态
|
||||||
responseStreamStateByProgressId.delete(progressId);
|
responseStreamStateByProgressId.delete(progressId);
|
||||||
mainIterationStateByProgressId.delete(String(progressId));
|
mainIterationStateByProgressId.delete(String(progressId));
|
||||||
@@ -2911,6 +2939,11 @@ function renderInlineWorkflowHitlApproval(itemId, data) {
|
|||||||
setBusy(false);
|
setBusy(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (body && body.streamResuming) {
|
||||||
|
statusEl.textContent = approved ? '已通过,工作流继续执行中…' : '已拒绝';
|
||||||
|
panel.classList.add('hitl-inline-done');
|
||||||
|
return;
|
||||||
|
}
|
||||||
statusEl.textContent = approved ? '已通过,工作流继续执行' : '已拒绝';
|
statusEl.textContent = approved ? '已通过,工作流继续执行' : '已拒绝';
|
||||||
panel.classList.add('hitl-inline-done');
|
panel.classList.add('hitl-inline-done');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -2923,6 +2956,136 @@ function renderInlineWorkflowHitlApproval(itemId, data) {
|
|||||||
rejectBtn.onclick = function () { submit(false); };
|
rejectBtn.onclick = function () { submit(false); };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseWorkflowHitlPendingJSON(raw) {
|
||||||
|
if (!raw) return {};
|
||||||
|
if (typeof raw === 'object') return raw;
|
||||||
|
try {
|
||||||
|
const o = JSON.parse(String(raw));
|
||||||
|
return o && typeof o === 'object' ? o : {};
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function workflowHitlDataFromRun(run) {
|
||||||
|
if (!run) return null;
|
||||||
|
const runId = run.id || run.workflowRunId || run.workflow_run_id;
|
||||||
|
if (!runId) return null;
|
||||||
|
const pending = parseWorkflowHitlPendingJSON(run.pending_hitl_json || run.pendingHitlJson || run.pendingHitlJSON);
|
||||||
|
const pendingHitl = pending.pendingHitl && typeof pending.pendingHitl === 'object' ? pending.pendingHitl : pending;
|
||||||
|
return {
|
||||||
|
workflowRunId: String(runId),
|
||||||
|
nodeId: pendingHitl.nodeId || run.pending_hitl_node_id || run.pendingHitlNodeId || '',
|
||||||
|
label: pendingHitl.label || pendingHitl.nodeId || run.pending_hitl_node_id || run.pendingHitlNodeId || runId,
|
||||||
|
prompt: pendingHitl.prompt || '',
|
||||||
|
conversationId: run.conversation_id || run.conversationId || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findWorkflowHitlTimelineItem(detailsContainer, runId) {
|
||||||
|
if (!detailsContainer || !runId) return null;
|
||||||
|
const rid = String(runId).trim();
|
||||||
|
const byRun = detailsContainer.querySelector('[data-workflow-run-id="' + hitlEscapeAttrSelector(rid) + '"]');
|
||||||
|
if (byRun) return byRun;
|
||||||
|
const items = detailsContainer.querySelectorAll('.timeline-item-workflow_hitl_waiting');
|
||||||
|
for (let i = items.length - 1; i >= 0; i--) {
|
||||||
|
const el = items[i];
|
||||||
|
if (!el.querySelector('.workflow-hitl-inline-approval.hitl-inline-done')) {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items.length ? items[items.length - 1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新或切换会话后:根据 workflow_runs(awaiting_hitl) 恢复工作流内联审批入口。
|
||||||
|
*/
|
||||||
|
async function restoreWorkflowHitlInlineForConversation(conversationId) {
|
||||||
|
if (!conversationId || typeof apiFetch !== 'function') return;
|
||||||
|
if (typeof window.currentConversationId === 'string' && window.currentConversationId !== conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const resp = await apiFetch('/api/workflows/runs/pending?conversationId=' + encodeURIComponent(conversationId));
|
||||||
|
if (!resp.ok) return;
|
||||||
|
const data = await resp.json().catch(function () { return {}; });
|
||||||
|
const runs = Array.isArray(data.runs) ? data.runs : [];
|
||||||
|
if (!runs.length) return;
|
||||||
|
|
||||||
|
let msgEl = document.querySelector('#chat-messages [data-backend-message-id]');
|
||||||
|
const nodes = document.querySelectorAll('#chat-messages .message.assistant');
|
||||||
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
||||||
|
if (nodes[i] && nodes[i].dataset && nodes[i].dataset.backendMessageId) {
|
||||||
|
msgEl = nodes[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!msgEl || !msgEl.id) return;
|
||||||
|
const clientMsgId = msgEl.id;
|
||||||
|
const backendMsgId = msgEl.dataset.backendMessageId;
|
||||||
|
const detailsContainer = document.getElementById('process-details-' + clientMsgId);
|
||||||
|
if (!detailsContainer) return;
|
||||||
|
|
||||||
|
if (detailsContainer.dataset.lazyNotLoaded === '1' && detailsContainer.dataset.loaded !== '1') {
|
||||||
|
try {
|
||||||
|
detailsContainer.dataset.loading = '1';
|
||||||
|
if (typeof loadProcessDetailsPaginated === 'function') {
|
||||||
|
await loadProcessDetailsPaginated(clientMsgId, backendMsgId);
|
||||||
|
} else if (typeof apiFetch === 'function' && backendMsgId) {
|
||||||
|
const res = await apiFetch('/api/messages/' + encodeURIComponent(backendMsgId) + '/process-details');
|
||||||
|
const j = await res.json().catch(function () { return {}; });
|
||||||
|
if (res.ok && typeof renderProcessDetails === 'function') {
|
||||||
|
renderProcessDetails(clientMsgId, (j && Array.isArray(j.processDetails)) ? j.processDetails : []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载过程详情失败(工作流 HITL 恢复):', e);
|
||||||
|
} finally {
|
||||||
|
detailsContainer.dataset.loading = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expandProcessDetailsTimeline(clientMsgId);
|
||||||
|
|
||||||
|
for (let i = 0; i < runs.length; i++) {
|
||||||
|
const hitlData = workflowHitlDataFromRun(runs[i]);
|
||||||
|
if (!hitlData) continue;
|
||||||
|
let hitlItemEl = findWorkflowHitlTimelineItem(detailsContainer, hitlData.workflowRunId);
|
||||||
|
if (!hitlItemEl) {
|
||||||
|
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||||
|
if (timeline && typeof addTimelineItem === 'function') {
|
||||||
|
const itemId = addTimelineItem(timeline, 'workflow_hitl_waiting', {
|
||||||
|
title: '🧑⚖️ 工作流等待审批',
|
||||||
|
message: hitlData.label || '',
|
||||||
|
data: hitlData
|
||||||
|
});
|
||||||
|
hitlItemEl = document.getElementById(itemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hitlItemEl && hitlItemEl.id) {
|
||||||
|
renderInlineWorkflowHitlApproval(hitlItemEl.id, hitlData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('restoreWorkflowHitlInlineForConversation failed', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.restoreWorkflowHitlInlineForConversation = restoreWorkflowHitlInlineForConversation;
|
||||||
|
window.submitWorkflowHitlDecision = async function submitWorkflowHitlDecision(runId, approved, comment) {
|
||||||
|
const fetchFn = typeof apiFetch === 'function' ? apiFetch : fetch;
|
||||||
|
const response = await fetchFn('/api/workflows/runs/' + encodeURIComponent(String(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) {
|
||||||
|
throw new Error((body && body.error) ? body.error : '提交失败');
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
function hitlEscapeAttrSelector(val) {
|
function hitlEscapeAttrSelector(val) {
|
||||||
const s = String(val);
|
const s = String(val);
|
||||||
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
|
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
|
||||||
@@ -3046,6 +3209,9 @@ async function restoreHitlInlineForConversation(conversationId) {
|
|||||||
if (!hitlItemEl) continue;
|
if (!hitlItemEl) continue;
|
||||||
renderInlineHitlApproval(hitlItemEl.id, hitlData);
|
renderInlineHitlApproval(hitlItemEl.id, hitlData);
|
||||||
}
|
}
|
||||||
|
if (typeof restoreWorkflowHitlInlineForConversation === 'function') {
|
||||||
|
await restoreWorkflowHitlInlineForConversation(conversationId);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('restoreHitlInlineForConversation failed', e);
|
console.error('restoreHitlInlineForConversation failed', e);
|
||||||
}
|
}
|
||||||
@@ -3542,6 +3708,12 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
if (type === 'hitl_interrupt' && options.data && options.data.interruptId != null && String(options.data.interruptId).trim() !== '') {
|
if (type === 'hitl_interrupt' && options.data && options.data.interruptId != null && String(options.data.interruptId).trim() !== '') {
|
||||||
item.dataset.hitlInterruptId = String(options.data.interruptId).trim();
|
item.dataset.hitlInterruptId = String(options.data.interruptId).trim();
|
||||||
}
|
}
|
||||||
|
if (type === 'workflow_hitl_waiting' && options.data) {
|
||||||
|
const runId = options.data.workflowRunId || options.data.workflow_run_id;
|
||||||
|
if (runId != null && String(runId).trim() !== '') {
|
||||||
|
item.dataset.workflowRunId = String(runId).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (type === 'tool_result' && options.data) {
|
if (type === 'tool_result' && options.data) {
|
||||||
const d = options.data;
|
const d = options.data;
|
||||||
item.dataset.toolName = (d.toolName != null && d.toolName !== '') ? String(d.toolName) : '';
|
item.dataset.toolName = (d.toolName != null && d.toolName !== '') ? String(d.toolName) : '';
|
||||||
|
|||||||
Reference in New Issue
Block a user