mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-30 17:55:32 +02:00
Add files via upload
This commit is contained in:
+468
-1
@@ -1513,7 +1513,7 @@ header {
|
||||
.hitl-pending-payload {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 160px;
|
||||
max-height: min(20vh, 180px);
|
||||
overflow: auto;
|
||||
margin: 0 0 4px 0;
|
||||
padding: 10px 12px;
|
||||
@@ -1595,6 +1595,473 @@ header {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.hitl-context-block {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.hitl-context-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.hitl-context-text {
|
||||
margin: 0;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: min(24vh, 210px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.hitl-log-modal-content .hitl-context-text,
|
||||
.hitl-log-detail-payload .hitl-context-text {
|
||||
max-height: min(28vh, 260px);
|
||||
}
|
||||
|
||||
.hitl-log-readonly-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.hitl-context-block--execution .hitl-context-text {
|
||||
background: #f0fdf4;
|
||||
border-color: #bbf7d0;
|
||||
max-height: min(20vh, 180px);
|
||||
}
|
||||
|
||||
.hitl-logs-summary {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
.hitl-reviewer-toggle {
|
||||
display: inline-flex;
|
||||
gap: 0;
|
||||
padding: 3px;
|
||||
border-radius: 10px;
|
||||
background: #f1f5f9;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.hitl-reviewer-toggle-btn {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hitl-reviewer-toggle-btn:hover {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.hitl-reviewer-toggle-btn.is-active {
|
||||
background: #fff;
|
||||
color: var(--accent-color, #0066ff);
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.hitl-reviewer-toggle-btn:focus-visible {
|
||||
outline: 2px solid var(--accent-color, #0066ff);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.hitl-page-reviewer-bar {
|
||||
margin-bottom: 16px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.hitl-page-reviewer-main {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.hitl-page-reviewer-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.hitl-page-reviewer-hint {
|
||||
margin: 10px 0 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.hitl-page-whitelist-bar {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hitl-page-whitelist-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.hitl-page-whitelist-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.hitl-page-whitelist-hint {
|
||||
margin: 0 0 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.hitl-page-whitelist-textarea {
|
||||
width: 100%;
|
||||
min-height: 140px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: #0f172a;
|
||||
background: #f8fafc;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.hitl-page-whitelist-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color, #0066ff);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.12);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hitl-page-strategy-bar {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hitl-page-strategy-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.hitl-page-strategy-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.hitl-page-strategy-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.hitl-strategy-subtabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.hitl-strategy-subtab {
|
||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
border-radius: 8px;
|
||||
padding: 5px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hitl-strategy-subtab:hover {
|
||||
background: #f1f5f9;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.hitl-strategy-subtab--active {
|
||||
background: #e0e7ff;
|
||||
border-color: #a5b4fc;
|
||||
color: #3730a3;
|
||||
}
|
||||
|
||||
.hitl-page-strategy-hint {
|
||||
margin: 0 0 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.hitl-strategy-textarea {
|
||||
width: 100%;
|
||||
min-height: 320px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: #0f172a;
|
||||
background: #f8fafc;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.hitl-strategy-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color, #0066ff);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.12);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hitl-page-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.hitl-page-tab {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary, #64748b);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.hitl-page-tab--active {
|
||||
color: var(--accent-color, #0066ff);
|
||||
border-bottom-color: var(--accent-color, #0066ff);
|
||||
}
|
||||
|
||||
.hitl-tab-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 6px;
|
||||
border-radius: 999px;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hitl-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: flex-end;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.hitl-filters label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary, #64748b);
|
||||
}
|
||||
|
||||
.hitl-filter-input,
|
||||
.hitl-filter-select {
|
||||
min-width: 220px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hitl-logs-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hitl-logs-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hitl-logs-table th,
|
||||
.hitl-logs-table td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.hitl-logs-table th {
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hitl-logs-cell-mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 12px;
|
||||
max-width: 180px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.hitl-logs-actions {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hitl-decision-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
background: #f1f5f9;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.hitl-decision-tag.hitl-decision--approve {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.hitl-decision-tag.hitl-decision--reject {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.hitl-logs-pagination,
|
||||
.hitl-pending-pagination {
|
||||
margin-top: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hitl-logs-pagination .monitor-pagination,
|
||||
.hitl-pending-pagination .monitor-pagination {
|
||||
margin-top: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.hitl-log-modal-content {
|
||||
max-width: 640px;
|
||||
width: calc(100vw - 32px);
|
||||
}
|
||||
|
||||
.hitl-log-detail-meta {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hitl-log-detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hitl-log-detail-row--full {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hitl-log-detail-row--full dt {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.hitl-log-detail-row dt {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.hitl-log-detail-row dd {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #0f172a;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.hitl-log-detail-mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hitl-log-detail-payload {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.hitl-logs-empty-hint {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--accent-color, #0066ff);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 0 6px 0 0;
|
||||
}
|
||||
|
||||
.btn-link--danger {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.hitl-config-select:focus,
|
||||
.hitl-config-textarea:focus {
|
||||
outline: none;
|
||||
|
||||
@@ -645,7 +645,10 @@
|
||||
"agentModeOrchSupervisor": "Supervisor",
|
||||
"hitlTitle": "Human-in-the-loop",
|
||||
"hitlCardSubtitle": "Approvals & allowlist",
|
||||
"hitlReviewer": "Review",
|
||||
"hitlReviewerLabel": "Reviewer",
|
||||
"hitlReviewerHuman": "Human approval",
|
||||
"hitlReviewerAgent": "Audit Agent",
|
||||
"hitlReviewerHint": "Switch between human and Audit Agent anytime; rules and whitelist stay the same. You can pre-select even when HITL is off.",
|
||||
"hitlConfigTitle": "Collaboration mode config",
|
||||
"hitlModeLabel": "Mode",
|
||||
"hitlModeOff": "Off",
|
||||
@@ -664,7 +667,75 @@
|
||||
},
|
||||
"hitl": {
|
||||
"pageTitle": "HITL approvals",
|
||||
"pageReviewerLabel": "Current reviewer",
|
||||
"pageReviewerHint": "Applies to the selected conversation. Without a conversation, saved locally for new chats. Takes effect immediately.",
|
||||
"pageReviewerSaved": "Reviewer saved.",
|
||||
"whitelistLabel": "Tool whitelist (no approval)",
|
||||
"whitelistHint": "One per line or comma-separated. Saved to config.yaml global whitelist and takes effect immediately (synced with chat sidebar).",
|
||||
"whitelistSaved": "Whitelist saved.",
|
||||
"whitelistSaveFailed": "Failed to save whitelist",
|
||||
"strategyLabel": "Audit strategy (prompt)",
|
||||
"strategyHint": "Whitelisted tools skip approval. Other tools are judged by the model using this prompt when Audit Agent is selected.",
|
||||
"strategyTabApproval": "Approval mode",
|
||||
"strategyTabReviewEdit": "Review & edit mode",
|
||||
"strategyHintApproval": "Whitelisted tools skip approval. In approval mode the Audit Agent only approves or rejects.",
|
||||
"strategyHintReviewEdit": "In review & edit mode the Audit Agent may narrow parameters via editedArguments before approve; reject if parameters cannot be safely adjusted.",
|
||||
"strategyReset": "Reset to default",
|
||||
"strategySaved": "Audit strategy saved.",
|
||||
"strategySaveFailed": "Failed to save audit strategy",
|
||||
"tabPending": "Pending",
|
||||
"tabLogs": "Audit logs",
|
||||
"tabStrategy": "Audit strategy",
|
||||
"tabWhitelist": "Tool whitelist",
|
||||
"pendingTitle": "Pending approvals",
|
||||
"searchLabel": "Search",
|
||||
"searchPlaceholder": "Tool, conversation, payload, comment…",
|
||||
"searchApply": "Search",
|
||||
"filterDecision": "Decision",
|
||||
"filterDecidedBy": "Reviewer",
|
||||
"filterAll": "All",
|
||||
"decisionApprove": "Approve",
|
||||
"decisionReject": "Reject",
|
||||
"reviewerHuman": "Human",
|
||||
"reviewerAgent": "Audit Agent",
|
||||
"reviewerSystem": "System",
|
||||
"reviewerManual": "Manual entry",
|
||||
"logCreate": "New log",
|
||||
"logModalTitle": "Audit log",
|
||||
"logModalEdit": "Edit audit log",
|
||||
"fieldConversation": "Conversation ID",
|
||||
"fieldTool": "Tool name",
|
||||
"fieldComment": "Comment",
|
||||
"fieldPayload": "Payload (JSON)",
|
||||
"fieldUserMessage": "User message",
|
||||
"fieldThinking": "Thinking",
|
||||
"fieldReasoning": "Reasoning chain",
|
||||
"fieldPlanning": "Planning",
|
||||
"colId": "ID",
|
||||
"colTool": "Tool",
|
||||
"colConversation": "Conversation",
|
||||
"colDecision": "Decision",
|
||||
"colDecidedBy": "Reviewer",
|
||||
"colContext": "Context",
|
||||
"colTime": "Time",
|
||||
"colActions": "Actions",
|
||||
"viewDetail": "Detail",
|
||||
"logModalView": "Audit log detail",
|
||||
"fieldExecutionResult": "Execution result",
|
||||
"executionSuccess": "success",
|
||||
"executionFailed": "failed",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"logsEmpty": "No audit logs",
|
||||
"logsEmptyHint": "Records are created automatically when HITL approvals are approved or rejected.",
|
||||
"pageInfo": "{{total}} total",
|
||||
"prevPage": "Previous",
|
||||
"nextPage": "Next",
|
||||
"conversationRequired": "Conversation ID is required",
|
||||
"toolRequired": "Tool name is required",
|
||||
"saveFailed": "Save failed",
|
||||
"deleteConfirm": "Delete this audit log?",
|
||||
"deleteFailed": "Delete failed",
|
||||
"loading": "Loading...",
|
||||
"emptyState": "No pending approvals",
|
||||
"dismiss": "Dismiss",
|
||||
|
||||
@@ -633,7 +633,10 @@
|
||||
"agentModeOrchSupervisor": "Supervisor",
|
||||
"hitlTitle": "人机协同",
|
||||
"hitlCardSubtitle": "审批与白名单",
|
||||
"hitlReviewer": "Review",
|
||||
"hitlReviewerLabel": "审批方",
|
||||
"hitlReviewerHuman": "人工审批",
|
||||
"hitlReviewerAgent": "审计 Agent",
|
||||
"hitlReviewerHint": "可在人工与审计 Agent 之间随时切换;规则与白名单不变。人机协同为「关闭」时也可预先选择。",
|
||||
"hitlConfigTitle": "协同模式配置",
|
||||
"hitlModeLabel": "模式",
|
||||
"hitlModeOff": "关闭",
|
||||
@@ -642,7 +645,7 @@
|
||||
"hitlSensitiveTools": "敏感工具(逗号分隔)",
|
||||
"hitlWhitelistTools": "白名单工具(免审批,逗号分隔)",
|
||||
"hitlWhitelistPlaceholder": "例:read_file, grep 或每行一个工具名(与 config 全局白名单合并)",
|
||||
"hitlWhitelistHint": "每行一个或逗号分隔;与 config 中全局白名单合并展示。",
|
||||
"hitlWhitelistHint": "白名单内工具免审批;每行一个或逗号分隔,与 config 全局白名单合并。",
|
||||
"hitlApply": "应用",
|
||||
"hitlApplyOkSync": "人机协同配置已保存并同步到服务器。",
|
||||
"hitlApplyOkWhitelistYaml": "免审批工具已合并进 config.yaml 并生效。协同模式、超时等仍须选中会话后再点「应用」才会写入服务器。",
|
||||
@@ -652,7 +655,75 @@
|
||||
},
|
||||
"hitl": {
|
||||
"pageTitle": "人机协同审批",
|
||||
"pageReviewerLabel": "当前审批方",
|
||||
"pageReviewerHint": "作用于当前选中会话;未选会话时保存到本机,新建会话时沿用。切换后立即生效。",
|
||||
"pageReviewerSaved": "审批方已保存。",
|
||||
"whitelistLabel": "免审批工具白名单",
|
||||
"whitelistHint": "每行一个或逗号分隔;保存后写入 config.yaml 全局白名单并立即生效(与聊天侧栏同步展示)。",
|
||||
"whitelistSaved": "白名单已保存。",
|
||||
"whitelistSaveFailed": "保存白名单失败",
|
||||
"strategyLabel": "审计策略(提示词)",
|
||||
"strategyHint": "白名单内工具免审批;其余工具在审批方为「审计 Agent」时,由模型按此提示词自主裁决。",
|
||||
"strategyTabApproval": "审批模式",
|
||||
"strategyTabReviewEdit": "审查编辑模式",
|
||||
"strategyHintApproval": "白名单内工具免审批;审批模式下审计 Agent 仅裁决通过/拒绝。",
|
||||
"strategyHintReviewEdit": "审查编辑模式下审计 Agent 可通过 editedArguments 收窄参数后放行;无法安全改参时应拒绝。",
|
||||
"strategyReset": "恢复默认",
|
||||
"strategySaved": "审计策略已保存。",
|
||||
"strategySaveFailed": "保存审计策略失败",
|
||||
"tabPending": "待审计",
|
||||
"tabLogs": "审计日志",
|
||||
"tabStrategy": "审计策略",
|
||||
"tabWhitelist": "工具白名单",
|
||||
"pendingTitle": "待处理审批",
|
||||
"searchLabel": "搜索",
|
||||
"searchPlaceholder": "工具名、会话 ID、载荷、备注…",
|
||||
"searchApply": "搜索",
|
||||
"filterDecision": "决策",
|
||||
"filterDecidedBy": "审批方",
|
||||
"filterAll": "全部",
|
||||
"decisionApprove": "通过",
|
||||
"decisionReject": "拒绝",
|
||||
"reviewerHuman": "人工",
|
||||
"reviewerAgent": "审计 Agent",
|
||||
"reviewerSystem": "系统",
|
||||
"reviewerManual": "手动录入",
|
||||
"logCreate": "新建日志",
|
||||
"logModalTitle": "审计日志",
|
||||
"logModalEdit": "编辑审计日志",
|
||||
"fieldConversation": "会话 ID",
|
||||
"fieldTool": "工具名",
|
||||
"fieldComment": "备注",
|
||||
"fieldPayload": "载荷 (JSON)",
|
||||
"fieldUserMessage": "用户原话",
|
||||
"fieldThinking": "本轮思考",
|
||||
"fieldReasoning": "推理链",
|
||||
"fieldPlanning": "规划",
|
||||
"colId": "ID",
|
||||
"colTool": "工具",
|
||||
"colConversation": "会话",
|
||||
"colDecision": "决策",
|
||||
"colDecidedBy": "审批方",
|
||||
"colContext": "上下文",
|
||||
"colTime": "时间",
|
||||
"colActions": "操作",
|
||||
"viewDetail": "详情",
|
||||
"logModalView": "审计日志详情",
|
||||
"fieldExecutionResult": "执行结果",
|
||||
"executionSuccess": "成功",
|
||||
"executionFailed": "失败",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"logsEmpty": "暂无审计日志",
|
||||
"logsEmptyHint": "人机协同审批通过或拒绝后会自动记录在此。",
|
||||
"pageInfo": "共 {{total}} 条",
|
||||
"prevPage": "上一页",
|
||||
"nextPage": "下一页",
|
||||
"conversationRequired": "请填写会话 ID",
|
||||
"toolRequired": "请填写工具名",
|
||||
"saveFailed": "保存失败",
|
||||
"deleteConfirm": "确定删除这条审计日志?",
|
||||
"deleteFailed": "删除失败",
|
||||
"loading": "加载中...",
|
||||
"emptyState": "暂无待审批项",
|
||||
"dismiss": "忽略",
|
||||
|
||||
@@ -22,7 +22,7 @@ const AUDIT_ACTIONS_BY_CATEGORY = {
|
||||
task: ['create_queue', 'start_queue', 'delete_queue', 'pause_queue', 'rerun_queue', 'delete_batch_task'],
|
||||
tool: ['execution_delete', 'execution_delete_batch'],
|
||||
file: ['upload', 'delete'],
|
||||
hitl: ['decision'],
|
||||
hitl: ['decision', 'audit_strategy_update'],
|
||||
role: ['create', 'update', 'delete'],
|
||||
skill: ['create', 'update', 'delete'],
|
||||
agent: ['markdown_create', 'markdown_update', 'markdown_delete']
|
||||
|
||||
+81
-1
@@ -139,11 +139,18 @@ function normalizeHitlMode(mode) {
|
||||
function defaultHitlConfig() {
|
||||
return {
|
||||
mode: HITL_MODE_OFF,
|
||||
reviewer: 'human',
|
||||
sensitiveTools: '',
|
||||
updatedAt: ''
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeHitlReviewer(v) {
|
||||
const x = String(v || '').trim().toLowerCase();
|
||||
if (x === 'audit_agent' || x === 'agent' || x === 'ai') return 'audit_agent';
|
||||
return 'human';
|
||||
}
|
||||
|
||||
/** 白名单字符串拆成数组(逗号或换行分隔,与 textarea 一致) */
|
||||
function hitlToolsSplitToArray(s) {
|
||||
return String(s || '')
|
||||
@@ -218,6 +225,7 @@ function getHitlLastGlobalConfig() {
|
||||
if (!parsed || typeof parsed !== 'object') return null;
|
||||
return {
|
||||
mode: normalizeHitlMode(parsed.mode),
|
||||
reviewer: normalizeHitlReviewer(parsed.reviewer),
|
||||
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
|
||||
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
|
||||
};
|
||||
@@ -248,6 +256,7 @@ function getHitlConfigForConversation(conversationId) {
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
draftCfg = {
|
||||
mode: normalizeHitlMode(parsed.mode),
|
||||
reviewer: normalizeHitlReviewer(parsed.reviewer),
|
||||
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
|
||||
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
|
||||
};
|
||||
@@ -258,6 +267,7 @@ function getHitlConfigForConversation(conversationId) {
|
||||
}
|
||||
const g = globalLast ? {
|
||||
mode: normalizeHitlMode(globalLast.mode),
|
||||
reviewer: normalizeHitlReviewer(globalLast.reviewer),
|
||||
sensitiveTools: typeof globalLast.sensitiveTools === 'string' ? globalLast.sensitiveTools : fallback.sensitiveTools,
|
||||
updatedAt: typeof globalLast.updatedAt === 'string' ? globalLast.updatedAt : ''
|
||||
} : null;
|
||||
@@ -280,6 +290,7 @@ function getHitlConfigForConversation(conversationId) {
|
||||
}
|
||||
return {
|
||||
mode: normalizeHitlMode(parsed.mode),
|
||||
reviewer: normalizeHitlReviewer(parsed.reviewer),
|
||||
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
|
||||
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
|
||||
};
|
||||
@@ -288,10 +299,52 @@ function getHitlConfigForConversation(conversationId) {
|
||||
}
|
||||
}
|
||||
|
||||
function setHitlReviewerUI(reviewer) {
|
||||
const v = normalizeHitlReviewer(reviewer);
|
||||
const hidden = document.getElementById('hitl-reviewer-select');
|
||||
if (hidden) hidden.value = v;
|
||||
document.querySelectorAll('.hitl-reviewer-toggle-btn').forEach(function (btn) {
|
||||
const active = btn.getAttribute('data-reviewer') === v;
|
||||
btn.classList.toggle('is-active', active);
|
||||
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
async function onHitlReviewerChanged(reviewer) {
|
||||
setHitlReviewerUI(reviewer);
|
||||
const cfg = readHitlConfigFromForm();
|
||||
const cid = typeof currentConversationId === 'string' ? currentConversationId.trim() : '';
|
||||
saveHitlConfigForConversation(cid, cfg, { syncGlobalLast: true });
|
||||
if (cid && typeof window.saveHitlConversationConfig === 'function') {
|
||||
try {
|
||||
await window.saveHitlConversationConfig(cid, cfg);
|
||||
const ok = typeof window.t === 'function' ? window.t('hitl.pageReviewerSaved') : '审批方已保存。';
|
||||
showChatToast(ok, 'success');
|
||||
} catch (e) {
|
||||
console.warn('onHitlReviewerChanged', e);
|
||||
const prefix = typeof window.t === 'function' ? window.t('chat.hitlApplyFail') : '同步到服务器失败';
|
||||
showChatToast(prefix, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindHitlReviewerToggleListeners() {
|
||||
document.querySelectorAll('.hitl-reviewer-toggle-btn').forEach(function (btn) {
|
||||
if (btn.dataset.hitlReviewerBound === '1') return;
|
||||
btn.dataset.hitlReviewerBound = '1';
|
||||
btn.addEventListener('click', function () {
|
||||
const v = btn.getAttribute('data-reviewer');
|
||||
if (!v) return;
|
||||
onHitlReviewerChanged(v);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function saveHitlConfigForConversation(conversationId, cfg, opts) {
|
||||
const syncGlobalLast = !!(opts && opts.syncGlobalLast);
|
||||
const payload = {
|
||||
mode: normalizeHitlMode(cfg && cfg.mode),
|
||||
reviewer: normalizeHitlReviewer(cfg && cfg.reviewer),
|
||||
sensitiveTools: typeof (cfg && cfg.sensitiveTools) === 'string' ? cfg.sensitiveTools : '',
|
||||
updatedAt: typeof (cfg && cfg.updatedAt) === 'string' ? cfg.updatedAt : ''
|
||||
};
|
||||
@@ -308,8 +361,10 @@ function saveHitlConfigForConversation(conversationId, cfg, opts) {
|
||||
|
||||
function readHitlConfigFromForm() {
|
||||
const modeEl = document.getElementById('hitl-mode-select');
|
||||
const reviewerEl = document.getElementById('hitl-reviewer-select');
|
||||
const toolsEl = document.getElementById('hitl-sensitive-tools');
|
||||
const mode = normalizeHitlMode(modeEl ? modeEl.value : HITL_MODE_OFF);
|
||||
const reviewer = normalizeHitlReviewer(reviewerEl ? reviewerEl.value : 'human');
|
||||
let sensitiveTools = toolsEl ? String(toolsEl.value || '').trim() : '';
|
||||
const g = typeof window !== 'undefined' ? window.csaiHitlGlobalToolWhitelist : null;
|
||||
if (Array.isArray(g) && g.length > 0) {
|
||||
@@ -317,6 +372,7 @@ function readHitlConfigFromForm() {
|
||||
}
|
||||
return {
|
||||
mode,
|
||||
reviewer,
|
||||
sensitiveTools,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
@@ -330,7 +386,9 @@ function applyHitlConfigToUI(cfg) {
|
||||
const conf = cfg || defaultHitlConfig();
|
||||
const modeEl = document.getElementById('hitl-mode-select');
|
||||
const toolsEl = document.getElementById('hitl-sensitive-tools');
|
||||
if (modeEl) modeEl.value = normalizeHitlMode(conf.mode);
|
||||
const uiMode = normalizeHitlMode(conf.mode);
|
||||
if (modeEl) modeEl.value = uiMode;
|
||||
setHitlReviewerUI(conf.reviewer);
|
||||
let toolsVal = conf.sensitiveTools || '';
|
||||
const g = typeof window !== 'undefined' ? window.csaiHitlGlobalToolWhitelist : null;
|
||||
if (Array.isArray(g) && g.length > 0) {
|
||||
@@ -341,6 +399,15 @@ function applyHitlConfigToUI(cfg) {
|
||||
updateHitlStatusUI(conf);
|
||||
}
|
||||
|
||||
function bindHitlSidebarModeListener() {
|
||||
const modeEl = document.getElementById('hitl-mode-select');
|
||||
if (!modeEl || modeEl.dataset.hitlModeBound === '1') return;
|
||||
modeEl.dataset.hitlModeBound = '1';
|
||||
modeEl.addEventListener('change', function () {
|
||||
applyHitlConfigToUI(readHitlConfigFromForm());
|
||||
});
|
||||
}
|
||||
|
||||
function refreshHitlConfigByCurrentConversation() {
|
||||
const cfg = getHitlConfigForConversation(currentConversationId || '');
|
||||
applyHitlConfigToUI(cfg);
|
||||
@@ -413,6 +480,9 @@ async function applyHitlSidebarConfig() {
|
||||
const localOnly = typeof window.t === 'function' ? window.t('chat.hitlApplyOkLocal') : '已保存到本浏览器。';
|
||||
showHitlApplyFeedback(localOnly, false);
|
||||
}
|
||||
if (typeof window.refreshHitlPageWhitelist === 'function') {
|
||||
window.refreshHitlPageWhitelist();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('applyHitlSidebarConfig', e);
|
||||
const prefix = typeof window.t === 'function' ? window.t('chat.hitlApplyFail') : '同步到服务器失败';
|
||||
@@ -449,6 +519,12 @@ if (typeof window !== 'undefined') {
|
||||
window.readHitlConfigFromForm = readHitlConfigFromForm;
|
||||
window.applyHitlConfigToUI = applyHitlConfigToUI;
|
||||
window.saveHitlConfigForConversation = saveHitlConfigForConversation;
|
||||
window.getHitlConfigForConversation = getHitlConfigForConversation;
|
||||
bindHitlSidebarModeListener();
|
||||
bindHitlReviewerToggleListeners();
|
||||
window.setHitlReviewerUI = setHitlReviewerUI;
|
||||
window.onHitlReviewerChanged = onHitlReviewerChanged;
|
||||
window.bindHitlReviewerToggleListeners = bindHitlReviewerToggleListeners;
|
||||
window.getHitlLastGlobalConfig = getHitlLastGlobalConfig;
|
||||
window.hitlMergeToolsForDisplay = hitlMergeToolsForDisplay;
|
||||
window.hitlStripGlobalToolsFromFormString = hitlStripGlobalToolsFromFormString;
|
||||
@@ -743,6 +819,9 @@ async function initChatAgentModeFromConfig() {
|
||||
window.csaiHitlGlobalToolWhitelist = tw.slice();
|
||||
}
|
||||
}
|
||||
if (typeof window.refreshHitlPageWhitelist === 'function') {
|
||||
window.refreshHitlPageWhitelist();
|
||||
}
|
||||
document.querySelectorAll('.agent-mode-option').forEach(function (el) {
|
||||
const v = el.getAttribute('data-value');
|
||||
if (v === 'deep' || v === 'plan_execute' || v === 'supervisor') {
|
||||
@@ -959,6 +1038,7 @@ async function sendMessage() {
|
||||
body.hitl = {
|
||||
enabled: true,
|
||||
mode: normalizeHitlMode(hitlCfg.mode),
|
||||
reviewer: normalizeHitlReviewer(hitlCfg.reviewer),
|
||||
sensitiveTools: sensitiveTools
|
||||
};
|
||||
}
|
||||
|
||||
+866
-17
@@ -1,3 +1,79 @@
|
||||
function hitlReviewerNormalize(v) {
|
||||
const x = String(v || '').trim().toLowerCase();
|
||||
if (x === 'audit_agent' || x === 'agent' || x === 'ai') return 'audit_agent';
|
||||
return 'human';
|
||||
}
|
||||
|
||||
function hitlParsePayloadObject(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 hitlRenderContextBlocks(payloadObj) {
|
||||
if (!payloadObj || typeof payloadObj !== 'object') return '';
|
||||
const blocks = [];
|
||||
function addBlock(label, value) {
|
||||
const s = String(value || '').trim();
|
||||
if (!s) return;
|
||||
blocks.push(
|
||||
'<div class="hitl-context-block">' +
|
||||
'<div class="hitl-context-label">' + escapeHtml(label) + '</div>' +
|
||||
'<pre class="hitl-context-text">' + escapeHtml(s) + '</pre>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
addBlock(hitlT('fieldUserMessage', 'User message'), payloadObj.userMessage);
|
||||
addBlock(hitlT('fieldThinking', 'Thinking'), payloadObj.thinking);
|
||||
addBlock(hitlT('fieldReasoning', 'Reasoning'), payloadObj.reasoningChain);
|
||||
addBlock(hitlT('fieldPlanning', 'Planning'), payloadObj.planning);
|
||||
return blocks.join('');
|
||||
}
|
||||
|
||||
function hitlRenderExecutionResultBlock(payloadObj) {
|
||||
if (!payloadObj || typeof payloadObj !== 'object') return '';
|
||||
const exec = payloadObj.executionResult;
|
||||
if (!exec || typeof exec !== 'object') return '';
|
||||
const ok = exec.success === true;
|
||||
const label = hitlT('fieldExecutionResult', 'Execution result') + (ok ? ' (' + hitlT('executionSuccess', 'success') + ')' : ' (' + hitlT('executionFailed', 'failed') + ')');
|
||||
const text = String(exec.result || '').trim();
|
||||
if (!text) return '';
|
||||
return (
|
||||
'<div class="hitl-context-block hitl-context-block--execution">' +
|
||||
'<div class="hitl-context-label">' + escapeHtml(label) + '</div>' +
|
||||
'<pre class="hitl-context-text">' + escapeHtml(text) + '</pre>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
function hitlFillLogModalReadonlySections(payloadObj) {
|
||||
const ctxEl = document.getElementById('hitl-log-context-readonly');
|
||||
const execEl = document.getElementById('hitl-log-execution-readonly');
|
||||
const ctxHtml = hitlRenderContextBlocks(payloadObj);
|
||||
const execHtml = hitlRenderExecutionResultBlock(payloadObj);
|
||||
if (ctxEl) {
|
||||
ctxEl.innerHTML = ctxHtml;
|
||||
ctxEl.hidden = !ctxHtml;
|
||||
}
|
||||
if (execEl) {
|
||||
execEl.innerHTML = execHtml;
|
||||
execEl.hidden = !execHtml;
|
||||
}
|
||||
}
|
||||
|
||||
function hitlPayloadSummary(payloadObj) {
|
||||
const parts = [];
|
||||
if (payloadObj && payloadObj.userMessage) parts.push(hitlT('fieldUserMessage', 'User'));
|
||||
if (payloadObj && payloadObj.thinking) parts.push(hitlT('fieldThinking', 'Thinking'));
|
||||
if (payloadObj && payloadObj.executionResult) parts.push(hitlT('fieldExecutionResult', 'Result'));
|
||||
return parts.length ? parts.join(' · ') : '—';
|
||||
}
|
||||
|
||||
function hitlModeNormalize(m) {
|
||||
let v = String(m || '').trim().toLowerCase().replace(/-/g, '_');
|
||||
if (v === 'feedback' || v === 'followup') {
|
||||
@@ -20,6 +96,81 @@ function hitlT(key, fallback, params) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const HITL_LOGS_PAGE_SIZE_KEY = 'cyberstrike_hitl_logs_page_size';
|
||||
const HITL_PENDING_PAGE_SIZE_KEY = 'cyberstrike_hitl_pending_page_size';
|
||||
const HITL_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
|
||||
|
||||
function hitlPaginationT(key, opts, fallback) {
|
||||
if (typeof window.t === 'function') {
|
||||
const keys = (key === 'paginationInfo' || key === 'perPageLabel')
|
||||
? ['mcpMonitor.' + key, 'mcp.' + key]
|
||||
: ['mcp.' + key];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const v = window.t(keys[i], opts || {});
|
||||
if (typeof v === 'string' && v && v !== keys[i]) return v;
|
||||
}
|
||||
}
|
||||
return fallback != null ? fallback : key;
|
||||
}
|
||||
|
||||
function hitlLocale() {
|
||||
if (typeof window.__locale === 'string' && window.__locale.length) {
|
||||
return window.__locale.startsWith('zh') ? 'zh-CN' : 'en-US';
|
||||
}
|
||||
return (typeof navigator !== 'undefined' && navigator.language) ? navigator.language : 'en-US';
|
||||
}
|
||||
|
||||
function initHitlPageSizeFromStorage(storageKey, fallbackSize, assignFn) {
|
||||
try {
|
||||
const saved = parseInt(localStorage.getItem(storageKey), 10);
|
||||
if (HITL_PAGE_SIZE_OPTIONS.indexOf(saved) >= 0) {
|
||||
assignFn(saved);
|
||||
return;
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
assignFn(fallbackSize);
|
||||
}
|
||||
|
||||
function renderHitlPagination(containerId, state, goPageFnName, pageSizeChangeFnName, pageSizeSelectId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
const esc = typeof escapeHtml === 'function' ? escapeHtml : function (s) { return String(s || ''); };
|
||||
const total = state.total || 0;
|
||||
const currentPage = state.page || 1;
|
||||
const pageSize = state.pageSize || 20;
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||
const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
|
||||
const end = total === 0 ? 0 : Math.min(currentPage * pageSize, total);
|
||||
const infoText = hitlPaginationT('paginationInfo', { start: start, end: end, total: total },
|
||||
'显示 ' + start + '-' + end + ' / 共 ' + total + ' 条记录');
|
||||
const perPageLabel = hitlPaginationT('perPageLabel', null, '每页显示');
|
||||
const firstPageLabel = hitlPaginationT('firstPage', null, '首页');
|
||||
const prevPageLabel = hitlPaginationT('prevPage', null, '上一页');
|
||||
const pageInfoText = hitlPaginationT('pageInfo', { page: currentPage, total: totalPages },
|
||||
'第 ' + currentPage + ' / ' + totalPages + ' 页');
|
||||
const nextPageLabel = hitlPaginationT('nextPage', null, '下一页');
|
||||
const lastPageLabel = hitlPaginationT('lastPage', null, '末页');
|
||||
const disabledFirst = currentPage === 1 || total === 0;
|
||||
const disabledLast = currentPage >= totalPages || total === 0;
|
||||
let html = '<div class="monitor-pagination">';
|
||||
html += '<div class="pagination-info">';
|
||||
html += '<span>' + esc(infoText) + '</span>';
|
||||
html += '<label class="pagination-page-size">' + esc(perPageLabel);
|
||||
html += '<select id="' + esc(pageSizeSelectId) + '" onchange="' + esc(pageSizeChangeFnName) + '()">';
|
||||
HITL_PAGE_SIZE_OPTIONS.forEach(function (n) {
|
||||
html += '<option value="' + n + '"' + (pageSize === n ? ' selected' : '') + '>' + n + '</option>';
|
||||
});
|
||||
html += '</select></label></div>';
|
||||
html += '<div class="pagination-controls">';
|
||||
html += '<button type="button" class="btn-secondary" onclick="' + esc(goPageFnName) + '(1)"' + (disabledFirst ? ' disabled' : '') + '>' + esc(firstPageLabel) + '</button>';
|
||||
html += '<button type="button" class="btn-secondary" onclick="' + esc(goPageFnName) + '(' + (currentPage - 1) + ')"' + (disabledFirst ? ' disabled' : '') + '>' + esc(prevPageLabel) + '</button>';
|
||||
html += '<span class="pagination-page">' + esc(pageInfoText) + '</span>';
|
||||
html += '<button type="button" class="btn-secondary" onclick="' + esc(goPageFnName) + '(' + (currentPage + 1) + ')"' + (disabledLast ? ' disabled' : '') + '>' + esc(nextPageLabel) + '</button>';
|
||||
html += '<button type="button" class="btn-secondary" onclick="' + esc(goPageFnName) + '(' + totalPages + ')"' + (disabledLast ? ' disabled' : '') + '>' + esc(lastPageLabel) + '</button>';
|
||||
html += '</div></div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function hitlEffectiveEnabled(cfg) {
|
||||
if (!cfg) return false;
|
||||
if (cfg.enabled === true) return true;
|
||||
@@ -104,12 +255,189 @@ async function mergeHitlGlobalToolWhitelist(sensitiveTools) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function hitlPageToolsSplit(s) {
|
||||
if (typeof window.hitlToolsSplitToArray === 'function') {
|
||||
return window.hitlToolsSplitToArray(s);
|
||||
}
|
||||
return String(s || '').split(/[,\n\r]+/).map(function (x) { return x.trim(); }).filter(Boolean);
|
||||
}
|
||||
|
||||
function hitlPageToolsMergeDisplay(globalArr, sessionToolsArr) {
|
||||
if (typeof window.hitlMergeToolsForDisplay === 'function') {
|
||||
return window.hitlMergeToolsForDisplay(globalArr, sessionToolsArr);
|
||||
}
|
||||
const out = [];
|
||||
const seen = Object.create(null);
|
||||
function addOne(t) {
|
||||
const n = String(t || '').trim();
|
||||
if (!n) return;
|
||||
const k = n.toLowerCase();
|
||||
if (seen[k]) return;
|
||||
seen[k] = true;
|
||||
out.push(n);
|
||||
}
|
||||
if (Array.isArray(globalArr)) globalArr.forEach(addOne);
|
||||
if (Array.isArray(sessionToolsArr)) sessionToolsArr.forEach(addOne);
|
||||
return out.join(', ');
|
||||
}
|
||||
|
||||
function showHitlPageWhitelistFeedback(text, isError) {
|
||||
const el = document.getElementById('hitl-page-whitelist-feedback');
|
||||
if (!el) return;
|
||||
const msg = String(text || '').trim();
|
||||
if (!msg) {
|
||||
el.hidden = true;
|
||||
el.textContent = '';
|
||||
el.className = 'hitl-apply-feedback';
|
||||
return;
|
||||
}
|
||||
el.hidden = false;
|
||||
el.textContent = msg;
|
||||
el.className = 'hitl-apply-feedback' + (isError ? ' hitl-apply-feedback--error' : '');
|
||||
}
|
||||
|
||||
function syncHitlSidebarWhitelistDisplay(toolsStr) {
|
||||
const sidebarEl = document.getElementById('hitl-sensitive-tools');
|
||||
if (sidebarEl) sidebarEl.value = toolsStr;
|
||||
}
|
||||
|
||||
async function fetchHitlGlobalToolWhitelist() {
|
||||
const resp = await hitlApiFetch('/api/hitl/tool-whitelist', { credentials: 'same-origin' });
|
||||
if (!resp.ok) {
|
||||
throw new Error(await readHitlApiError(resp));
|
||||
}
|
||||
const data = await resp.json();
|
||||
const list = Array.isArray(data.toolWhitelist) ? data.toolWhitelist : (
|
||||
Array.isArray(data.hitlGlobalToolWhitelist) ? data.hitlGlobalToolWhitelist : []
|
||||
);
|
||||
if (typeof window !== 'undefined') {
|
||||
window.csaiHitlGlobalToolWhitelist = list;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
async function resolveHitlGlobalToolWhitelist() {
|
||||
try {
|
||||
return await fetchHitlGlobalToolWhitelist();
|
||||
} catch (e) {
|
||||
if (typeof window !== 'undefined' && Array.isArray(window.csaiHitlGlobalToolWhitelist)) {
|
||||
return window.csaiHitlGlobalToolWhitelist.slice();
|
||||
}
|
||||
try {
|
||||
const resp = await hitlApiFetch('/api/config', { credentials: 'same-origin' });
|
||||
if (resp.ok) {
|
||||
const cfg = await resp.json();
|
||||
const tw = cfg.hitl && cfg.hitl.tool_whitelist;
|
||||
if (Array.isArray(tw)) {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.csaiHitlGlobalToolWhitelist = tw.slice();
|
||||
}
|
||||
return tw.slice();
|
||||
}
|
||||
}
|
||||
} catch (e2) {
|
||||
console.warn('resolveHitlGlobalToolWhitelist fallback', e2);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function hitlPageWhitelistDisplayValue(globalArr, sessionArr) {
|
||||
return hitlPageToolsMergeDisplay(globalArr, sessionArr);
|
||||
}
|
||||
|
||||
async function refreshHitlPageWhitelist() {
|
||||
const ta = document.getElementById('hitl-page-sensitive-tools');
|
||||
if (!ta) return;
|
||||
const cached = typeof window !== 'undefined' && Array.isArray(window.csaiHitlGlobalToolWhitelist)
|
||||
? window.csaiHitlGlobalToolWhitelist
|
||||
: [];
|
||||
if (cached.length > 0) {
|
||||
ta.value = hitlPageWhitelistDisplayValue(cached, []);
|
||||
}
|
||||
try {
|
||||
const globalArr = await resolveHitlGlobalToolWhitelist();
|
||||
const cid = getCurrentConversationIdForHitl();
|
||||
let sessionArr = [];
|
||||
if (cid) {
|
||||
const cfg = typeof window.getHitlConfigForConversation === 'function'
|
||||
? window.getHitlConfigForConversation(cid)
|
||||
: null;
|
||||
sessionArr = hitlSensitiveToolsToArray(cfg || {});
|
||||
}
|
||||
ta.value = hitlPageWhitelistDisplayValue(globalArr, sessionArr);
|
||||
syncHitlSidebarWhitelistDisplay(ta.value);
|
||||
} catch (e) {
|
||||
console.warn('refreshHitlPageWhitelist', e);
|
||||
if (!ta.value.trim() && cached.length > 0) {
|
||||
ta.value = hitlPageWhitelistDisplayValue(cached, []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function putHitlGlobalToolWhitelist(toolWhitelist) {
|
||||
const list = Array.isArray(toolWhitelist) ? toolWhitelist : [];
|
||||
const resp = await hitlApiFetch('/api/hitl/tool-whitelist', {
|
||||
method: 'PUT',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ toolWhitelist: list })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(await readHitlApiError(resp));
|
||||
}
|
||||
const data = await resp.json();
|
||||
const out = Array.isArray(data.toolWhitelist) ? data.toolWhitelist : (
|
||||
Array.isArray(data.hitlGlobalToolWhitelist) ? data.hitlGlobalToolWhitelist : list
|
||||
);
|
||||
if (typeof window !== 'undefined') {
|
||||
window.csaiHitlGlobalToolWhitelist = out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function saveHitlPageWhitelist() {
|
||||
const ta = document.getElementById('hitl-page-sensitive-tools');
|
||||
const btn = document.getElementById('hitl-page-whitelist-save-btn');
|
||||
if (!ta) return;
|
||||
showHitlPageWhitelistFeedback('', false);
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const desired = hitlPageToolsSplit(ta.value);
|
||||
const globalArr = await putHitlGlobalToolWhitelist(desired);
|
||||
const displayStr = hitlPageToolsMergeDisplay(globalArr, []);
|
||||
ta.value = displayStr;
|
||||
syncHitlSidebarWhitelistDisplay(displayStr);
|
||||
|
||||
const cid = getCurrentConversationIdForHitl();
|
||||
if (cid) {
|
||||
const cfg = typeof window.getHitlConfigForConversation === 'function'
|
||||
? window.getHitlConfigForConversation(cid)
|
||||
: { mode: 'off', reviewer: 'human', sensitiveTools: '', timeoutSeconds: 0 };
|
||||
const nextCfg = Object.assign({}, cfg, { sensitiveTools: '' });
|
||||
if (typeof window.saveHitlConfigForConversation === 'function') {
|
||||
window.saveHitlConfigForConversation(cid, nextCfg);
|
||||
}
|
||||
if (typeof saveHitlConversationConfig === 'function') {
|
||||
await saveHitlConversationConfig(cid, nextCfg);
|
||||
}
|
||||
}
|
||||
|
||||
showHitlPageWhitelistFeedback(hitlT('whitelistSaved', 'Whitelist saved.'), false);
|
||||
} catch (e) {
|
||||
showHitlPageWhitelistFeedback(hitlT('whitelistSaveFailed', 'Failed to save whitelist') + ': ' + (e.message || e), true);
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHitlConversationConfig(conversationId, config) {
|
||||
if (!conversationId || !config) return false;
|
||||
const mode = hitlModeNormalize(config.mode || 'off');
|
||||
const enabled = typeof config.enabled === 'boolean' ? config.enabled : (mode !== 'off');
|
||||
const sensitiveTools = hitlSensitiveToolsToArray(config);
|
||||
const timeoutSeconds = normalizeHitlTimeoutSeconds(config.timeoutSeconds, 0);
|
||||
const reviewer = hitlReviewerNormalize(config.reviewer || 'human');
|
||||
const resp = await hitlApiFetch('/api/hitl/config', {
|
||||
method: 'PUT',
|
||||
credentials: 'same-origin',
|
||||
@@ -118,6 +446,7 @@ async function saveHitlConversationConfig(conversationId, config) {
|
||||
conversationId: conversationId,
|
||||
enabled: enabled,
|
||||
mode: mode,
|
||||
reviewer: reviewer,
|
||||
sensitiveTools: sensitiveTools,
|
||||
timeoutSeconds: timeoutSeconds
|
||||
})
|
||||
@@ -192,6 +521,7 @@ async function syncHitlConfigFromServer(conversationId) {
|
||||
const sessionOnlyStr = strip(globalWL, rawArr.join(', '));
|
||||
const normalizedCfg = Object.assign({}, merged, {
|
||||
mode: uiMode,
|
||||
reviewer: hitlReviewerNormalize(merged.reviewer || cfg.reviewer || 'human'),
|
||||
sensitiveTools: sessionOnlyStr
|
||||
});
|
||||
if (typeof window.saveHitlConfigForConversation === 'function') {
|
||||
@@ -288,24 +618,18 @@ async function followAgentRunAfterHitlDecision(conversationId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshHitlPending() {
|
||||
function renderHitlPendingList(items) {
|
||||
const container = document.getElementById('hitl-pending-list');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<div class="loading-spinner">' + escapeHtml(hitlT('loading', 'Loading...')) + '</div>';
|
||||
try {
|
||||
const resp = await hitlApiFetch('/api/hitl/pending', { credentials: 'same-origin' });
|
||||
if (!resp.ok) {
|
||||
throw new Error('request failed');
|
||||
}
|
||||
const data = await resp.json();
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
if (!items.length) {
|
||||
container.innerHTML = '<div class="empty-state">' + escapeHtml(hitlT('emptyState', 'No pending approvals')) + '</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = items.map(function (item) {
|
||||
const list = Array.isArray(items) ? items : [];
|
||||
if (!list.length) {
|
||||
container.innerHTML = '<div class="empty-state">' + escapeHtml(hitlT('emptyState', 'No pending approvals')) + '</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = list.map(function (item) {
|
||||
const payloadObj = hitlParsePayloadObject(item.payload || '');
|
||||
const payload = String(item.payload || '');
|
||||
const preview = payload.length > 280 ? (payload.slice(0, 280) + '...') : payload;
|
||||
const contextHtml = hitlRenderContextBlocks(payloadObj);
|
||||
const mode = String(item.mode || '').trim().toLowerCase();
|
||||
const allowEdit = mode === 'review_edit';
|
||||
var escId = escapeHtml(String(item.id || ''));
|
||||
@@ -321,7 +645,9 @@ async function refreshHitlPending() {
|
||||
'<button class="hitl-dismiss-btn" title="' + escapeHtml(hitlT('dismiss', 'Dismiss')) + '" onclick="dismissHitlItem(' + qId + ')">×</button>' +
|
||||
'</div>' +
|
||||
'<div class="hitl-pending-meta">' + escapeHtml(hitlT('conversationLabel', 'Conversation:')) + ' ' + escapeHtml(item.conversationId || '-') + '</div>' +
|
||||
'<pre class="hitl-pending-payload">' + escapeHtml(preview) + '</pre>' +
|
||||
contextHtml +
|
||||
hitlRenderExecutionResultBlock(payloadObj) +
|
||||
'<pre class="hitl-pending-payload">' + escapeHtml(payload) + '</pre>' +
|
||||
(allowEdit
|
||||
? ('<div class="hitl-input-help">' + escapeHtml(hitlT('reviewEditHelp', 'Review & edit mode: provide a JSON object to override tool arguments. Example: {"command":"ls -la"}')) + '</div>' +
|
||||
'<textarea id="hitl-edit-' + escId + '" class="hitl-edit-args" placeholder=\'{"command":"ls -la"}\'></textarea>')
|
||||
@@ -335,11 +661,53 @@ async function refreshHitlPending() {
|
||||
'</div>'
|
||||
);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function refreshHitlPending() {
|
||||
const container = document.getElementById('hitl-pending-list');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<div class="loading-spinner">' + escapeHtml(hitlT('loading', 'Loading...')) + '</div>';
|
||||
try {
|
||||
const q = document.getElementById('hitl-pending-search');
|
||||
const params = new URLSearchParams({
|
||||
page: String(hitlPendingPage),
|
||||
pageSize: String(hitlPendingPageSize)
|
||||
});
|
||||
if (q && q.value.trim()) params.set('q', q.value.trim());
|
||||
const resp = await hitlApiFetch('/api/hitl/pending?' + params.toString(), { credentials: 'same-origin' });
|
||||
if (!resp.ok) {
|
||||
throw new Error('request failed');
|
||||
}
|
||||
const data = await resp.json();
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
hitlPendingTotal = typeof data.total === 'number' ? data.total : items.length;
|
||||
const maxPage = Math.max(1, Math.ceil(hitlPendingTotal / hitlPendingPageSize));
|
||||
if (hitlPendingPage > maxPage) {
|
||||
hitlPendingPage = maxPage;
|
||||
await refreshHitlPending();
|
||||
return;
|
||||
}
|
||||
const badge = document.getElementById('hitl-pending-count');
|
||||
if (badge) {
|
||||
badge.textContent = String(hitlPendingTotal);
|
||||
badge.hidden = hitlPendingTotal <= 0;
|
||||
}
|
||||
hitlPendingCache = items;
|
||||
hitlPendingLoaded = true;
|
||||
renderHitlPendingList(items);
|
||||
renderHitlPendingPagination();
|
||||
} catch (e) {
|
||||
hitlPendingLoaded = false;
|
||||
container.innerHTML = '<div class="empty-state">' + escapeHtml(hitlT('loadFailed', 'Failed to load')) + '</div>';
|
||||
renderHitlPendingPagination();
|
||||
}
|
||||
}
|
||||
|
||||
function filterHitlPending() {
|
||||
hitlPendingPage = 1;
|
||||
refreshHitlPending();
|
||||
}
|
||||
|
||||
async function submitHitlDecision(interruptId, decision, conversationIdOpt) {
|
||||
const commentBox = document.getElementById('hitl-comment-' + interruptId);
|
||||
const comment = (commentBox && commentBox.value) ? commentBox.value.trim() : '';
|
||||
@@ -412,7 +780,475 @@ async function dismissHitlItem(interruptId, silent) {
|
||||
refreshHitlPending();
|
||||
}
|
||||
|
||||
let hitlActiveTab = 'pending';
|
||||
let hitlLogsPage = 1;
|
||||
let hitlLogsPageSize = 20;
|
||||
let hitlLogsTotal = 0;
|
||||
let hitlLogsCache = [];
|
||||
let hitlLogsLoaded = false;
|
||||
let hitlPendingPage = 1;
|
||||
let hitlPendingPageSize = 20;
|
||||
let hitlPendingTotal = 0;
|
||||
let hitlPendingCache = [];
|
||||
let hitlPendingLoaded = false;
|
||||
|
||||
function switchHitlPageTab(tab) {
|
||||
const tabs = ['pending', 'logs', 'strategy', 'whitelist'];
|
||||
hitlActiveTab = tabs.indexOf(tab) >= 0 ? tab : 'pending';
|
||||
const pendingTab = document.getElementById('hitl-tab-pending');
|
||||
const logsTab = document.getElementById('hitl-tab-logs');
|
||||
const strategyTab = document.getElementById('hitl-tab-strategy');
|
||||
const whitelistTab = document.getElementById('hitl-tab-whitelist');
|
||||
const pendingPanel = document.getElementById('hitl-panel-pending');
|
||||
const logsPanel = document.getElementById('hitl-panel-logs');
|
||||
const strategyPanel = document.getElementById('hitl-panel-strategy');
|
||||
const whitelistPanel = document.getElementById('hitl-panel-whitelist');
|
||||
if (pendingTab) {
|
||||
pendingTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'pending');
|
||||
pendingTab.setAttribute('aria-selected', hitlActiveTab === 'pending' ? 'true' : 'false');
|
||||
}
|
||||
if (logsTab) {
|
||||
logsTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'logs');
|
||||
logsTab.setAttribute('aria-selected', hitlActiveTab === 'logs' ? 'true' : 'false');
|
||||
}
|
||||
if (strategyTab) {
|
||||
strategyTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'strategy');
|
||||
strategyTab.setAttribute('aria-selected', hitlActiveTab === 'strategy' ? 'true' : 'false');
|
||||
}
|
||||
if (whitelistTab) {
|
||||
whitelistTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'whitelist');
|
||||
whitelistTab.setAttribute('aria-selected', hitlActiveTab === 'whitelist' ? 'true' : 'false');
|
||||
}
|
||||
if (pendingPanel) pendingPanel.hidden = hitlActiveTab !== 'pending';
|
||||
if (logsPanel) logsPanel.hidden = hitlActiveTab !== 'logs';
|
||||
if (strategyPanel) strategyPanel.hidden = hitlActiveTab !== 'strategy';
|
||||
if (whitelistPanel) whitelistPanel.hidden = hitlActiveTab !== 'whitelist';
|
||||
refreshHitlActivePanel();
|
||||
}
|
||||
|
||||
function refreshHitlPageReviewerBar() {
|
||||
const cid = getCurrentConversationIdForHitl();
|
||||
const cfg = typeof window.getHitlConfigForConversation === 'function'
|
||||
? window.getHitlConfigForConversation(cid)
|
||||
: null;
|
||||
if (cfg && typeof window.setHitlReviewerUI === 'function') {
|
||||
window.setHitlReviewerUI(cfg.reviewer);
|
||||
}
|
||||
if (typeof window.bindHitlReviewerToggleListeners === 'function') {
|
||||
window.bindHitlReviewerToggleListeners();
|
||||
}
|
||||
}
|
||||
|
||||
let hitlDefaultAuditPrompt = '';
|
||||
let hitlDefaultAuditPromptReviewEdit = '';
|
||||
let hitlStrategyMode = 'approval';
|
||||
|
||||
function switchHitlStrategyMode(mode) {
|
||||
hitlStrategyMode = mode === 'review_edit' ? 'review_edit' : 'approval';
|
||||
const approvalTab = document.getElementById('hitl-strategy-tab-approval');
|
||||
const reviewTab = document.getElementById('hitl-strategy-tab-review-edit');
|
||||
const approvalTa = document.getElementById('hitl-audit-agent-prompt');
|
||||
const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
|
||||
const hintApproval = document.getElementById('hitl-strategy-hint-approval');
|
||||
const hintReview = document.getElementById('hitl-strategy-hint-review-edit');
|
||||
if (approvalTab) {
|
||||
approvalTab.classList.toggle('hitl-strategy-subtab--active', hitlStrategyMode === 'approval');
|
||||
approvalTab.setAttribute('aria-selected', hitlStrategyMode === 'approval' ? 'true' : 'false');
|
||||
}
|
||||
if (reviewTab) {
|
||||
reviewTab.classList.toggle('hitl-strategy-subtab--active', hitlStrategyMode === 'review_edit');
|
||||
reviewTab.setAttribute('aria-selected', hitlStrategyMode === 'review_edit' ? 'true' : 'false');
|
||||
}
|
||||
if (approvalTa) approvalTa.hidden = hitlStrategyMode !== 'approval';
|
||||
if (reviewTa) reviewTa.hidden = hitlStrategyMode !== 'review_edit';
|
||||
if (hintApproval) hintApproval.hidden = hitlStrategyMode !== 'approval';
|
||||
if (hintReview) hintReview.hidden = hitlStrategyMode !== 'review_edit';
|
||||
}
|
||||
|
||||
function showHitlStrategyFeedback(text, isError) {
|
||||
const el = document.getElementById('hitl-strategy-feedback');
|
||||
if (!el) return;
|
||||
const msg = String(text || '').trim();
|
||||
if (!msg) {
|
||||
el.hidden = true;
|
||||
el.textContent = '';
|
||||
el.className = 'hitl-apply-feedback';
|
||||
return;
|
||||
}
|
||||
el.hidden = false;
|
||||
el.textContent = msg;
|
||||
el.className = 'hitl-apply-feedback' + (isError ? ' hitl-apply-feedback--error' : '');
|
||||
}
|
||||
|
||||
async function refreshHitlAuditStrategy() {
|
||||
const approvalTa = document.getElementById('hitl-audit-agent-prompt');
|
||||
const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
|
||||
if (!approvalTa) return;
|
||||
try {
|
||||
const resp = await hitlApiFetch('/api/hitl/audit-strategy', { credentials: 'same-origin' });
|
||||
if (!resp.ok) return;
|
||||
const data = await resp.json();
|
||||
hitlDefaultAuditPrompt = typeof data.defaultAuditAgentPrompt === 'string' ? data.defaultAuditAgentPrompt : '';
|
||||
hitlDefaultAuditPromptReviewEdit = typeof data.defaultAuditAgentPromptReviewEdit === 'string' ? data.defaultAuditAgentPromptReviewEdit : '';
|
||||
approvalTa.value = typeof data.auditAgentPrompt === 'string' ? data.auditAgentPrompt : hitlDefaultAuditPrompt;
|
||||
if (reviewTa) {
|
||||
reviewTa.value = typeof data.auditAgentPromptReviewEdit === 'string' ? data.auditAgentPromptReviewEdit : hitlDefaultAuditPromptReviewEdit;
|
||||
}
|
||||
switchHitlStrategyMode(hitlStrategyMode);
|
||||
} catch (e) {
|
||||
console.warn('refreshHitlAuditStrategy', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHitlAuditStrategy() {
|
||||
const approvalTa = document.getElementById('hitl-audit-agent-prompt');
|
||||
const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
|
||||
const btn = document.getElementById('hitl-strategy-save-btn');
|
||||
if (!approvalTa) return;
|
||||
showHitlStrategyFeedback('', false);
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const resp = await hitlApiFetch('/api/hitl/audit-strategy', {
|
||||
method: 'PUT',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
auditAgentPrompt: String(approvalTa.value || ''),
|
||||
auditAgentPromptReviewEdit: reviewTa ? String(reviewTa.value || '') : ''
|
||||
})
|
||||
});
|
||||
if (!resp.ok) throw new Error(await readHitlApiError(resp));
|
||||
const data = await resp.json();
|
||||
if (typeof data.auditAgentPrompt === 'string') approvalTa.value = data.auditAgentPrompt;
|
||||
if (reviewTa && typeof data.auditAgentPromptReviewEdit === 'string') reviewTa.value = data.auditAgentPromptReviewEdit;
|
||||
showHitlStrategyFeedback(hitlT('strategySaved', 'Audit strategy saved.'), false);
|
||||
} catch (e) {
|
||||
showHitlStrategyFeedback(hitlT('strategySaveFailed', 'Failed to save') + ': ' + (e.message || e), true);
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetHitlAuditStrategy() {
|
||||
const approvalTa = document.getElementById('hitl-audit-agent-prompt');
|
||||
const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
|
||||
if (hitlStrategyMode === 'review_edit' && reviewTa) {
|
||||
reviewTa.value = hitlDefaultAuditPromptReviewEdit || reviewTa.value;
|
||||
} else if (approvalTa) {
|
||||
approvalTa.value = hitlDefaultAuditPrompt || approvalTa.value;
|
||||
}
|
||||
showHitlStrategyFeedback('', false);
|
||||
}
|
||||
|
||||
function refreshHitlActivePanel() {
|
||||
refreshHitlPageReviewerBar();
|
||||
if (hitlActiveTab === 'logs') {
|
||||
refreshHitlLogs();
|
||||
} else if (hitlActiveTab === 'strategy') {
|
||||
refreshHitlAuditStrategy();
|
||||
} else if (hitlActiveTab === 'whitelist') {
|
||||
refreshHitlPageWhitelist();
|
||||
} else {
|
||||
refreshHitlPending();
|
||||
}
|
||||
}
|
||||
|
||||
function hitlDecidedByLabel(v) {
|
||||
const key = 'reviewer' + String(v || 'human').replace(/_([a-z])/g, function (_, c) { return c.toUpperCase(); }).replace(/^./, function (c) { return c.toUpperCase(); });
|
||||
const map = {
|
||||
human: hitlT('reviewerHuman', 'Human'),
|
||||
audit_agent: hitlT('reviewerAgent', 'Audit Agent'),
|
||||
system: hitlT('reviewerSystem', 'System'),
|
||||
manual: hitlT('reviewerManual', 'Manual')
|
||||
};
|
||||
return map[v] || v || '-';
|
||||
}
|
||||
|
||||
function hitlFormatTime(v) {
|
||||
if (!v) return '-';
|
||||
try {
|
||||
const d = new Date(v);
|
||||
if (Number.isNaN(d.getTime())) return String(v);
|
||||
return d.toLocaleString(hitlLocale(), {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
} catch (e) {
|
||||
return String(v);
|
||||
}
|
||||
}
|
||||
|
||||
function renderHitlLogsTable(items) {
|
||||
const wrap = document.getElementById('hitl-logs-table-wrap');
|
||||
if (!wrap) return;
|
||||
const list = Array.isArray(items) ? items : [];
|
||||
if (!list.length) {
|
||||
wrap.innerHTML =
|
||||
'<div class="empty-state">' +
|
||||
'<p>' + escapeHtml(hitlT('logsEmpty', 'No audit logs')) + '</p>' +
|
||||
'<p class="hitl-logs-empty-hint">' + escapeHtml(hitlT('logsEmptyHint', 'Records appear here after HITL decisions.')) + '</p>' +
|
||||
'</div>';
|
||||
renderHitlLogsPagination();
|
||||
return;
|
||||
}
|
||||
const rows = list.map(function (item) {
|
||||
const id = escapeHtml(String(item.id || ''));
|
||||
const qId = JSON.stringify(String(item.id || '')).replace(/"/g, '"');
|
||||
const payloadObj = hitlParsePayloadObject(item.payload || '');
|
||||
const decision = String(item.decision || '-');
|
||||
const decisionCls = decision === 'approve' ? 'hitl-decision--approve' : (decision === 'reject' ? 'hitl-decision--reject' : '');
|
||||
const summary = hitlPayloadSummary(payloadObj);
|
||||
return (
|
||||
'<tr>' +
|
||||
'<td class="hitl-logs-cell-mono">' + id + '</td>' +
|
||||
'<td>' + escapeHtml(String(item.toolName || '-')) + '</td>' +
|
||||
'<td class="hitl-logs-cell-mono">' + escapeHtml(String(item.conversationId || '-')) + '</td>' +
|
||||
'<td><span class="hitl-decision-tag ' + decisionCls + '">' + escapeHtml(hitlDecisionLabel(decision)) + '</span></td>' +
|
||||
'<td>' + escapeHtml(hitlDecidedByLabel(item.decidedBy)) + '</td>' +
|
||||
'<td class="hitl-logs-summary">' + escapeHtml(summary) + '</td>' +
|
||||
'<td>' + escapeHtml(hitlFormatTime(item.decidedAt || item.createdAt)) + '</td>' +
|
||||
'<td class="hitl-logs-actions">' +
|
||||
'<button type="button" class="btn-link" onclick="openHitlLogModal(' + qId + ')">' + escapeHtml(hitlT('viewDetail', 'Detail')) + '</button>' +
|
||||
'</td>' +
|
||||
'</tr>'
|
||||
);
|
||||
}).join('');
|
||||
wrap.innerHTML =
|
||||
'<table class="hitl-logs-table">' +
|
||||
'<thead><tr>' +
|
||||
'<th>' + escapeHtml(hitlT('colId', 'ID')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colTool', 'Tool')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colConversation', 'Conversation')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colDecision', 'Decision')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colDecidedBy', 'Reviewer')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colContext', 'Context')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colTime', 'Time')) + '</th>' +
|
||||
'<th>' + escapeHtml(hitlT('colActions', 'Actions')) + '</th>' +
|
||||
'</tr></thead><tbody>' + rows + '</tbody></table>';
|
||||
renderHitlLogsPagination();
|
||||
}
|
||||
|
||||
async function refreshHitlLogs() {
|
||||
const wrap = document.getElementById('hitl-logs-table-wrap');
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = '<div class="loading-spinner">' + escapeHtml(hitlT('loading', 'Loading...')) + '</div>';
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: String(hitlLogsPage),
|
||||
pageSize: String(hitlLogsPageSize)
|
||||
});
|
||||
const qEl = document.getElementById('hitl-logs-search');
|
||||
const decEl = document.getElementById('hitl-logs-decision-filter');
|
||||
const byEl = document.getElementById('hitl-logs-decidedby-filter');
|
||||
if (qEl && qEl.value.trim()) params.set('q', qEl.value.trim());
|
||||
if (decEl && decEl.value && decEl.value !== 'all') params.set('decision', decEl.value);
|
||||
if (byEl && byEl.value && byEl.value !== 'all') params.set('decidedBy', byEl.value);
|
||||
const resp = await hitlApiFetch('/api/hitl/logs?' + params.toString(), { credentials: 'same-origin' });
|
||||
if (!resp.ok) throw new Error('request failed');
|
||||
const data = await resp.json();
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
hitlLogsTotal = typeof data.total === 'number' ? data.total : items.length;
|
||||
const maxPage = Math.max(1, Math.ceil(hitlLogsTotal / hitlLogsPageSize));
|
||||
if (hitlLogsPage > maxPage) {
|
||||
hitlLogsPage = maxPage;
|
||||
await refreshHitlLogs();
|
||||
return;
|
||||
}
|
||||
hitlLogsCache = items;
|
||||
hitlLogsLoaded = true;
|
||||
renderHitlLogsTable(items);
|
||||
} catch (e) {
|
||||
hitlLogsLoaded = false;
|
||||
wrap.innerHTML = '<div class="empty-state">' + escapeHtml(hitlT('loadFailed', 'Failed to load')) + '</div>';
|
||||
renderHitlLogsPagination();
|
||||
}
|
||||
}
|
||||
|
||||
function filterHitlLogs() {
|
||||
hitlLogsPage = 1;
|
||||
refreshHitlLogs();
|
||||
}
|
||||
|
||||
function refreshHitlLogsI18n() {
|
||||
if (!document.getElementById('hitl-logs-table-wrap') || !hitlLogsLoaded) return;
|
||||
renderHitlLogsTable(hitlLogsCache);
|
||||
}
|
||||
|
||||
function refreshHitlPendingI18n() {
|
||||
if (!document.getElementById('hitl-pending-list') || !hitlPendingLoaded) return;
|
||||
renderHitlPendingList(hitlPendingCache);
|
||||
}
|
||||
|
||||
function refreshHitlI18n() {
|
||||
refreshHitlLogsI18n();
|
||||
refreshHitlPendingI18n();
|
||||
renderHitlLogsPagination();
|
||||
renderHitlPendingPagination();
|
||||
}
|
||||
|
||||
function renderHitlLogsPagination() {
|
||||
renderHitlPagination('hitl-logs-pagination', {
|
||||
total: hitlLogsTotal,
|
||||
page: hitlLogsPage,
|
||||
pageSize: hitlLogsPageSize
|
||||
}, 'hitlLogsGoPage', 'onHitlLogsPageSizeChange', 'hitl-logs-page-size');
|
||||
}
|
||||
|
||||
function renderHitlPendingPagination() {
|
||||
renderHitlPagination('hitl-pending-pagination', {
|
||||
total: hitlPendingTotal,
|
||||
page: hitlPendingPage,
|
||||
pageSize: hitlPendingPageSize
|
||||
}, 'hitlPendingGoPage', 'onHitlPendingPageSizeChange', 'hitl-pending-page-size');
|
||||
}
|
||||
|
||||
function onHitlLogsPageSizeChange() {
|
||||
const sel = document.getElementById('hitl-logs-page-size');
|
||||
if (!sel) return;
|
||||
const n = parseInt(sel.value, 10);
|
||||
if (HITL_PAGE_SIZE_OPTIONS.indexOf(n) < 0) return;
|
||||
hitlLogsPageSize = n;
|
||||
try {
|
||||
localStorage.setItem(HITL_LOGS_PAGE_SIZE_KEY, String(n));
|
||||
} catch (e) { /* ignore */ }
|
||||
hitlLogsPage = 1;
|
||||
refreshHitlLogs();
|
||||
}
|
||||
|
||||
function onHitlPendingPageSizeChange() {
|
||||
const sel = document.getElementById('hitl-pending-page-size');
|
||||
if (!sel) return;
|
||||
const n = parseInt(sel.value, 10);
|
||||
if (HITL_PAGE_SIZE_OPTIONS.indexOf(n) < 0) return;
|
||||
hitlPendingPageSize = n;
|
||||
try {
|
||||
localStorage.setItem(HITL_PENDING_PAGE_SIZE_KEY, String(n));
|
||||
} catch (e) { /* ignore */ }
|
||||
hitlPendingPage = 1;
|
||||
refreshHitlPending();
|
||||
}
|
||||
|
||||
function hitlLogsGoPage(page) {
|
||||
const totalPages = Math.max(1, Math.ceil((hitlLogsTotal || 0) / (hitlLogsPageSize || 20)));
|
||||
if (page < 1 || page > totalPages) return;
|
||||
hitlLogsPage = page;
|
||||
refreshHitlLogs();
|
||||
}
|
||||
|
||||
function hitlPendingGoPage(page) {
|
||||
const totalPages = Math.max(1, Math.ceil((hitlPendingTotal || 0) / (hitlPendingPageSize || 20)));
|
||||
if (page < 1 || page > totalPages) return;
|
||||
hitlPendingPage = page;
|
||||
refreshHitlPending();
|
||||
}
|
||||
|
||||
function hitlDecisionLabel(decision) {
|
||||
const d = String(decision || '').toLowerCase();
|
||||
if (d === 'approve') return hitlT('decisionApprove', 'Approve');
|
||||
if (d === 'reject') return hitlT('decisionReject', 'Reject');
|
||||
return decision || '—';
|
||||
}
|
||||
|
||||
function hitlFormatPayloadForDisplay(raw) {
|
||||
if (!raw) return '';
|
||||
if (typeof raw === 'object') {
|
||||
try {
|
||||
return JSON.stringify(raw, null, 2);
|
||||
} catch (e) {
|
||||
return String(raw);
|
||||
}
|
||||
}
|
||||
const s = String(raw).trim();
|
||||
if (!s) return '';
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(s), null, 2);
|
||||
} catch (e) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
async function openHitlLogModal(idOpt) {
|
||||
const modal = document.getElementById('hitl-log-modal');
|
||||
if (!modal || !idOpt) return;
|
||||
const resp = await hitlApiFetch('/api/hitl/logs/' + encodeURIComponent(idOpt), { credentials: 'same-origin' });
|
||||
if (!resp.ok) {
|
||||
alert(hitlT('loadFailed', 'Failed to load'));
|
||||
return;
|
||||
}
|
||||
const item = await resp.json();
|
||||
const payloadObj = hitlParsePayloadObject(item.payload || '');
|
||||
const idEl = document.getElementById('hitl-log-detail-id');
|
||||
const toolEl = document.getElementById('hitl-log-detail-tool');
|
||||
const convEl = document.getElementById('hitl-log-detail-conversation');
|
||||
const decisionEl = document.getElementById('hitl-log-detail-decision');
|
||||
const decidedByEl = document.getElementById('hitl-log-detail-decided-by');
|
||||
const timeEl = document.getElementById('hitl-log-detail-time');
|
||||
const commentRow = document.getElementById('hitl-log-detail-comment-row');
|
||||
const commentEl = document.getElementById('hitl-log-detail-comment');
|
||||
const payloadWrap = document.getElementById('hitl-log-detail-payload-wrap');
|
||||
const payloadEl = document.getElementById('hitl-log-detail-payload');
|
||||
if (idEl) idEl.textContent = item.id || '—';
|
||||
if (toolEl) toolEl.textContent = item.toolName || '—';
|
||||
if (convEl) convEl.textContent = item.conversationId || '—';
|
||||
if (decisionEl) {
|
||||
const decision = String(item.decision || '');
|
||||
const cls = decision === 'approve' ? 'hitl-decision--approve' : (decision === 'reject' ? 'hitl-decision--reject' : '');
|
||||
decisionEl.innerHTML = '<span class="hitl-decision-tag ' + cls + '">' + escapeHtml(hitlDecisionLabel(decision)) + '</span>';
|
||||
}
|
||||
if (decidedByEl) decidedByEl.textContent = hitlDecidedByLabel(item.decidedBy);
|
||||
if (timeEl) timeEl.textContent = hitlFormatTime(item.decidedAt || item.createdAt);
|
||||
const comment = String(item.comment || '').trim();
|
||||
if (commentRow && commentEl) {
|
||||
if (comment) {
|
||||
commentEl.textContent = comment;
|
||||
commentRow.hidden = false;
|
||||
} else {
|
||||
commentEl.textContent = '';
|
||||
commentRow.hidden = true;
|
||||
}
|
||||
}
|
||||
hitlFillLogModalReadonlySections(payloadObj);
|
||||
const payloadText = hitlFormatPayloadForDisplay(item.payload || '');
|
||||
if (payloadWrap && payloadEl) {
|
||||
if (payloadText) {
|
||||
payloadEl.textContent = payloadText;
|
||||
payloadWrap.hidden = false;
|
||||
} else {
|
||||
payloadEl.textContent = '';
|
||||
payloadWrap.hidden = true;
|
||||
}
|
||||
}
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeHitlLogModal() {
|
||||
const modal = document.getElementById('hitl-log-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
window.saveHitlPageWhitelist = saveHitlPageWhitelist;
|
||||
window.refreshHitlPageWhitelist = refreshHitlPageWhitelist;
|
||||
window.refreshHitlPending = refreshHitlPending;
|
||||
window.refreshHitlLogs = refreshHitlLogs;
|
||||
window.refreshHitlActivePanel = refreshHitlActivePanel;
|
||||
window.switchHitlPageTab = switchHitlPageTab;
|
||||
window.switchHitlStrategyMode = switchHitlStrategyMode;
|
||||
window.resetHitlAuditStrategy = resetHitlAuditStrategy;
|
||||
window.saveHitlAuditStrategy = saveHitlAuditStrategy;
|
||||
window.refreshHitlAuditStrategy = refreshHitlAuditStrategy;
|
||||
window.openHitlLogModal = openHitlLogModal;
|
||||
window.closeHitlLogModal = closeHitlLogModal;
|
||||
window.hitlLogsGoPage = hitlLogsGoPage;
|
||||
window.hitlPendingGoPage = hitlPendingGoPage;
|
||||
window.filterHitlLogs = filterHitlLogs;
|
||||
window.filterHitlPending = filterHitlPending;
|
||||
window.onHitlLogsPageSizeChange = onHitlLogsPageSizeChange;
|
||||
window.onHitlPendingPageSizeChange = onHitlPendingPageSizeChange;
|
||||
window.submitHitlDecision = submitHitlDecision;
|
||||
window.submitHitlDecisionWithPayload = submitHitlDecisionWithPayload;
|
||||
window.dismissHitlItem = dismissHitlItem;
|
||||
@@ -420,7 +1256,7 @@ window.followAgentRunAfterHitlDecision = followAgentRunAfterHitlDecision;
|
||||
|
||||
window.addEventListener('hitl-interrupt', function () {
|
||||
if (typeof window.currentPage === 'function' && window.currentPage() === 'hitl') {
|
||||
refreshHitlPending();
|
||||
refreshHitlActivePanel();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -428,9 +1264,22 @@ window.addEventListener('pageshow', function () {
|
||||
setTimeout(reconcileHitlUiState, 0);
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initHitlPageSizeFromStorage(HITL_LOGS_PAGE_SIZE_KEY, 20, function (n) { hitlLogsPageSize = n; });
|
||||
initHitlPageSizeFromStorage(HITL_PENDING_PAGE_SIZE_KEY, 20, function (n) { hitlPendingPageSize = n; });
|
||||
if (typeof window.bindHitlReviewerToggleListeners === 'function') {
|
||||
window.bindHitlReviewerToggleListeners();
|
||||
}
|
||||
setTimeout(reconcileHitlUiState, 0);
|
||||
});
|
||||
|
||||
document.addEventListener('languagechange', function () {
|
||||
try {
|
||||
refreshHitlI18n();
|
||||
} catch (e) {
|
||||
console.warn('languagechange hitl refresh failed', e);
|
||||
}
|
||||
});
|
||||
|
||||
// 由 applyHitlSidebarConfig 调用,将侧栏配置同步到后端
|
||||
window.syncHitlConfigToServerByCurrentConversation = syncHitlConfigToServerByCurrentConversation;
|
||||
window.saveHitlConversationConfig = saveHitlConversationConfig;
|
||||
|
||||
@@ -335,7 +335,9 @@ async function initPage(pageId) {
|
||||
}
|
||||
break;
|
||||
case 'hitl':
|
||||
if (typeof refreshHitlPending === 'function') {
|
||||
if (typeof refreshHitlActivePanel === 'function') {
|
||||
refreshHitlActivePanel();
|
||||
} else if (typeof refreshHitlPending === 'function') {
|
||||
refreshHitlPending();
|
||||
}
|
||||
break;
|
||||
|
||||
+168
-3
@@ -942,6 +942,19 @@
|
||||
<option value="review_edit" data-i18n="chat.hitlModeReviewEdit">审查编辑</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="hitl-config-field" id="hitl-reviewer-field">
|
||||
<label class="hitl-config-label" data-i18n="chat.hitlReviewerLabel">审批方</label>
|
||||
<div class="hitl-reviewer-toggle" role="group" aria-label="Reviewer">
|
||||
<button type="button" class="hitl-reviewer-toggle-btn is-active" data-reviewer="human" aria-pressed="true">
|
||||
<span data-i18n="chat.hitlReviewerHuman">人工审批</span>
|
||||
</button>
|
||||
<button type="button" class="hitl-reviewer-toggle-btn" data-reviewer="audit_agent" aria-pressed="false">
|
||||
<span data-i18n="chat.hitlReviewerAgent">审计 Agent</span>
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" id="hitl-reviewer-select" value="human" />
|
||||
<p class="hitl-config-hint" data-i18n="chat.hitlReviewerHint">可在人工与审计 Agent 之间随时切换;规则与白名单不变。人机协同为「关闭」时也可预先选择。</p>
|
||||
</div>
|
||||
<div class="hitl-config-field hitl-config-field--tools">
|
||||
<label class="hitl-config-label" for="hitl-sensitive-tools" data-i18n="chat.hitlWhitelistTools">白名单工具(免审批,逗号分隔)</label>
|
||||
<textarea id="hitl-sensitive-tools" class="hitl-config-textarea" rows="3" spellcheck="false" autocomplete="off" data-i18n="chat.hitlWhitelistPlaceholder" data-i18n-attr="placeholder" placeholder=""></textarea>
|
||||
@@ -1160,13 +1173,165 @@
|
||||
<div class="page-header">
|
||||
<h2 data-i18n="hitl.pageTitle">人机协同审批</h2>
|
||||
<div class="page-header-actions">
|
||||
<button class="btn-secondary" onclick="refreshHitlPending()" data-i18n="common.refresh">刷新</button>
|
||||
<button type="button" class="btn-secondary" id="hitl-refresh-btn" onclick="refreshHitlActivePanel()" data-i18n="common.refresh">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<div class="settings-section">
|
||||
<h3 data-i18n="hitl.pendingTitle">待处理审批</h3>
|
||||
<div class="hitl-page-reviewer-bar" id="hitl-page-reviewer-bar">
|
||||
<div class="hitl-page-reviewer-main">
|
||||
<span class="hitl-page-reviewer-label" data-i18n="hitl.pageReviewerLabel">当前审批方</span>
|
||||
<div class="hitl-reviewer-toggle hitl-reviewer-toggle--page" role="group" aria-label="Reviewer">
|
||||
<button type="button" class="hitl-reviewer-toggle-btn is-active" data-reviewer="human" aria-pressed="true">
|
||||
<span data-i18n="chat.hitlReviewerHuman">人工审批</span>
|
||||
</button>
|
||||
<button type="button" class="hitl-reviewer-toggle-btn" data-reviewer="audit_agent" aria-pressed="false">
|
||||
<span data-i18n="chat.hitlReviewerAgent">审计 Agent</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hitl-page-reviewer-hint" data-i18n="hitl.pageReviewerHint">作用于当前选中会话;未选会话时保存到本机,新建会话时沿用。切换后立即生效。</p>
|
||||
</div>
|
||||
<div class="hitl-page-tabs" role="tablist">
|
||||
<button type="button" class="hitl-page-tab hitl-page-tab--active" id="hitl-tab-pending" role="tab" aria-selected="true" onclick="switchHitlPageTab('pending')">
|
||||
<span data-i18n="hitl.tabPending">待审计</span>
|
||||
<span class="hitl-tab-badge" id="hitl-pending-count" hidden>0</span>
|
||||
</button>
|
||||
<button type="button" class="hitl-page-tab" id="hitl-tab-logs" role="tab" aria-selected="false" onclick="switchHitlPageTab('logs')">
|
||||
<span data-i18n="hitl.tabLogs">审计日志</span>
|
||||
</button>
|
||||
<button type="button" class="hitl-page-tab" id="hitl-tab-strategy" role="tab" aria-selected="false" onclick="switchHitlPageTab('strategy')">
|
||||
<span data-i18n="hitl.tabStrategy">审计策略</span>
|
||||
</button>
|
||||
<button type="button" class="hitl-page-tab" id="hitl-tab-whitelist" role="tab" aria-selected="false" onclick="switchHitlPageTab('whitelist')">
|
||||
<span data-i18n="hitl.tabWhitelist">工具白名单</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="hitl-panel-pending" class="hitl-page-panel">
|
||||
<div class="hitl-filters">
|
||||
<label>
|
||||
<span data-i18n="hitl.searchLabel">搜索</span>
|
||||
<input type="search" id="hitl-pending-search" class="hitl-filter-input" data-i18n="hitl.searchPlaceholder" data-i18n-attr="placeholder" placeholder="" onkeydown="if(event.key==='Enter')filterHitlPending()" />
|
||||
</label>
|
||||
<button type="button" class="btn-secondary" onclick="filterHitlPending()" data-i18n="hitl.searchApply">搜索</button>
|
||||
</div>
|
||||
<div id="hitl-pending-list" class="hitl-pending-list"></div>
|
||||
<div id="hitl-pending-pagination" class="hitl-pending-pagination"></div>
|
||||
</div>
|
||||
|
||||
<div id="hitl-panel-logs" class="hitl-page-panel" hidden>
|
||||
<div class="hitl-filters hitl-filters--logs">
|
||||
<label>
|
||||
<span data-i18n="hitl.searchLabel">搜索</span>
|
||||
<input type="search" id="hitl-logs-search" class="hitl-filter-input" data-i18n="hitl.searchPlaceholder" data-i18n-attr="placeholder" placeholder="" onkeydown="if(event.key==='Enter')filterHitlLogs()" />
|
||||
</label>
|
||||
<label>
|
||||
<span data-i18n="hitl.filterDecision">决策</span>
|
||||
<select id="hitl-logs-decision-filter" class="hitl-filter-select" onchange="filterHitlLogs()">
|
||||
<option value="all" data-i18n="hitl.filterAll">全部</option>
|
||||
<option value="approve" data-i18n="hitl.decisionApprove">通过</option>
|
||||
<option value="reject" data-i18n="hitl.decisionReject">拒绝</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span data-i18n="hitl.filterDecidedBy">审批方</span>
|
||||
<select id="hitl-logs-decidedby-filter" class="hitl-filter-select" onchange="filterHitlLogs()">
|
||||
<option value="all" data-i18n="hitl.filterAll">全部</option>
|
||||
<option value="human" data-i18n="hitl.reviewerHuman">人工</option>
|
||||
<option value="audit_agent" data-i18n="hitl.reviewerAgent">审计 Agent</option>
|
||||
<option value="system" data-i18n="hitl.reviewerSystem">系统</option>
|
||||
<option value="manual" data-i18n="hitl.reviewerManual">手动录入</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="btn-secondary" onclick="filterHitlLogs()" data-i18n="hitl.searchApply">搜索</button>
|
||||
</div>
|
||||
<div id="hitl-logs-table-wrap" class="hitl-logs-table-wrap">
|
||||
<div class="loading-spinner" data-i18n="hitl.loading">加载中...</div>
|
||||
</div>
|
||||
<div id="hitl-logs-pagination" class="hitl-logs-pagination"></div>
|
||||
</div>
|
||||
|
||||
<div id="hitl-panel-strategy" class="hitl-page-panel" hidden>
|
||||
<div class="hitl-page-strategy-bar" id="hitl-page-strategy-bar">
|
||||
<div class="hitl-page-strategy-header">
|
||||
<span class="hitl-page-strategy-label" data-i18n="hitl.strategyLabel">审计策略</span>
|
||||
<div class="hitl-page-strategy-actions">
|
||||
<button type="button" class="btn-link" id="hitl-strategy-reset-btn" onclick="resetHitlAuditStrategy()" data-i18n="hitl.strategyReset">恢复默认</button>
|
||||
<button type="button" class="btn-secondary" id="hitl-strategy-save-btn" onclick="saveHitlAuditStrategy()" data-i18n="common.save">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hitl-strategy-subtabs" role="tablist" aria-label="Audit strategy mode">
|
||||
<button type="button" class="hitl-strategy-subtab hitl-strategy-subtab--active" id="hitl-strategy-tab-approval" role="tab" aria-selected="true" data-strategy-mode="approval" onclick="switchHitlStrategyMode('approval')" data-i18n="hitl.strategyTabApproval">审批模式</button>
|
||||
<button type="button" class="hitl-strategy-subtab" id="hitl-strategy-tab-review-edit" role="tab" aria-selected="false" data-strategy-mode="review_edit" onclick="switchHitlStrategyMode('review_edit')" data-i18n="hitl.strategyTabReviewEdit">审查编辑模式</button>
|
||||
</div>
|
||||
<p class="hitl-page-strategy-hint" id="hitl-strategy-hint-approval" data-i18n="hitl.strategyHintApproval">白名单内工具免审批;审批模式下审计 Agent 仅裁决通过/拒绝。</p>
|
||||
<p class="hitl-page-strategy-hint" id="hitl-strategy-hint-review-edit" hidden data-i18n="hitl.strategyHintReviewEdit">审查编辑模式下审计 Agent 可通过 editedArguments 收窄参数后放行;无法安全改参时应拒绝。</p>
|
||||
<textarea id="hitl-audit-agent-prompt" class="hitl-strategy-textarea" rows="14" spellcheck="false" autocomplete="off"></textarea>
|
||||
<textarea id="hitl-audit-agent-prompt-review-edit" class="hitl-strategy-textarea" rows="14" spellcheck="false" autocomplete="off" hidden></textarea>
|
||||
<div id="hitl-strategy-feedback" class="hitl-apply-feedback" role="status" aria-live="polite" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="hitl-panel-whitelist" class="hitl-page-panel" hidden>
|
||||
<div class="hitl-page-whitelist-bar" id="hitl-page-whitelist-bar">
|
||||
<div class="hitl-page-whitelist-header">
|
||||
<span class="hitl-page-whitelist-label" data-i18n="hitl.whitelistLabel">免审批工具白名单</span>
|
||||
<button type="button" class="btn-secondary" id="hitl-page-whitelist-save-btn" onclick="saveHitlPageWhitelist()" data-i18n="common.save">保存</button>
|
||||
</div>
|
||||
<p class="hitl-page-whitelist-hint" data-i18n="hitl.whitelistHint">每行一个或逗号分隔;保存后写入 config.yaml 全局白名单并立即生效(与聊天侧栏同步展示)。</p>
|
||||
<textarea id="hitl-page-sensitive-tools" class="hitl-page-whitelist-textarea" rows="6" spellcheck="false" autocomplete="off" data-i18n="chat.hitlWhitelistPlaceholder" data-i18n-attr="placeholder" placeholder=""></textarea>
|
||||
<div id="hitl-page-whitelist-feedback" class="hitl-apply-feedback" role="status" aria-live="polite" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="hitl-log-modal" class="modal" style="display:none" role="dialog" aria-modal="true" aria-labelledby="hitl-log-modal-title">
|
||||
<div class="modal-content hitl-log-modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 id="hitl-log-modal-title" data-i18n="hitl.logModalView">审计日志详情</h3>
|
||||
<button type="button" class="modal-close" onclick="closeHitlLogModal()" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="hitl-log-context-readonly" class="hitl-log-readonly-section" hidden></div>
|
||||
<div id="hitl-log-execution-readonly" class="hitl-log-readonly-section" hidden></div>
|
||||
<dl class="hitl-log-detail-meta">
|
||||
<div class="hitl-log-detail-row">
|
||||
<dt data-i18n="hitl.colId">ID</dt>
|
||||
<dd id="hitl-log-detail-id" class="hitl-log-detail-mono">—</dd>
|
||||
</div>
|
||||
<div class="hitl-log-detail-row">
|
||||
<dt data-i18n="hitl.colTool">工具</dt>
|
||||
<dd id="hitl-log-detail-tool">—</dd>
|
||||
</div>
|
||||
<div class="hitl-log-detail-row">
|
||||
<dt data-i18n="hitl.colConversation">会话</dt>
|
||||
<dd id="hitl-log-detail-conversation" class="hitl-log-detail-mono">—</dd>
|
||||
</div>
|
||||
<div class="hitl-log-detail-row">
|
||||
<dt data-i18n="hitl.colDecision">决策</dt>
|
||||
<dd id="hitl-log-detail-decision">—</dd>
|
||||
</div>
|
||||
<div class="hitl-log-detail-row">
|
||||
<dt data-i18n="hitl.colDecidedBy">审批方</dt>
|
||||
<dd id="hitl-log-detail-decided-by">—</dd>
|
||||
</div>
|
||||
<div class="hitl-log-detail-row">
|
||||
<dt data-i18n="hitl.colTime">时间</dt>
|
||||
<dd id="hitl-log-detail-time">—</dd>
|
||||
</div>
|
||||
<div class="hitl-log-detail-row hitl-log-detail-row--full" id="hitl-log-detail-comment-row" hidden>
|
||||
<dt data-i18n="hitl.fieldComment">备注</dt>
|
||||
<dd id="hitl-log-detail-comment">—</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="hitl-log-detail-payload" id="hitl-log-detail-payload-wrap" hidden>
|
||||
<div class="hitl-context-label" data-i18n="hitl.fieldPayload">载荷</div>
|
||||
<pre id="hitl-log-detail-payload" class="hitl-context-text"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn-secondary" onclick="closeHitlLogModal()" data-i18n="common.close">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user