dsh-subagent-profile 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/index.mjs +14 -5
- package/lib/client.js +948 -170
- package/lib/core/delegation.mjs +3 -2
- package/lib/core/evolution-advice.mjs +125 -26
- package/lib/core/evolution-assets.mjs +167 -0
- package/lib/core/evolution-draft.mjs +312 -0
- package/lib/core/evolution-engine.mjs +269 -0
- package/lib/core/evolution-generate.mjs +196 -0
- package/lib/core/evolution-ledger.mjs +131 -26
- package/lib/core/evolution-persistence.mjs +213 -0
- package/lib/core/evolution-renewal.mjs +64 -0
- package/lib/core/evolution-routes.mjs +109 -0
- package/lib/core/http-helpers.mjs +35 -0
- package/lib/core/http-routes.mjs +21 -36
- package/lib/core/profile-provider.mjs +2 -1
- package/lib/core/profiles-store.mjs +23 -5
- package/lib/core/session-read.mjs +17 -0
- package/lib/core/shims.mjs +2 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// ── 纯数据:样式(tokens 对齐 shipped「模型」设置节)────────────────────────
|
|
23
23
|
|
|
24
24
|
const CSS = [
|
|
25
|
-
'.sap-section{
|
|
25
|
+
'.sap-section{width:100%;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}',
|
|
26
26
|
'.sap-statusBar{position:sticky;top:0;z-index:1;background:transparent;border:0;border-bottom:1px solid var(--dsw-alias-border-l2);flex-wrap:wrap;gap:4px 14px;padding:4px 0 8px;display:flex;font-size:12px;line-height:18px}',
|
|
27
27
|
'.sap-statusDim{color:var(--dsw-alias-label-tertiary)}',
|
|
28
28
|
'.sap-rowHighlight{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover)}',
|
|
@@ -35,7 +35,7 @@ const CSS = [
|
|
|
35
35
|
'.sap-phaseStep-running{color:var(--dsw-alias-state-warn-label);font-weight:500}',
|
|
36
36
|
'.sap-reminderP0{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-secondary);font-weight:600}',
|
|
37
37
|
'.sap-reminderP1{color:var(--dsw-alias-state-warn-label);font-weight:600}',
|
|
38
|
-
'.sap-reminderUnread{border-color:var(--dsw-alias-
|
|
38
|
+
'.sap-reminderUnread{border-color:var(--dsw-alias-label-primary)}',
|
|
39
39
|
'.sap-reminderHead{display:flex;align-items:center;gap:6px;min-width:0}',
|
|
40
40
|
'.sap-reminderTitle{font-size:14px;font-weight:500;line-height:22px;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1}',
|
|
41
41
|
'.sap-reminderMeta{display:flex;align-items:center;gap:6px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;font-family:monospace;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
@@ -70,11 +70,6 @@ const CSS = [
|
|
|
70
70
|
'.sap-cardStatic .sap-cardHead{cursor:default}',
|
|
71
71
|
'.sap-cardStatic .sap-cardHead:hover{background:0 0}',
|
|
72
72
|
'.sap-cardStatic .sap-cardBody{display:block}',
|
|
73
|
-
'.sap-subgroup{flex-direction:column;gap:6px;padding:2px 0;display:flex}',
|
|
74
|
-
'.sap-subgroup + .sap-subgroup{margin-top:20px}',
|
|
75
|
-
'.sap-subgroupTitle{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;font-weight:600}',
|
|
76
|
-
'.sap-subgroupHint{color:var(--dsw-alias-label-tertiary);font-size:12.5px;line-height:18px;margin:0}',
|
|
77
|
-
'.sap-subgroup .sap-desc,.sap-subgroup .sap-costLine{color:var(--dsw-alias-label-tertiary);font-size:12.5px;line-height:18px}',
|
|
78
73
|
'.sap-costLine{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}',
|
|
79
74
|
'.sap-title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}',
|
|
80
75
|
'.sap-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}',
|
|
@@ -88,6 +83,67 @@ const CSS = [
|
|
|
88
83
|
'.sap-rowId{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;font-family:monospace}',
|
|
89
84
|
'.sap-rowTag{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}',
|
|
90
85
|
'.sap-rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}',
|
|
86
|
+
'.sap-adviceManageCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px;flex-direction:column;gap:6px;display:flex}',
|
|
87
|
+
'.sap-adviceCardHead{flex-direction:column;align-items:stretch;gap:2px}',
|
|
88
|
+
'.sap-adviceHeadTitleRow{display:flex;align-items:center;gap:8px}',
|
|
89
|
+
'.sap-adviceGlobalLine{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}',
|
|
90
|
+
'.sap-adviceSection{flex-direction:column;display:flex}',
|
|
91
|
+
'.sap-adviceSection + .sap-adviceSection{margin-top:10px}',
|
|
92
|
+
'.sap-adviceSectionTitle{color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;font-weight:700;margin:0 0 2px}',
|
|
93
|
+
'.sap-adviceSectionBody{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;margin:0}',
|
|
94
|
+
'.sap-candidateHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:16px}',
|
|
95
|
+
'.sap-candidateBody{border-top:1px dashed var(--dsw-alias-border-l2);margin-top:4px;padding-top:8px;padding-left:16px;flex-direction:column;gap:6px;display:flex}',
|
|
96
|
+
'.sap-candidateChanges{flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;display:flex}',
|
|
97
|
+
'.sap-candidateChange{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}',
|
|
98
|
+
'.sap-adviceDetailBlock{flex-direction:column;gap:4px;display:flex}',
|
|
99
|
+
'.sap-adviceDetailToggle{border:0;background:0 0;padding:0;margin:0;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;line-height:18px;display:inline-flex;align-self:flex-start}',
|
|
100
|
+
'.sap-adviceDetailToggle:hover{color:var(--dsw-alias-label-primary)}',
|
|
101
|
+
'.sap-adviceDetailArrow{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:1;transition:transform .15s}',
|
|
102
|
+
'.sap-adviceDetailToggle[aria-expanded="true"] .sap-adviceDetailArrow{transform:rotate(90deg)}',
|
|
103
|
+
'.sap-adviceDetailTable{width:100%;border-collapse:collapse;font-size:12px;line-height:18px}',
|
|
104
|
+
'.sap-adviceDetailTable th{text-align:left;font-weight:500;color:var(--dsw-alias-label-tertiary);padding:3px 8px 3px 0;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
105
|
+
'.sap-adviceDetailTable td{text-align:left;padding:4px 8px 4px 0;border-bottom:1px dotted var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);vertical-align:top}',
|
|
106
|
+
'.sap-adviceDetailTime{color:var(--dsw-alias-label-primary);font-weight:500;white-space:nowrap}',
|
|
107
|
+
'.sap-adviceDetailSession{color:var(--dsw-alias-label-primary)}',
|
|
108
|
+
'.sap-adviceDetailSessionId{color:var(--dsw-alias-label-tertiary);font-size:11px;margin-left:4px}',
|
|
109
|
+
'.sap-adviceDetailChild{font-family:ui-monospace,Consolas,monospace;font-size:11px;color:var(--dsw-alias-label-tertiary)}',
|
|
110
|
+
'.sap-warn-x{color:var(--dsw-alias-state-error-primary)}',
|
|
111
|
+
'.sap-candidates{flex-direction:column;gap:6px;margin-top:14px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2);display:flex}',
|
|
112
|
+
'.sap-candidatesTitle{color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;font-weight:700;margin:0}',
|
|
113
|
+
'.sap-candidatesTitle .sap-candidatesNotApplied{color:var(--dsw-alias-label-tertiary);font-weight:400}',
|
|
114
|
+
'.sap-candidatesCount{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;margin:2px 0 0}',
|
|
115
|
+
'.sap-candidate{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1)}',
|
|
116
|
+
'.sap-candidateSelected{border-color:var(--dsw-alias-label-primary)}',
|
|
117
|
+
'.sap-candidateRow{box-sizing:border-box;width:100%;border:0;background:0 0;padding:8px 10px;margin:0;font:inherit;text-align:left;cursor:pointer;align-items:center;gap:8px;display:flex}',
|
|
118
|
+
'.sap-candidateRadio{box-sizing:border-box;width:14px;height:14px;border:1.5px solid var(--dsw-alias-border-l2);border-radius:7px;flex:none}',
|
|
119
|
+
'.sap-candidateSelected .sap-candidateRadio{border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-label-primary);box-shadow:inset 0 0 0 3px var(--dsw-alias-bg-layer-1)}',
|
|
120
|
+
'.sap-candidateName{font-weight:600;flex:1;min-width:0;padding-right:8px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;overflow-wrap:anywhere}',
|
|
121
|
+
'.sap-candidateValidity{margin-left:auto;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);border-radius:3px;padding:1px 6px;white-space:nowrap;flex:none}',
|
|
122
|
+
'.sap-candidateArrow{margin-left:6px;color:var(--dsw-alias-label-tertiary);font-size:9px;line-height:1;flex:none;transition:transform .15s}',
|
|
123
|
+
'.sap-candidateOpen .sap-candidateArrow{transform:rotate(90deg)}',
|
|
124
|
+
'.sap-candidateGroup{flex-direction:column;gap:4px;display:flex}',
|
|
125
|
+
'.sap-candidateGroup + .sap-candidateGroup{margin-top:6px}',
|
|
126
|
+
'.sap-candidateGroupTitle{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;font-weight:700;margin:0}',
|
|
127
|
+
'.sap-candidateSafetyList{margin:0 0 0 16px;padding:0;list-style:none;flex-direction:column;gap:2px;display:flex}',
|
|
128
|
+
'.sap-candidateSafetyItem{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}',
|
|
129
|
+
'.sap-candidateSafetyToggle{border:0;background:0 0;padding:0;margin:0;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);align-items:center;gap:4px;font-size:12px;line-height:18px;display:inline-flex;align-self:flex-start;font-weight:600}',
|
|
130
|
+
'.sap-candidateSafetyToggle:hover{color:var(--dsw-alias-label-primary)}',
|
|
131
|
+
'.sap-candidateSafetyArrow{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:1;transition:transform .15s}',
|
|
132
|
+
'.sap-candidateSafetyToggleOpen .sap-candidateSafetyArrow{transform:rotate(90deg)}',
|
|
133
|
+
'.sap-adviceFoot{align-items:center;gap:10px;flex-wrap:wrap;display:flex;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px;margin-top:2px}',
|
|
134
|
+
'.sap-adviceFootNotes{flex-direction:column;gap:2px;display:flex;min-width:0}',
|
|
135
|
+
'.sap-adviceFootNote{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0}',
|
|
136
|
+
'.sap-candidateSettingsRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}',
|
|
137
|
+
'.sap-candidateSettingsRow b{font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
138
|
+
'.sap-candidateSettingsEdit{border:0;background:0 0;padding:0;margin:0;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-decoration:underline;text-underline-offset:3px}',
|
|
139
|
+
'.sap-candidateSettingsEdit:hover{color:var(--dsw-alias-label-primary)}',
|
|
140
|
+
'.sap-candidateSettingsPanel{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:12px 14px;margin-top:8px;flex-direction:column;gap:10px;display:flex;background:var(--dsw-alias-bg-layer-1)}',
|
|
141
|
+
'.sap-candidateSettingsField{flex-direction:column;gap:6px;display:flex}',
|
|
142
|
+
'.sap-candidateSettingsOption{align-items:center;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);display:inline-flex;cursor:pointer}',
|
|
143
|
+
'.sap-candidateSettingsOptions{flex-wrap:wrap;gap:4px 14px;display:flex}',
|
|
144
|
+
'.sap-candidateSettingsCustom{align-items:center;gap:8px;display:flex}',
|
|
145
|
+
'.sap-candidateSettingsCustom .sap-input{max-width:120px}',
|
|
146
|
+
'.sap-candidateSettingsActions{align-items:center;gap:10px;display:flex}',
|
|
91
147
|
'.sap-desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}',
|
|
92
148
|
'.sap-warn{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}',
|
|
93
149
|
'.sap-versionWarn{background:var(--dsw-alias-state-warn-tertiary);border:1px solid var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);border-radius:8px;flex-direction:column;gap:4px;padding:10px 12px;font-size:12px;line-height:18px;margin:0;display:flex}',
|
|
@@ -121,6 +177,10 @@ const CSS = [
|
|
|
121
177
|
'.sap-customized[open]>.sap-customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}',
|
|
122
178
|
'.sap-customizedSummary:hover{color:var(--dsw-alias-label-primary)}',
|
|
123
179
|
'.sap-customizedBody{flex-direction:column;gap:12px;padding-top:12px;display:flex}',
|
|
180
|
+
'.sap-customizedArrowEnd .sap-customizedSummary:before{display:none}',
|
|
181
|
+
'.sap-customizedEndArrow{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:1;transition:transform .15s}',
|
|
182
|
+
'.sap-customizedArrowEnd[open] .sap-customizedEndArrow{transform:rotate(90deg)}',
|
|
183
|
+
'.sap-reminderHandled{border-top:none;padding-top:8px}',
|
|
124
184
|
'.sap-editorActions{justify-content:flex-end;gap:8px;display:flex}',
|
|
125
185
|
'.sap-primaryButton,.sap-secondaryButton{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border:none;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}',
|
|
126
186
|
'.sap-primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}',
|
|
@@ -284,6 +344,37 @@ const CSS = [
|
|
|
284
344
|
'.sap-escapeAdd{align-items:center;gap:8px;display:flex}',
|
|
285
345
|
'.sap-chipRemove{box-sizing:border-box;color:var(--dsw-alias-state-error-primary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:4px;padding:0 4px;font-size:11px;line-height:16px}',
|
|
286
346
|
'.sap-chipRemove:hover{background:var(--dsw-alias-interactive-bg-hover-danger)}',
|
|
347
|
+
// 六页签:横向 tab(下划线激活+加粗)+ 内容区(非激活隐藏,全部挂载保状态)
|
|
348
|
+
'.sap-tabs{display:flex;flex-wrap:wrap;gap:2px;border-bottom:1px solid var(--dsw-alias-border-l2);flex:none}',
|
|
349
|
+
'.sap-tab{box-sizing:border-box;border:0;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;padding:9px 14px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;white-space:nowrap;user-select:none}',
|
|
350
|
+
'.sap-tab:hover{color:var(--dsw-alias-label-primary)}',
|
|
351
|
+
'.sap-tabActive{color:var(--dsw-alias-label-primary);font-weight:700;border-bottom-color:var(--dsw-alias-label-primary)}',
|
|
352
|
+
'.sap-zone{display:none}',
|
|
353
|
+
'.sap-zoneActive{display:block}',
|
|
354
|
+
// 建议摘要行 + 展开区(v8:卡片边界 .advice-item,无二级标题)
|
|
355
|
+
'.sap-adviceItem{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;flex-direction:column;display:flex}',
|
|
356
|
+
'.sap-adviceSummary{box-sizing:border-box;width:100%;border:0;background:0 0;padding:10px 14px;margin:0;font:inherit;text-align:left;cursor:pointer;flex-direction:column;align-items:stretch;gap:2px;display:flex}',
|
|
357
|
+
'.sap-adviceObjLine{align-items:center;gap:6px;flex-wrap:wrap;min-width:0;display:flex}',
|
|
358
|
+
'.sap-adviceSummaryLine{align-items:center;gap:8px;flex-wrap:wrap;min-width:0;display:flex}',
|
|
359
|
+
'.sap-adviceSummary:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
360
|
+
'.sap-adviceObj{color:var(--dsw-alias-label-primary);font-weight:600;font-size:13px;line-height:20px}',
|
|
361
|
+
'.sap-adviceSummaryText{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}',
|
|
362
|
+
'.sap-adviceConf{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;white-space:nowrap}',
|
|
363
|
+
'.sap-adviceSummaryArrow{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:1;transition:transform .15s}',
|
|
364
|
+
'.sap-adviceItemOpen .sap-adviceSummaryArrow{transform:rotate(90deg)}',
|
|
365
|
+
'.sap-adviceNoProfileTag{font-size:11px;line-height:16px;color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:3px;padding:0 5px;flex:none}',
|
|
366
|
+
'.sap-adviceDetail{border-top:1px dashed var(--dsw-alias-border-l2);padding:10px 14px 12px;flex-direction:column;gap:8px;display:flex}',
|
|
367
|
+
// 审计明细折叠表(只读展示)
|
|
368
|
+
'.sap-auditCollapse{border:0;background:0 0;padding:0;margin:0;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;line-height:18px;display:inline-flex;align-self:flex-start}',
|
|
369
|
+
'.sap-auditCollapse:hover{color:var(--dsw-alias-label-primary)}',
|
|
370
|
+
'.sap-auditArrow{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:1;transition:transform .15s}',
|
|
371
|
+
'.sap-auditCollapse[aria-expanded="true"] .sap-auditArrow{transform:rotate(90deg)}',
|
|
372
|
+
'.sap-auditTable{width:100%;border-collapse:collapse;font-size:12px;line-height:18px;margin-top:4px}',
|
|
373
|
+
'.sap-auditTable th{text-align:left;font-weight:500;color:var(--dsw-alias-label-tertiary);padding:3px 8px 3px 0;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
374
|
+
'.sap-auditTable td{text-align:left;padding:4px 8px 4px 0;border-bottom:1px dotted var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);vertical-align:top}',
|
|
375
|
+
'.sap-auditTime{color:var(--dsw-alias-label-primary);font-weight:500;white-space:nowrap}',
|
|
376
|
+
'.sap-auditEvent{color:var(--dsw-alias-label-secondary);white-space:nowrap}',
|
|
377
|
+
'.sap-auditObject{color:var(--dsw-alias-label-primary)}',
|
|
287
378
|
].join('\n')
|
|
288
379
|
|
|
289
380
|
// ── 纯数据:dispatch 卡片面向用户固定文案 ──────────────────────────────────
|
|
@@ -299,7 +390,7 @@ const ZH = {
|
|
|
299
390
|
chipProvider: '提供方',
|
|
300
391
|
chipModel: '模型',
|
|
301
392
|
chipEffort: '推理强度',
|
|
302
|
-
chipTier: '
|
|
393
|
+
chipTier: '档位(估算口径)',
|
|
303
394
|
chipEnabled: '已启用',
|
|
304
395
|
chipDisabled: '已禁用',
|
|
305
396
|
ignoredMark: '已忽略',
|
|
@@ -327,6 +418,7 @@ const ZH = {
|
|
|
327
418
|
phaseLegacy: '已结算(旧版)',
|
|
328
419
|
// 提醒系统
|
|
329
420
|
reminderCenterTitle: '提醒中心',
|
|
421
|
+
reminderCenterDesc: '需要你关注的事件汇总,所有动作均有审计记录;处理过的条目折叠保存',
|
|
330
422
|
reminderEmpty: '暂无提醒(治理事件会在这里留痕)',
|
|
331
423
|
reminderAck: '标记已读',
|
|
332
424
|
reminderAdopt: '采纳',
|
|
@@ -375,7 +467,7 @@ const ZH = {
|
|
|
375
467
|
ledgerGateRejectDist: '安全检查拒绝分布',
|
|
376
468
|
ledgerByProfile: '按方案',
|
|
377
469
|
ledgerByModel: '按模型',
|
|
378
|
-
ledgerByTier: '
|
|
470
|
+
ledgerByTier: '按档位(估算口径)',
|
|
379
471
|
ledgerEmpty: '本会话暂无派发记录',
|
|
380
472
|
ledgerEmptyHint: '在此会话中派发一次子 Agent 后,即可查看完整的决策轨迹。',
|
|
381
473
|
ledgerLoadOlder: '加载更早',
|
|
@@ -484,6 +576,7 @@ const ZH = {
|
|
|
484
576
|
ignoredChipTitleEmpty: '请求值已忽略',
|
|
485
577
|
// 逃生舱(放行非官方预设)
|
|
486
578
|
escapeTitle: '逃生舱(放行非官方预设)',
|
|
579
|
+
escapeCardDesc: '放行非官方预设的高风险通道,默认关闭',
|
|
487
580
|
escapeWarn: '逃生舱已开启:允许派发非官方预设。风险自负——其余安全检查仍全量生效,每个放行都会留审计记录。',
|
|
488
581
|
escapeEmpty: '暂无放行预设',
|
|
489
582
|
escapeAdd: '添加',
|
|
@@ -492,6 +585,11 @@ const ZH = {
|
|
|
492
585
|
escapeAddEmpty: '逃生舱预设 id 不能为空',
|
|
493
586
|
// 派发优化建议注入(开关 + 只提示面板)
|
|
494
587
|
adviceTitle: '派发优化建议',
|
|
588
|
+
advicePanelDesc: '根据历史派发数据向父会话附优化提示(例:某方案近期成功率偏低,可考虑换方案)。只提示,不自动改配置',
|
|
589
|
+
adviceEnableLabel: '开启建议',
|
|
590
|
+
adviceSectionReason: '理由',
|
|
591
|
+
adviceSectionPerf: '近期表现',
|
|
592
|
+
adviceSectionDetail: '派发明细',
|
|
495
593
|
adviceSwitchOn: '开(只提示,不自动改配置)',
|
|
496
594
|
adviceSwitchOff: '关(只提示,不自动改配置)',
|
|
497
595
|
advicePanelEmpty: '暂无建议(需累计 ≥3 次派发且加权成功分低于阈值)',
|
|
@@ -500,15 +598,23 @@ const ZH = {
|
|
|
500
598
|
adviceDirUp: '可上调预算',
|
|
501
599
|
adviceDirDown: '建议降级',
|
|
502
600
|
adviceDirFlat: '维持现状',
|
|
503
|
-
adviceConfidence: '
|
|
504
|
-
|
|
505
|
-
|
|
601
|
+
adviceConfidence: '置信度:',
|
|
602
|
+
confidenceZh: { low: '低', medium: '中等', high: '高' },
|
|
603
|
+
adviceRowLocate: '定位到方案管理',
|
|
604
|
+
// 建议行/理由行(建议直接带对象;理由灰字小字)
|
|
605
|
+
adviceSuggestionPrefix: '建议:',
|
|
606
|
+
adviceReasonPrefix: '理由:',
|
|
607
|
+
adviceDirShortDown: '降级',
|
|
608
|
+
adviceDirShortUp: '升预算',
|
|
609
|
+
// 建议对象标题(摘要行对象与底部备注共用)
|
|
610
|
+
adviceTitleNoProfile: '手动配置的派发',
|
|
611
|
+
adviceTitleProfilePrefix: '方案:',
|
|
506
612
|
costEditProfile: '定位方案',
|
|
507
613
|
costNoProfile: '无对应方案',
|
|
508
614
|
advancedSafetyLine: '安全保证:派发不豁免审批 · 子代理工具不会超出父会话 · 成本与深度有硬上限。',
|
|
509
615
|
resetAllBuiltins: '重置所有内置方案',
|
|
510
616
|
statusLost: '审计丢失',
|
|
511
|
-
profilesIntro: '在此管理「派发子 Agent」工具可用的 profile
|
|
617
|
+
profilesIntro: '在此管理「派发子 Agent」工具可用的 profile。内置方案可编辑、可删除、可在此重置回默认;每个方案可单独启用/禁用。',
|
|
512
618
|
draftSavedWithId: '已保存到 draft,可在设置页「方案管理」中应用;方案 id:{id}',
|
|
513
619
|
draftCopyId: '复制 id',
|
|
514
620
|
ledgerCopyTarget: '复制',
|
|
@@ -520,14 +626,124 @@ const ZH = {
|
|
|
520
626
|
costInheritRatio: '继承占比',
|
|
521
627
|
costByModel: '按模型',
|
|
522
628
|
draftPreview: '安全检查预览',
|
|
523
|
-
draftPreviewPass: '
|
|
524
|
-
draftPreviewFail: '
|
|
629
|
+
draftPreviewPass: '安全检查:全部通过 ✓',
|
|
630
|
+
draftPreviewFail: '安全检查:存在拒绝项',
|
|
631
|
+
// 建议面板候选方案区(三层:标题行 → 说明句 → 候选项列表;有效期徽标在候选行右侧;
|
|
632
|
+
// 应用确认流随后续版本开放,不可应用提示收进展开区尾部)。
|
|
633
|
+
candidateTitle: '候选方案',
|
|
634
|
+
candidateNotApplied: '(未应用)',
|
|
635
|
+
candidateCountSuffix: ' 个 · 单选',
|
|
636
|
+
candidateCountNote: ' · 仅参考,不会自动应用',
|
|
637
|
+
candidateIntro: '系统基于近期表现生成的改进方案,仅参考、不自动应用。',
|
|
638
|
+
candidateRegenerate: '重新生成候选',
|
|
639
|
+
candidateSafetyPending: '安全检查:正在核对…',
|
|
640
|
+
candidateGroupChanges: '改动',
|
|
641
|
+
candidateGroupGenerated: '生成信息',
|
|
642
|
+
candidateSafetyPrefix: '安全检查:',
|
|
643
|
+
candidateSafetyPass: '全部通过 ✓',
|
|
644
|
+
candidateSafetyFail: '存在拒绝项',
|
|
645
|
+
candidateNotApplicable: '应用功能在后续版本开放(届时会先经你确认)',
|
|
646
|
+
candidateValidityForever: '⏱ 无期限',
|
|
647
|
+
candidateValidityPrefix: '⏱ 至 ',
|
|
648
|
+
candidateGeneratedPrefix: '生成于 ',
|
|
649
|
+
candidateSamplesPrefix: '基于最近 ',
|
|
650
|
+
candidateSamplesSuffix: ' 次派发',
|
|
651
|
+
candidatePerfPrefix: '近期表现 ',
|
|
652
|
+
candidatePerfSuffix: '(满分 100)',
|
|
653
|
+
candidateNoSample: '暂无近期派发样本,仅供参考',
|
|
654
|
+
// 候选卡底部:无对应方案存来源配置为新方案;有对应方案调整按钮灰置 + 定位。
|
|
655
|
+
adviceSaveProfile: '保存为方案',
|
|
656
|
+
adviceApplyDisabled: '按建议调整方案',
|
|
657
|
+
adviceSavedNotice: '已保存为新方案:{id}',
|
|
658
|
+
adviceFootNoteSave: '仅保存预设、模型与提供方;自定义人格与工具过滤无法还原(隐私保护不落盘),如需完整配置请手动编辑方案。',
|
|
659
|
+
adviceFootNoteMatchedPrefix: '对应方案:',
|
|
660
|
+
// 建议摘要行(v8:对象/建议/置信度/加权成功分;展开后见理由与明细)
|
|
661
|
+
adviceNoProfileTag: '无方案',
|
|
662
|
+
// 安全页签:安全保证卡 + 审计健康卡(只读明细表)
|
|
663
|
+
safetyCardTitle: '安全保证',
|
|
664
|
+
safetyCardDesc: '派发与审计的安全保证,明细只读',
|
|
665
|
+
safetyCheckNote: '派发前安全检查:白名单 / 成本 / 工具面 / 审批不豁免——拒绝分布见下方审计明细。',
|
|
666
|
+
auditCardTitle: '审计健康',
|
|
667
|
+
auditCardDesc: '治理审计状态与留痕,异常红字提示',
|
|
668
|
+
auditDetailToggle: '查看审计明细(点击展开)',
|
|
669
|
+
auditDetailEmpty: '暂无审计明细(治理事件会在这里留痕)',
|
|
670
|
+
auditDetailScope: '只读展示,暂不提供导出与过滤',
|
|
671
|
+
auditColTime: '时间',
|
|
672
|
+
auditColEvent: '事件',
|
|
673
|
+
auditColObject: '对象',
|
|
674
|
+
auditColResult: '结果',
|
|
675
|
+
auditEventZh: { 'adoption-false': '派发未采纳', 'escape-allow': '逃生舱放行', 'audit-degraded': '审计降级' },
|
|
676
|
+
auditResultPending: '未处理',
|
|
677
|
+
auditResultRead: '已读',
|
|
678
|
+
auditResultActedPrefix: '已处理:',
|
|
679
|
+
// 近期派发明细折叠表
|
|
680
|
+
adviceDetailTogglePrefix: '近期派发明细 · ',
|
|
681
|
+
adviceDetailToggleSuffix: ' 次(点击展开)',
|
|
682
|
+
adviceDetailEmpty: '近期暂无明细',
|
|
683
|
+
adviceDetailColTime: '时间',
|
|
684
|
+
adviceDetailColSession: '主会话(发起者)',
|
|
685
|
+
adviceDetailColChild: '子 Agent',
|
|
686
|
+
adviceDetailColOutcome: '结果',
|
|
687
|
+
adviceDetailColElapsed: '耗时',
|
|
688
|
+
adviceDetailColMode: '模式',
|
|
689
|
+
dispatchOutcomeZh: { completed: '完成', failed: '失败', killed: '终止' },
|
|
690
|
+
// 候选管理设置(顶部设置行 + 就地浮层)
|
|
691
|
+
candidateSettingsRow: '候选时效:',
|
|
692
|
+
candidateSettingsEdit: '修改 ⚙',
|
|
693
|
+
candidateSettingsSaved: '候选设置已保存',
|
|
694
|
+
candidateModeAuto: '自动换新',
|
|
695
|
+
candidateModeManual: '手动维护',
|
|
696
|
+
candidateTtlLabel: '有效期',
|
|
697
|
+
candidateTtl24: '24 小时',
|
|
698
|
+
candidateTtl72: '3 天',
|
|
699
|
+
candidateTtl168: '7 天',
|
|
700
|
+
candidateTtl0: '不自动过期',
|
|
701
|
+
candidateTtlCustom: '自定义',
|
|
702
|
+
candidateSettingsModeLabel: '更新模式',
|
|
703
|
+
candidateSettingsModeAutoText: '自动换新:数据显著变化时换代',
|
|
704
|
+
candidateSettingsModeManualText: '手动维护:不自动换代,候选保留,可点「重新生成候选」刷新',
|
|
705
|
+
candidateSettingsCustomLabel: '自定义天数(1~365)',
|
|
706
|
+
candidateSettingsCustomInvalid: '自定义有效期无效:请输入 1~365 的整数天数',
|
|
707
|
+
candidateSettingsSave: '保存',
|
|
708
|
+
candidateSettingsCancel: '取消',
|
|
709
|
+
// 编辑表单档位选择器口径说明(档位是估算口径,不决定模型)
|
|
710
|
+
tierFormHint: '档位仅影响估算与方案排序,不改变派发模型;显式选择模型才决定实际模型'
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// profileKey 内部表示 → 人话(仅展示层;工具提示/日志保留原串):preset:X→预设:X、
|
|
714
|
+
// persona:1→含自定义人格、toolFilter:1→含工具过滤,段组合以 · 连接;
|
|
715
|
+
// (inline) 即未存方案;空键兜底未指定方案特征。
|
|
716
|
+
function profileKeyZh(key) {
|
|
717
|
+
const text = typeof key === 'string' ? key : ''
|
|
718
|
+
const parts = text.split('|').filter((part) => part !== '').map((part) => {
|
|
719
|
+
if (part === '(inline)') return '未存方案'
|
|
720
|
+
const idx = part.indexOf(':')
|
|
721
|
+
const head = idx >= 0 ? part.slice(0, idx) : part
|
|
722
|
+
const value = idx >= 0 ? part.slice(idx + 1) : ''
|
|
723
|
+
if (head === 'preset') return '预设:' + value
|
|
724
|
+
if (head === 'provider') return '提供方:' + value
|
|
725
|
+
if (head === 'model') return '模型:' + value
|
|
726
|
+
if (head === 'persona') return '含自定义人格'
|
|
727
|
+
if (head === 'toolFilter') return '含工具过滤'
|
|
728
|
+
if (head === 'effort') return '推理强度:' + value
|
|
729
|
+
return part
|
|
730
|
+
})
|
|
731
|
+
const zh = parts.join(' · ')
|
|
732
|
+
return zh === '' ? '未指定方案特征' : zh
|
|
525
733
|
}
|
|
526
734
|
|
|
527
735
|
// ── 纯数据:设置页常量 ─────────────────────────────────────────────────────
|
|
528
736
|
|
|
529
|
-
const EMPTY_FORM = { id: '', description: '', preset: '', provider: '', model: '', reasoningEffort: '', toolMode: 'none', toolList: [] }
|
|
737
|
+
const EMPTY_FORM = { id: '', description: '', preset: '', provider: '', model: '', reasoningEffort: '', tokenTier: 'balanced', toolMode: 'none', toolList: [] }
|
|
530
738
|
const EFFORT_ZH = { off: '关闭', high: '高', max: '最大' }
|
|
739
|
+
// 设置页五页签顺序(v9:维护并入方案管理尾部折叠卡;方案管理第一位并默认激活)。
|
|
740
|
+
const SETTINGS_TABS = [
|
|
741
|
+
{ key: 'profiles', label: '方案管理' },
|
|
742
|
+
{ key: 'cost', label: '成本与统计' },
|
|
743
|
+
{ key: 'advice', label: '自进化与建议' },
|
|
744
|
+
{ key: 'safety', label: '安全' },
|
|
745
|
+
{ key: 'reminders', label: '通知与提醒' }
|
|
746
|
+
]
|
|
531
747
|
const TOOL_MODES = [
|
|
532
748
|
['none', '不限制(继承父会话的全部工具,自动剔除 run_code)'],
|
|
533
749
|
['allow', '白名单(只允许勾选的工具)'],
|
|
@@ -545,12 +761,14 @@ const GATE_FIX_HINTS = {
|
|
|
545
761
|
approval: '审批策略冲突,请核对审批配置',
|
|
546
762
|
budget: '并发派发数或本会话累计 token 已达上限,请等待在途任务完成或开启新会话'
|
|
547
763
|
}
|
|
764
|
+
// 安全检查项 → 人话映射(可见面零内部术语;title 保留原串供排查;未知项原样回退)。
|
|
765
|
+
const CHECK_ZH = { sanitize: '配置格式检查', id: '方案编号不冲突', whitelist: '工具范围安全检查', catalog: '模型目录有效' }
|
|
548
766
|
// 派发决策台账:决策轨迹字段键名 → 中文标签(字段级渲染去「JSON 墙」用)。
|
|
549
767
|
// 派发决策台账:决策轨迹字段键名 → 中文标签(FIELD_ZH,字段级渲染去「JSON 墙」用)。
|
|
550
768
|
// 未知键回退原样(fail-soft 不崩溃);同义键(执行/结算/派发摘要字段)一并收录。
|
|
551
769
|
const FIELD_ZH = {
|
|
552
770
|
parentPreset: '父预设', parentProvider: '父提供方', parentModel: '父模型', parentToolCount: '父工具数',
|
|
553
|
-
tokenTier: '
|
|
771
|
+
tokenTier: '档位(估算口径)', persona_present: '是否设置人格', toolFilter_present: '是否设置工具过滤', envelope: '信封模式',
|
|
554
772
|
prompt_excerpt: '任务摘要', prompt_truncated: '任务已截断',
|
|
555
773
|
systemTrustCandidates: '候选白名单', resolvedPreset: '解析后预设', allowed: '放行',
|
|
556
774
|
field: '检查项', checkedAgainst: '对照', verdict: '结论', cap: '上限',
|
|
@@ -562,7 +780,7 @@ const FIELD_ZH = {
|
|
|
562
780
|
requestedPreset: '目标预设', checks: '检查项', detail: '详情',
|
|
563
781
|
effectiveAllowCount: '生效工具数', jobId: '任务ID', reason: '原因', limit: '上限',
|
|
564
782
|
skipped: '跳过', allowFailOpen: '兼容模式', maxTokens: 'token 上限', maxDepth: '深度上限',
|
|
565
|
-
requestedToolFilter: '请求工具过滤', providers: '提供方', modelEfforts: '
|
|
783
|
+
requestedToolFilter: '请求工具过滤', providers: '提供方', modelEfforts: '模型支持的推理强度',
|
|
566
784
|
effectiveToolNames: '生效工具', provider: '提供方', model: '模型',
|
|
567
785
|
reasoningEffort: '推理强度',
|
|
568
786
|
unknown_keys: '未知字段(已被拒绝)',
|
|
@@ -586,7 +804,8 @@ function readAuditFromData(data) {
|
|
|
586
804
|
}
|
|
587
805
|
|
|
588
806
|
// /options/summary 响应 → 设置页状态(enabled/options/audit/逃生舱开关与放行集)。
|
|
589
|
-
// set 为各 setter 的注入对象;escapePresets 非数组回退空数组,escapeEnabled 仅 true
|
|
807
|
+
// set 为各 setter 的注入对象;escapePresets 非数组回退空数组,escapeEnabled 仅 true 才开;
|
|
808
|
+
// 候选设置(更新模式/有效期)缺失时回退默认。
|
|
590
809
|
function applySummaryData(data, set) {
|
|
591
810
|
set.enabled(data.enabled !== false)
|
|
592
811
|
set.options({
|
|
@@ -598,6 +817,10 @@ function applySummaryData(data, set) {
|
|
|
598
817
|
set.escapeEnabled(data.escapeEnabled === true)
|
|
599
818
|
set.escapePresets(Array.isArray(data.escapePresets) ? data.escapePresets : [])
|
|
600
819
|
set.evolutionAdvice(data.evolutionAdvice === true)
|
|
820
|
+
set.candidateSettings({
|
|
821
|
+
candidateMode: data.candidateMode === 'manual' ? 'manual' : 'auto',
|
|
822
|
+
candidateTtlH: Number.isFinite(data.candidateTtlH) ? data.candidateTtlH : 24
|
|
823
|
+
})
|
|
601
824
|
}
|
|
602
825
|
|
|
603
826
|
// ── 纯函数:api / 样式注入 / 文案映射 ──────────────────────────────────────
|
|
@@ -1183,7 +1406,7 @@ function buildSubagentBanner(el, subagent, parentPreset, modelState, bannerMode)
|
|
|
1183
1406
|
|
|
1184
1407
|
// ── 提醒系统 ─────────────────────────────────────────────────────
|
|
1185
1408
|
|
|
1186
|
-
//
|
|
1409
|
+
// 通知中心(设置页通知与提醒页签):读 /reminders(与审计同源 host store),全动作
|
|
1187
1410
|
// (标记已读/采纳/忽略/拒绝/关闭)经 POST 回写 host 并刷新——一条提醒 = 一条审计记录。
|
|
1188
1411
|
// sessions 服务供条目主/子会话标题查询与跳转;跳转失败静默(设置页不被导航异常打断)。
|
|
1189
1412
|
function makeReminderCenter(el, useState, useEffect, openChild, getSessions) {
|
|
@@ -1204,13 +1427,37 @@ function makeReminderCenter(el, useState, useEffect, openChild, getSessions) {
|
|
|
1204
1427
|
return ReminderCenter
|
|
1205
1428
|
}
|
|
1206
1429
|
|
|
1430
|
+
// 提醒中心分层:未终态条目(action 为空)进默认列表;已终态条目折叠为「已处理(N)」
|
|
1431
|
+
// 可展开区(原生 details,展开后仍按现有行渲染并保留已处理标注,可追溯)。审计留痕
|
|
1432
|
+
// 由 host 侧动作回写,已处理条目不再堆在默认列表里。全空才显示空态文案;仅剩已处理时
|
|
1433
|
+
// 只显示折叠区,不显示空态。
|
|
1207
1434
|
function buildReminderCenter(el, reminders, error, act, sessions, openChild) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1435
|
+
const handled = reminders.filter((r) => typeof r.action === 'string' && r.action !== '')
|
|
1436
|
+
const pending = reminders.filter((r) => !(typeof r.action === 'string' && r.action !== ''))
|
|
1437
|
+
const pendingList = pending.length > 0
|
|
1438
|
+
? el('ul', { className: 'sap-rows' }, pending.map((r) => buildReminderRow(el, r, act, sessions, openChild)))
|
|
1439
|
+
: null
|
|
1440
|
+
const handledList = handled.length > 0
|
|
1441
|
+
? el('details', { className: 'sap-customized sap-customizedArrowEnd sap-reminderHandled' },
|
|
1442
|
+
el('summary', { className: 'sap-customizedSummary' }, '已处理(' + handled.length + ')', el('span', { className: 'sap-customizedEndArrow' }, '▸')),
|
|
1443
|
+
el('div', { className: 'sap-customizedBody' },
|
|
1444
|
+
el('ul', { className: 'sap-rows' }, handled.map((r) => buildReminderRow(el, r, act, sessions, openChild)))
|
|
1445
|
+
)
|
|
1446
|
+
)
|
|
1447
|
+
: null
|
|
1448
|
+
return el('div', { className: 'sap-card sap-cardStatic' },
|
|
1449
|
+
el('div', { className: 'sap-cardHead' },
|
|
1450
|
+
el('div', { className: 'sap-cardTitleBox' },
|
|
1451
|
+
el('div', { className: 'sap-cardTitle' }, ZH.reminderCenterTitle),
|
|
1452
|
+
el('div', { className: 'sap-cardDesc' }, ZH.reminderCenterDesc)
|
|
1453
|
+
)
|
|
1454
|
+
),
|
|
1455
|
+
el('div', { className: 'sap-cardBody' },
|
|
1456
|
+
error !== '' ? el('p', { className: 'sap-notice' }, error) : null,
|
|
1457
|
+
pendingList === null && handledList === null
|
|
1458
|
+
? el('p', { className: 'sap-desc' }, ZH.reminderEmpty)
|
|
1459
|
+
: [pendingList, handledList]
|
|
1460
|
+
)
|
|
1214
1461
|
)
|
|
1215
1462
|
}
|
|
1216
1463
|
|
|
@@ -1295,20 +1542,19 @@ function buildUnadoptedFields(el, r, sessions, openChild) {
|
|
|
1295
1542
|
)
|
|
1296
1543
|
}
|
|
1297
1544
|
|
|
1298
|
-
//
|
|
1299
|
-
//
|
|
1545
|
+
// 未采纳条目按钮三件套:查看子会话(跳转,恒保留)/ 标记已读(未读显示)/ 关闭。
|
|
1546
|
+
// 已终态条目隐藏标记已读与关闭(只留只读跳转),防对终态条目重复处置产生重复审计;
|
|
1547
|
+
// 提醒上的处置只改变提醒自身状态(已读/终态),不改变采纳判定——采纳与否由事件流自动汇总。
|
|
1300
1548
|
function buildUnadoptedActions(el, r, act, childAddress, jumpChild) {
|
|
1549
|
+
const terminated = typeof r.action === 'string' && r.action !== ''
|
|
1301
1550
|
return el('div', { className: 'sap-reminderActions' }, [
|
|
1302
1551
|
el('button', { type: 'button', className: 'sap-detailToggle', disabled: childAddress === null, onClick: jumpChild }, ZH.reminderViewChild),
|
|
1303
|
-
r.
|
|
1304
|
-
|
|
1305
|
-
: null,
|
|
1306
|
-
r.unread === true ? el('button', { type: 'button', className: 'sap-detailToggle', onClick: act(r.id, 'ack') }, ZH.reminderAck) : null,
|
|
1307
|
-
el('button', { type: 'button', className: 'sap-dangerButton', onClick: act(r.id, 'dismiss') }, ZH.reminderDismiss)
|
|
1552
|
+
terminated ? null : r.unread === true ? el('button', { type: 'button', className: 'sap-detailToggle', onClick: act(r.id, 'ack') }, ZH.reminderAck) : null,
|
|
1553
|
+
terminated ? null : el('button', { type: 'button', className: 'sap-dangerButton', onClick: act(r.id, 'dismiss') }, ZH.reminderDismiss)
|
|
1308
1554
|
])
|
|
1309
1555
|
}
|
|
1310
1556
|
|
|
1311
|
-
// 未采纳提醒条目(完整版):标题 + 五字段 +
|
|
1557
|
+
// 未采纳提醒条目(完整版):标题 + 五字段 + 按钮三件套 + 已处理标注。
|
|
1312
1558
|
function buildUnadoptedReminderRow(el, r, act, sessions, openChild) {
|
|
1313
1559
|
const severityCls = r.severity === 'P0' ? 'sap-reminderP0' : 'sap-reminderP1'
|
|
1314
1560
|
const severityText = r.severity === 'P0' ? '紧急' : '注意'
|
|
@@ -1329,6 +1575,9 @@ function buildUnadoptedReminderRow(el, r, act, sessions, openChild) {
|
|
|
1329
1575
|
)
|
|
1330
1576
|
}
|
|
1331
1577
|
|
|
1578
|
+
// 通知类提醒行:P0 未读只保留「标记已读」(常驻至已读,不可跳过未读);已读后出现
|
|
1579
|
+
// 「关闭」(与 P1/P2 同);P1/P2 恒保留「标记已读 / 关闭」。已终态条目隐藏动作按钮,
|
|
1580
|
+
// 只显示已处理灰字标注(折叠区可追溯、防对终态条目误点产生重复审计)。
|
|
1332
1581
|
function buildReminderRow(el, r, act, sessions, openChild) {
|
|
1333
1582
|
if (r.kind === 'adoption-false') return buildUnadoptedReminderRow(el, r, act, sessions, openChild)
|
|
1334
1583
|
const severityCls = r.severity === 'P0' ? 'sap-reminderP0' : 'sap-reminderP1'
|
|
@@ -1341,10 +1590,14 @@ function buildReminderRow(el, r, act, sessions, openChild) {
|
|
|
1341
1590
|
),
|
|
1342
1591
|
el('div', { className: 'sap-reminderMeta', title: session.title }, session.text),
|
|
1343
1592
|
el('p', { className: 'sap-desc' }, r.detail),
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
el('
|
|
1347
|
-
|
|
1593
|
+
typeof r.action === 'string' && r.action !== ''
|
|
1594
|
+
? null
|
|
1595
|
+
: el('div', { className: 'sap-reminderActions' }, [
|
|
1596
|
+
r.unread === true ? el('button', { className: 'sap-detailToggle', onClick: act(r.id, 'ack') }, ZH.reminderAck) : null,
|
|
1597
|
+
r.severity !== 'P0' || r.unread !== true
|
|
1598
|
+
? el('button', { className: 'sap-dangerButton', onClick: act(r.id, 'dismiss') }, ZH.reminderDismiss)
|
|
1599
|
+
: null
|
|
1600
|
+
]),
|
|
1348
1601
|
typeof r.action === 'string' && r.action !== ''
|
|
1349
1602
|
? el('p', { className: 'sap-metaValueDefault' }, ZH.reminderActed.replace('{action}', ZH.reminderActionZh[r.action] || r.action))
|
|
1350
1603
|
: null
|
|
@@ -2107,6 +2360,7 @@ function makeEditActions(s, loadEfforts, loadTools) {
|
|
|
2107
2360
|
provider: typeof profile.provider === 'string' ? profile.provider : '',
|
|
2108
2361
|
model: typeof profile.model === 'string' ? profile.model : '',
|
|
2109
2362
|
reasoningEffort: typeof profile.reasoningEffort === 'string' ? profile.reasoningEffort : '',
|
|
2363
|
+
tokenTier: typeof profile.tokenTier === 'string' && (profile.tokenTier === 'cheap' || profile.tokenTier === 'balanced' || profile.tokenTier === 'premium') ? profile.tokenTier : 'balanced',
|
|
2110
2364
|
toolMode,
|
|
2111
2365
|
toolList: toolMode === 'allow' ? allowArr : denyArr
|
|
2112
2366
|
})
|
|
@@ -2134,6 +2388,7 @@ function makeSubmitActions(api, s, refresh, applyWrite) {
|
|
|
2134
2388
|
add('provider', s.form.provider)
|
|
2135
2389
|
add('model', s.form.model)
|
|
2136
2390
|
add('reasoningEffort', s.form.reasoningEffort)
|
|
2391
|
+
if (typeof s.form.tokenTier === 'string' && s.form.tokenTier !== '') payload.tokenTier = s.form.tokenTier
|
|
2137
2392
|
const list = Array.isArray(s.form.toolList) ? s.form.toolList : []
|
|
2138
2393
|
if (s.form.toolMode === 'allow' && list.length > 0) payload.toolFilter = { allow: list }
|
|
2139
2394
|
else if (s.form.toolMode === 'deny' && list.length > 0) payload.toolFilter = { deny: list }
|
|
@@ -2173,7 +2428,18 @@ function makeToggle(api, path, payloadKey, get, set, setError) {
|
|
|
2173
2428
|
function makeRowActions(api, s, refresh, applyWrite) {
|
|
2174
2429
|
const toggleEnabled = makeToggle(api, '/set-enabled', 'enabled', () => s.enabled, s.setEnabled, s.setError)
|
|
2175
2430
|
const toggleEscape = makeToggle(api, '/set-escape', 'enabled', () => s.escapeEnabled, s.setEscapeEnabled, s.setError)
|
|
2176
|
-
|
|
2431
|
+
// 建议开关切换后立即刷新 /list:候选随建议链同生共死(开→生成可见,关→不再下发)。
|
|
2432
|
+
const toggleAdvice = () => {
|
|
2433
|
+
const next = !s.evolutionAdvice
|
|
2434
|
+
s.setEvolutionAdvice(next)
|
|
2435
|
+
s.setError('')
|
|
2436
|
+
api('/set-evolution-advice', { advice: next })
|
|
2437
|
+
.then(() => refresh())
|
|
2438
|
+
.catch((err) => {
|
|
2439
|
+
s.setEvolutionAdvice(!next)
|
|
2440
|
+
s.setError(String((err && err.message) || err))
|
|
2441
|
+
})
|
|
2442
|
+
}
|
|
2177
2443
|
const setProfileEnabled = (profile, next) => () => {
|
|
2178
2444
|
s.setError('')
|
|
2179
2445
|
api('/set-profile-enabled', { id: profile.id, enabled: next })
|
|
@@ -2247,7 +2513,7 @@ function makeToolsLoader(api, setTools) {
|
|
|
2247
2513
|
}
|
|
2248
2514
|
}
|
|
2249
2515
|
|
|
2250
|
-
// 版本探测 → 拉 /options/versions
|
|
2516
|
+
// 版本探测 → 拉 /options/versions;维护区 0.5.0 再统一设计归属,数据暂留。
|
|
2251
2517
|
function makeVersionsLoader(api, setVersionWarnings, setPluginVersion) {
|
|
2252
2518
|
return () => {
|
|
2253
2519
|
api('/options/versions')
|
|
@@ -2259,10 +2525,19 @@ function makeVersionsLoader(api, setVersionWarnings, setPluginVersion) {
|
|
|
2259
2525
|
}
|
|
2260
2526
|
}
|
|
2261
2527
|
|
|
2528
|
+
// 会话服务快照(派发明细表查主会话标题用);拿不到回退 null(fail-soft)。
|
|
2529
|
+
function sessionsSnapshot(getSessions) {
|
|
2530
|
+
try {
|
|
2531
|
+
return typeof getSessions === 'function' ? getSessions() : null
|
|
2532
|
+
} catch {
|
|
2533
|
+
return null
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2262
2537
|
// 定位方案:展开方案管理区 + 高亮对应卡片 + 滚动到可视区(建议/成本行的去向)。
|
|
2263
2538
|
function makeLocateProfile(s) {
|
|
2264
2539
|
return (profileId) => () => {
|
|
2265
|
-
s.
|
|
2540
|
+
s.setActiveTab('profiles')
|
|
2266
2541
|
s.setHighlightId(String(profileId))
|
|
2267
2542
|
setTimeout(() => {
|
|
2268
2543
|
const node = document.getElementById('sap-profile-' + String(profileId))
|
|
@@ -2272,6 +2547,116 @@ function makeLocateProfile(s) {
|
|
|
2272
2547
|
}
|
|
2273
2548
|
}
|
|
2274
2549
|
|
|
2550
|
+
// 候选动作:行点击展开/收起(默认收起,展开态不持久化;展开时复用 /draft/preview
|
|
2551
|
+
// 只读安全检查流);radio 仅选中(不改展开态);安全检查在展开区内部默认收起;
|
|
2552
|
+
// 派发明细折叠行独立开关。
|
|
2553
|
+
function makeCandidateActions(api, s) {
|
|
2554
|
+
const previewCandidate = (candidate) => () => {
|
|
2555
|
+
s.setError('')
|
|
2556
|
+
api('/draft/preview', { config: candidate.config ?? {}, name: candidate.name ?? '', description: candidate.description ?? '', source: 'evolution' })
|
|
2557
|
+
.then((data) => {
|
|
2558
|
+
const previews = { ...(s.draftPreviews || {}) }
|
|
2559
|
+
previews[candidate.id] = { ok: data.ok === true, checks: Array.isArray(data.checks) ? data.checks : [] }
|
|
2560
|
+
s.setDraftPreviews(previews)
|
|
2561
|
+
})
|
|
2562
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
2563
|
+
}
|
|
2564
|
+
const toggleCandidateOpen = (candidate) => () => {
|
|
2565
|
+
const openState = s.candidateOpen !== null && typeof s.candidateOpen === 'object' ? s.candidateOpen : {}
|
|
2566
|
+
if (openState[candidate.id] === true) {
|
|
2567
|
+
s.setCandidateOpen({ ...openState, [candidate.id]: false })
|
|
2568
|
+
} else {
|
|
2569
|
+
s.setCandidateOpen({ ...openState, [candidate.id]: true })
|
|
2570
|
+
previewCandidate(candidate)()
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
const toggleCandidateSafety = (candidate) => () => {
|
|
2574
|
+
const openState = s.candidateSafetyOpen !== null && typeof s.candidateSafetyOpen === 'object' ? s.candidateSafetyOpen : {}
|
|
2575
|
+
s.setCandidateSafetyOpen({ ...openState, [candidate.id]: openState[candidate.id] !== true })
|
|
2576
|
+
}
|
|
2577
|
+
const selectCandidate = (profileKey, candidateId) => () => {
|
|
2578
|
+
const map = s.candidateSelected !== null && typeof s.candidateSelected === 'object' ? s.candidateSelected : {}
|
|
2579
|
+
s.setCandidateSelected({ ...map, [profileKey]: candidateId })
|
|
2580
|
+
}
|
|
2581
|
+
const toggleDetail = (profileKey) => () => {
|
|
2582
|
+
const openState = s.detailOpen !== null && typeof s.detailOpen === 'object' ? s.detailOpen : {}
|
|
2583
|
+
s.setDetailOpen({ ...openState, [profileKey]: openState[profileKey] !== true })
|
|
2584
|
+
}
|
|
2585
|
+
return { previewCandidate, toggleCandidateOpen, toggleCandidateSafety, selectCandidate, toggleDetail }
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// 表单 → 有效期小时数:自定义档按天数×24(1~365 整数),其余档原样;
|
|
2589
|
+
// 非法输入返回 null(调用方就地报错不提交)。
|
|
2590
|
+
function candidateTtlFromForm(form) {
|
|
2591
|
+
if (form.candidateTtlChoice === 'custom') {
|
|
2592
|
+
const days = Number(String(form.customDays ?? '').trim())
|
|
2593
|
+
if (!Number.isInteger(days) || days < 1 || days > 365) return null
|
|
2594
|
+
return days * 24
|
|
2595
|
+
}
|
|
2596
|
+
const ttlH = Number(form.candidateTtlChoice)
|
|
2597
|
+
if (!Number.isInteger(ttlH) || !(ttlH === 0 || (ttlH >= 1 && ttlH <= 8760))) return 24
|
|
2598
|
+
return ttlH
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// 页签内交互动作:建议摘要行展开(按 profileKey)与审计明细折叠。
|
|
2602
|
+
function makeTabActions(s) {
|
|
2603
|
+
return {
|
|
2604
|
+
toggleAdviceOpen: (profileKey) => () => {
|
|
2605
|
+
const map = s.adviceOpen !== null && typeof s.adviceOpen === 'object' ? s.adviceOpen : {}
|
|
2606
|
+
s.setAdviceOpen({ ...map, [profileKey]: map[profileKey] !== true })
|
|
2607
|
+
},
|
|
2608
|
+
toggleAuditDetail: () => s.setAuditDetailOpen(!(s.auditDetailOpen === true))
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// 候选管理设置动作:设置浮层开合、表单字段、保存(POST /settings/candidate)与
|
|
2613
|
+
// 取消;重新生成候选(POST /evolution/regenerate)与保存为方案(POST /advice/save-profile)。
|
|
2614
|
+
function makeCandidateSettingsActions(api, s, refresh, applyWrite) {
|
|
2615
|
+
const formOf = () => (s.candidateSettingsForm !== null && typeof s.candidateSettingsForm === 'object' ? s.candidateSettingsForm : {})
|
|
2616
|
+
const openSettings = () => {
|
|
2617
|
+
const settings = s.candidateSettings !== null && typeof s.candidateSettings === 'object' ? s.candidateSettings : { candidateMode: 'auto', candidateTtlH: 24 }
|
|
2618
|
+
const choice = candidateTtlChoiceOf(settings.candidateTtlH)
|
|
2619
|
+
s.setCandidateSettingsOpen(true)
|
|
2620
|
+
s.setCandidateSettingsForm({ candidateMode: settings.candidateMode, candidateTtlChoice: choice, customDays: choice === 'custom' ? String(settings.candidateTtlH / 24) : '', error: '' })
|
|
2621
|
+
}
|
|
2622
|
+
const closeSettings = () => {
|
|
2623
|
+
s.setCandidateSettingsOpen(false)
|
|
2624
|
+
s.setCandidateSettingsForm((prev) => ({ ...(prev ?? {}), error: '' }))
|
|
2625
|
+
}
|
|
2626
|
+
const setForm = (patch) => s.setCandidateSettingsForm((prev) => ({ ...(prev ?? {}), ...patch, error: '' }))
|
|
2627
|
+
const save = () => {
|
|
2628
|
+
const form = formOf()
|
|
2629
|
+
const ttlH = candidateTtlFromForm(form)
|
|
2630
|
+
if (ttlH === null) {
|
|
2631
|
+
s.setCandidateSettingsForm((prev) => ({ ...(prev ?? {}), error: ZH.candidateSettingsCustomInvalid }))
|
|
2632
|
+
return
|
|
2633
|
+
}
|
|
2634
|
+
s.setError('')
|
|
2635
|
+
api('/settings/candidate', { candidateMode: form.candidateMode === 'manual' ? 'manual' : 'auto', candidateTtlH: ttlH })
|
|
2636
|
+
.then((data) => {
|
|
2637
|
+
s.setCandidateSettings({ candidateMode: data.candidateMode === 'manual' ? 'manual' : 'auto', candidateTtlH: Number.isFinite(data.candidateTtlH) ? data.candidateTtlH : 24 })
|
|
2638
|
+
s.setCandidateSettingsOpen(false)
|
|
2639
|
+
applyWrite(data, ZH.candidateSettingsSaved)
|
|
2640
|
+
refresh()
|
|
2641
|
+
})
|
|
2642
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
2643
|
+
}
|
|
2644
|
+
const post = (path, body, onData) => {
|
|
2645
|
+
s.setError('')
|
|
2646
|
+
api(path, body).then(onData).catch((err) => s.setError(String((err && err.message) || err)))
|
|
2647
|
+
}
|
|
2648
|
+
return {
|
|
2649
|
+
toggleCandidateSettings: () => (s.candidateSettingsOpen === true ? closeSettings() : openSettings()),
|
|
2650
|
+
setCandidateFormMode: (mode) => () => setForm({ candidateMode: mode }),
|
|
2651
|
+
setCandidateFormTtl: (choice) => () => setForm({ candidateTtlChoice: choice }),
|
|
2652
|
+
setCandidateFormCustomDays: (value) => setForm({ customDays: value }),
|
|
2653
|
+
saveCandidateSettings: save,
|
|
2654
|
+
cancelCandidateSettings: closeSettings,
|
|
2655
|
+
regenerateCandidates: (profileKey) => () => post('/evolution/regenerate', { profileKey }, () => { s.setSavedNotice(ZH.candidateRegenerate + ' 完成'); refresh() }),
|
|
2656
|
+
saveAdviceProfile: (profileKey) => () => post('/advice/save-profile', { profileKey }, (data) => { applyWrite(data, ZH.adviceSavedNotice.replace('{id}', String(data.id ?? ''))); refresh() }),
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2275
2660
|
function makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools) {
|
|
2276
2661
|
return {
|
|
2277
2662
|
...makeFormActions(s, loadEfforts, loadTools),
|
|
@@ -2279,6 +2664,9 @@ function makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools
|
|
|
2279
2664
|
...makeSubmitActions(api, s, refresh, applyWrite),
|
|
2280
2665
|
...makeRowActions(api, s, refresh, applyWrite),
|
|
2281
2666
|
...makeEscapeActions(api, s),
|
|
2667
|
+
...makeCandidateActions(api, s),
|
|
2668
|
+
...makeCandidateSettingsActions(api, s, refresh, applyWrite),
|
|
2669
|
+
...makeTabActions(s),
|
|
2282
2670
|
locateProfile: makeLocateProfile(s),
|
|
2283
2671
|
}
|
|
2284
2672
|
}
|
|
@@ -2417,6 +2805,17 @@ function buildProfileRows(el, chip, s, actions) {
|
|
|
2417
2805
|
})
|
|
2418
2806
|
}
|
|
2419
2807
|
|
|
2808
|
+
// 档位选择器(估算口径):仅影响估算与方案排序,不决定模型;下方附口径说明。
|
|
2809
|
+
function buildTierField(el, s, actions) {
|
|
2810
|
+
return el('div', { className: 'sap-field' },
|
|
2811
|
+
el('span', { className: 'sap-fieldLabel' }, ZH.chipTier),
|
|
2812
|
+
el('select', { className: 'sap-input sap-select', value: s.form.tokenTier, onChange: actions.setField('tokenTier') },
|
|
2813
|
+
Object.entries(ZH.tierZh).map(([id, label]) => el('option', { key: id, value: id }, label))
|
|
2814
|
+
),
|
|
2815
|
+
el('p', { className: 'sap-metaValueDefault', style: { margin: '4px 0 0' } }, ZH.tierFormHint)
|
|
2816
|
+
)
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2420
2819
|
// 提供方只读展示:provider 由已选 model 联动派生;未选模型时回退表单存量
|
|
2421
2820
|
// provider 值(编辑存量 profile 时照常展示),不可独立编辑。
|
|
2422
2821
|
function buildProviderReadonlyLine(el, s) {
|
|
@@ -2449,8 +2848,8 @@ function buildAddCard(el, s, actions, toolSection) {
|
|
|
2449
2848
|
buildSelectField(el, s, actions.setField, '模型(子 agent 使用的模型)', 'model', s.options.models,
|
|
2450
2849
|
(m) => el('option', { key: m.id, value: m.id }, m.name && m.name !== m.id ? `${m.name}(${m.providerName || m.provider})` : m.id)),
|
|
2451
2850
|
s.options.models.length === 0 ? el('p', { className: 'sap-metaValueDefault', style: { margin: '0' } }, '未检测到模型目录(headless 部署)——派发成本安全检查将按「兼容模式」开关决定放行或拒绝') : null,
|
|
2452
|
-
el('details', { className: 'sap-customized' },
|
|
2453
|
-
el('summary', { className: 'sap-customizedSummary' }, '高级选项'),
|
|
2851
|
+
el('details', { className: 'sap-customized sap-customizedArrowEnd' },
|
|
2852
|
+
el('summary', { className: 'sap-customizedSummary' }, '高级选项', el('span', { className: 'sap-customizedEndArrow' }, '▸')),
|
|
2454
2853
|
el('div', { className: 'sap-customizedBody' },
|
|
2455
2854
|
el('p', { className: 'sap-metaValueDefault', style: { margin: '0' } }, 'continuable 模式忽略 preset 换用与推理强度(子 Agent 继承父会话预设),派发结果以「ignored」标注'),
|
|
2456
2855
|
buildSelectField(el, s, actions.setField, '目标预设(切换子 agent 使用的 Agent 预设)', 'preset', s.options.presets,
|
|
@@ -2465,6 +2864,7 @@ function buildAddCard(el, s, actions, toolSection) {
|
|
|
2465
2864
|
)
|
|
2466
2865
|
)
|
|
2467
2866
|
: buildSelectField(el, s, actions.setField, '推理强度(思考深度)', 'reasoningEffort', effortsForModel, renderEffortOption),
|
|
2867
|
+
buildTierField(el, s, actions),
|
|
2468
2868
|
toolSection
|
|
2469
2869
|
)
|
|
2470
2870
|
),
|
|
@@ -2475,23 +2875,6 @@ function buildAddCard(el, s, actions, toolSection) {
|
|
|
2475
2875
|
)
|
|
2476
2876
|
}
|
|
2477
2877
|
|
|
2478
|
-
// 派发优化建议注入:一行开关,只提示不改变派发行为。
|
|
2479
|
-
function buildAdviceSection(el, s, actions) {
|
|
2480
|
-
return el('div', { className: 'sap-rowCard' },
|
|
2481
|
-
el('div', { className: 'sap-rowHead' },
|
|
2482
|
-
el('span', { className: 'sap-rowIdentity' },
|
|
2483
|
-
el('span', { className: 'sap-rowName' }, ZH.adviceTitle)
|
|
2484
|
-
),
|
|
2485
|
-
el('span', { className: 'sap-rowActions' },
|
|
2486
|
-
el('label', { className: 'sap-switch', title: ZH.adviceTitle },
|
|
2487
|
-
el('input', { type: 'checkbox', checked: s.evolutionAdvice, onChange: actions.toggleAdvice }),
|
|
2488
|
-
el('span', null, s.evolutionAdvice ? ZH.adviceSwitchOn : ZH.adviceSwitchOff)
|
|
2489
|
-
)
|
|
2490
|
-
)
|
|
2491
|
-
)
|
|
2492
|
-
)
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
2878
|
// 按 profileKey 的 preset/model 段匹配方案管理区的 profile 卡片(建议行的去向)。
|
|
2496
2879
|
function matchingProfileOf(profiles, profileKey) {
|
|
2497
2880
|
if (!Array.isArray(profiles) || typeof profileKey !== 'string') return null
|
|
@@ -2508,42 +2891,395 @@ function matchingProfileOf(profiles, profileKey) {
|
|
|
2508
2891
|
return null
|
|
2509
2892
|
}
|
|
2510
2893
|
|
|
2511
|
-
//
|
|
2512
|
-
|
|
2894
|
+
// ── 建议面板纯函数(epoch 格式化 / 文案换算 / 建议一句话)────────────────────
|
|
2895
|
+
|
|
2896
|
+
// epoch ms → MM-DD HH:mm(本地时区,与原型同口径);非有限数返回空串。
|
|
2897
|
+
function formatMonthDayTime(ts) {
|
|
2898
|
+
if (typeof ts !== 'number' || !Number.isFinite(ts)) return ''
|
|
2899
|
+
const d = new Date(ts)
|
|
2900
|
+
const p = (n) => String(n).padStart(2, '0')
|
|
2901
|
+
return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
// 候选有效期徽标文案:null(不自动过期)= ⏱ 无期限;数字 = ⏱ 至 MM-DD HH:mm。
|
|
2905
|
+
function candidateValidityText(expiry) {
|
|
2906
|
+
if (typeof expiry !== 'number' || !Number.isFinite(expiry)) return ZH.candidateValidityForever
|
|
2907
|
+
return ZH.candidateValidityPrefix + formatMonthDayTime(expiry)
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
// 指标行:把「平均耗时 <ms>ms」换算成短时(40.4s 等),其余指标原样(信息不删)。
|
|
2911
|
+
function adviceMetricsOf(performanceText) {
|
|
2912
|
+
return adviceEvidenceOf(performanceText).metrics.map((metric) => {
|
|
2913
|
+
const hit = /^平均耗时 (.+)$/.exec(metric)
|
|
2914
|
+
if (hit !== null) {
|
|
2915
|
+
const ms = Number(hit[1])
|
|
2916
|
+
return Number.isFinite(ms) ? '平均耗时 ' + formatDurationMs(ms) : metric
|
|
2917
|
+
}
|
|
2918
|
+
return metric
|
|
2919
|
+
})
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
// 方向与置信度中文(模块级共用:面板建议行与旧格式回退)。
|
|
2923
|
+
function directionZh(d) {
|
|
2924
|
+
return d === '升' ? ZH.adviceDirUp : d === '降' ? ZH.adviceDirDown : ZH.adviceDirFlat
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
function confidenceZhOf(level) {
|
|
2928
|
+
return ZH.confidenceZh[level] || level
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// 摘要行建议短句:只带对象与方向(置信度在摘要行独立成列,不再并入一句)。
|
|
2932
|
+
function adviceSuggestionShort(a) {
|
|
2933
|
+
const object = typeof a.objectText === 'string' && a.objectText !== '' ? a.objectText : ''
|
|
2934
|
+
if (object === '') return directionZh(a.suggestion)
|
|
2935
|
+
const word = a.suggestion === '降' ? ZH.adviceDirShortDown : a.suggestion === '升' ? ZH.adviceDirShortUp : ''
|
|
2936
|
+
return ZH.adviceSuggestionPrefix + object + (word !== '' ? '(' + word + ')' : '')
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
// 摘要行对象文本:已有方案「方案:名称」;无方案「手动配置的派发(含自定义人格…)」
|
|
2940
|
+
// 并附「无方案」小标签(特征取 profileKeyZh,与候选/对应关系同一口径)。
|
|
2941
|
+
function adviceSummaryObjectText(profile, profileKey) {
|
|
2942
|
+
if (profile !== null) return adviceObjectTitleOf(profile)
|
|
2943
|
+
const features = profileKeyZh(profileKey).split(' · ').filter((part) => part !== '未存方案' && part !== '未指定方案特征').join(' · ')
|
|
2944
|
+
return adviceObjectTitleOf(null) + (features !== '' ? '(' + features + ')' : '')
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
// 卡头标题 = 建议对象:已有方案用「方案:名称」,未匹配用「手动配置的派发」。
|
|
2948
|
+
function adviceObjectTitleOf(profile) {
|
|
2949
|
+
return profile !== null ? ZH.adviceTitleProfilePrefix + (profile.name || profile.id) : ZH.adviceTitleNoProfile
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// 会话/子 Agent 标识截断(悬停由原生 title 保留全文;不足长度原样)。
|
|
2953
|
+
function sessionIdShortOf(id) {
|
|
2954
|
+
const text = typeof id === 'string' ? id : ''
|
|
2955
|
+
return text.length > 12 ? text.slice(0, 12) + '…' : text
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
function childIdShortOf(id) {
|
|
2959
|
+
const text = typeof id === 'string' ? id : ''
|
|
2960
|
+
return text.length > 8 ? text.slice(0, 8) + '…' : text
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// 派发模式中文(前台/后台/持久);未知值原样(fail-soft)。
|
|
2964
|
+
function dispatchModeZh(mode) {
|
|
2965
|
+
if (mode === 'background') return ZH.modeBackground
|
|
2966
|
+
if (mode === 'continuable') return ZH.bannerModePersistent
|
|
2967
|
+
if (mode === 'foreground') return ZH.modeForeground
|
|
2968
|
+
return mode
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
// 表现摘要拆两层:计数(完成/失败/终止)与指标(加权成功分/平均耗时/平均输出),
|
|
2972
|
+
// 各渲染一遍;解析不出的部分原样保留(信息不删)。
|
|
2973
|
+
function adviceEvidenceOf(performanceText) {
|
|
2974
|
+
const text = typeof performanceText === 'string' ? performanceText : ''
|
|
2975
|
+
const withoutWindow = text.replace(/^过去 \d+ 次:/, '')
|
|
2976
|
+
const parts = withoutWindow.split(',').map((part) => part.trim()).filter((part) => part !== '')
|
|
2977
|
+
return { counts: parts.length > 0 ? parts[0] : '', metrics: parts.slice(1) }
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
// 候选安全检查四项:缩进列表对齐折叠文字下方(margin-left 16px),人话映射;
|
|
2981
|
+
// 只渲染四项本身,不重复「安全检查:全部通过 ✓」标题行(标题 = 折叠行)。
|
|
2982
|
+
function buildCandidateSafetyChecks(el, preview) {
|
|
2983
|
+
const checks = Array.isArray(preview.checks) ? preview.checks : []
|
|
2984
|
+
return el('ul', { className: 'sap-candidateSafetyList' },
|
|
2985
|
+
checks.map((c) => el('li', { key: String(c.name), className: 'sap-candidateSafetyItem', title: c.name },
|
|
2986
|
+
(c.verdict === 'pass' ? '✅ ' : '❌ ') + (CHECK_ZH[c.name] || c.name) + (typeof c.reason === 'string' && c.reason !== '' ? ':' + c.reason : '')
|
|
2987
|
+
))
|
|
2988
|
+
)
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// 候选改动项:候选 config 与建议来源 profileKey 对比成人话列表(不加前缀,
|
|
2992
|
+
// 展开区语境已是改动清单)。
|
|
2993
|
+
// persona/toolFilter 从键里消失 = 去掉该结构;其余字段值不同 = 换成新值。
|
|
2994
|
+
function candidateChangesOf(profileKey, cfg) {
|
|
2995
|
+
const config = cfg !== null && typeof cfg === 'object' ? cfg : {}
|
|
2996
|
+
const text = typeof profileKey === 'string' ? profileKey : ''
|
|
2997
|
+
const seg = {}
|
|
2998
|
+
for (const part of text.split('|')) {
|
|
2999
|
+
const idx = part.indexOf(':')
|
|
3000
|
+
if (idx >= 0) seg[part.slice(0, idx)] = part.slice(idx + 1)
|
|
3001
|
+
}
|
|
3002
|
+
const changes = []
|
|
3003
|
+
const changed = (field, value) => typeof value === 'string' && value !== '' && seg[field] !== value
|
|
3004
|
+
if (seg.persona === '1' && config.persona_present !== true) changes.push('去掉自定义人格')
|
|
3005
|
+
if (seg.toolFilter === '1' && config.toolFilter_present !== true) changes.push('去掉工具过滤')
|
|
3006
|
+
if (changed('preset', config.preset)) changes.push('预设 → ' + config.preset)
|
|
3007
|
+
if (changed('model', config.model)) changes.push('模型 → ' + config.model)
|
|
3008
|
+
if (changed('effort', config.reasoningEffort)) changes.push('推理强度 → ' + config.reasoningEffort)
|
|
3009
|
+
if (typeof config.maxTokens === 'number' && config.maxTokens > 0) changes.push('token 上限 → ' + String(config.maxTokens))
|
|
3010
|
+
return changes
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
// 建议大卡(v16):卡头只留标题 + 右侧「开启建议」开关;卡体顶部 = 管理子卡
|
|
3014
|
+
// (浅底紧凑,仅候选时效行 + 全局行,就地浮层随行);下方 = 建议列表。
|
|
3015
|
+
function buildAdvicePanel(el, s, actions, sessions) {
|
|
2513
3016
|
const advice = Array.isArray(s.advice) ? s.advice : []
|
|
2514
3017
|
const global = s.adviceGlobal !== null && typeof s.adviceGlobal === 'object' ? s.adviceGlobal : null
|
|
2515
|
-
const
|
|
3018
|
+
const candidates = Array.isArray(s.evolutionCandidates) ? s.evolutionCandidates : []
|
|
3019
|
+
const candidatesOf = (profileKey) => candidates.filter((c) => c && typeof c === 'object' && c.profile_key === profileKey)
|
|
2516
3020
|
if (global === null) return null
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
el('
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
: el('span', { className: 'sap-metaValueDefault' }, ZH.adviceRowNoMatch)
|
|
2530
|
-
)
|
|
2531
|
-
),
|
|
2532
|
-
el('p', { className: 'sap-desc' }, a.performanceText + ';' + directionZh(a.suggestion) + '(' + ZH.adviceConfidence + ' ' + a.confidence + ')')
|
|
3021
|
+
return el('div', { className: 'sap-card sap-cardStatic' },
|
|
3022
|
+
el('div', { className: 'sap-cardHead sap-adviceCardHead' },
|
|
3023
|
+
el('div', { className: 'sap-adviceHeadTitleRow' },
|
|
3024
|
+
el('div', { className: 'sap-cardTitleBox' },
|
|
3025
|
+
el('div', { className: 'sap-cardTitle' }, ZH.adviceTitle)
|
|
3026
|
+
),
|
|
3027
|
+
el('span', { className: 'sap-rowActions' },
|
|
3028
|
+
ZH.adviceEnableLabel,
|
|
3029
|
+
el('label', { className: 'sap-switch', title: ZH.adviceTitle },
|
|
3030
|
+
el('input', { type: 'checkbox', checked: s.evolutionAdvice, onChange: actions.toggleAdvice }),
|
|
3031
|
+
el('span', null, s.evolutionAdvice ? ZH.adviceSwitchOn : ZH.adviceSwitchOff)
|
|
3032
|
+
)
|
|
2533
3033
|
)
|
|
2534
|
-
|
|
3034
|
+
),
|
|
3035
|
+
el('div', { className: 'sap-cardDesc' }, ZH.advicePanelDesc)
|
|
3036
|
+
),
|
|
3037
|
+
el('div', { className: 'sap-cardBody' },
|
|
3038
|
+
el('div', { className: 'sap-adviceManageCard' },
|
|
3039
|
+
buildCandidateSettingsRow(el, s, actions),
|
|
3040
|
+
el('p', { className: 'sap-adviceGlobalLine' }, ZH.advicePanelGlobal + ':累计 ' + global.total + ' 次(完成 ' + global.completed + '、失败 ' + global.failed + '、终止 ' + global.killed + ')' + ZH.advicePanelReadonly),
|
|
3041
|
+
s.candidateSettingsOpen === true ? buildCandidateSettingsPanel(el, s, actions) : null
|
|
3042
|
+
),
|
|
3043
|
+
advice.length === 0
|
|
3044
|
+
? el('p', { className: 'sap-desc' }, ZH.advicePanelEmpty)
|
|
3045
|
+
: el('ul', { className: 'sap-rows' },
|
|
3046
|
+
advice.map((a) => buildAdviceCard(el, s, actions, a, candidatesOf(a.profileKey), sessions))
|
|
3047
|
+
)
|
|
2535
3048
|
)
|
|
2536
3049
|
)
|
|
2537
3050
|
}
|
|
2538
3051
|
|
|
3052
|
+
// 详情小节:纯文字加粗小标题(12px label-primary)+ 内容(13px),无左线/无底块。
|
|
3053
|
+
function adviceSectionOf(el, title, children) {
|
|
3054
|
+
return el('div', { className: 'sap-adviceSection' },
|
|
3055
|
+
el('p', { className: 'sap-adviceSectionTitle' }, title),
|
|
3056
|
+
children
|
|
3057
|
+
)
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
// 单张建议卡(v10):摘要行 = 对象/建议/置信度/▸(结论三件套,无加权分);
|
|
3061
|
+
// 展开区 = 小节序列 理由 → 近期表现 → 派发明细 → 候选方案(未应用)→ 操作区。
|
|
3062
|
+
function buildAdviceCard(el, s, actions, a, candidates, sessions) {
|
|
3063
|
+
const profile = matchingProfileOf(s.profiles, a.profileKey)
|
|
3064
|
+
const metrics = adviceMetricsOf(a.performanceText)
|
|
3065
|
+
const rows = s.adviceDetails && typeof s.adviceDetails === 'object' && Array.isArray(s.adviceDetails[a.profileKey]) ? s.adviceDetails[a.profileKey] : []
|
|
3066
|
+
const openMap = s.adviceOpen !== null && typeof s.adviceOpen === 'object' ? s.adviceOpen : {}
|
|
3067
|
+
const open = openMap[a.profileKey] === true
|
|
3068
|
+
const summary = el('button', { type: 'button', className: 'sap-adviceSummary', 'aria-expanded': open, onClick: actions.toggleAdviceOpen(a.profileKey) },
|
|
3069
|
+
el('span', { className: 'sap-adviceObjLine' },
|
|
3070
|
+
el('span', { className: 'sap-adviceObj' }, adviceSummaryObjectText(profile, a.profileKey)),
|
|
3071
|
+
profile === null ? el('span', { className: 'sap-adviceNoProfileTag' }, ZH.adviceNoProfileTag) : null
|
|
3072
|
+
),
|
|
3073
|
+
el('span', { className: 'sap-adviceSummaryLine' },
|
|
3074
|
+
el('span', { className: 'sap-adviceSummaryText' }, adviceSuggestionShort(a)),
|
|
3075
|
+
el('span', { className: 'sap-adviceConf' }, ZH.adviceConfidence + confidenceZhOf(a.confidence)),
|
|
3076
|
+
el('span', { className: 'sap-adviceSummaryArrow' }, '▸')))
|
|
3077
|
+
const detail = open !== true ? null : el('div', { className: 'sap-adviceDetail' },
|
|
3078
|
+
typeof a.reasonText === 'string' && a.reasonText !== '' ? adviceSectionOf(el, ZH.adviceSectionReason, el('p', { className: 'sap-adviceSectionBody' }, a.reasonText)) : null,
|
|
3079
|
+
metrics.length > 0 ? adviceSectionOf(el, ZH.adviceSectionPerf, el('p', { className: 'sap-adviceSectionBody' }, metrics.join(' · '))) : null,
|
|
3080
|
+
adviceSectionOf(el, ZH.adviceSectionDetail, buildAdviceDetails(el, s, actions, a.profileKey, rows, sessions)),
|
|
3081
|
+
candidates.length > 0 ? buildAdviceCandidates(el, s, actions, a, candidates) : null,
|
|
3082
|
+
buildAdviceFoot(el, s, actions, a, profile, candidates))
|
|
3083
|
+
return el('li', { key: String(a.profileKey), className: 'sap-adviceItem' + (open === true ? ' sap-adviceItemOpen' : '') }, summary, detail)
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// 近期派发明细折叠行:▸ 近期派发明细 · N 次(点击展开)→ 六列表格;默认收起。
|
|
3087
|
+
function buildAdviceDetails(el, s, actions, profileKey, rows, sessions) {
|
|
3088
|
+
const map = s.detailOpen !== null && typeof s.detailOpen === 'object' ? s.detailOpen : {}
|
|
3089
|
+
const open = map[profileKey] === true
|
|
3090
|
+
const header = el('tr', null,
|
|
3091
|
+
el('th', null, ZH.adviceDetailColTime), el('th', null, ZH.adviceDetailColSession), el('th', null, ZH.adviceDetailColChild),
|
|
3092
|
+
el('th', null, ZH.adviceDetailColOutcome), el('th', null, ZH.adviceDetailColElapsed), el('th', null, ZH.adviceDetailColMode))
|
|
3093
|
+
const cell = (row) => {
|
|
3094
|
+
const outcomeZh = ZH.dispatchOutcomeZh[row.outcome] || row.outcome || ''
|
|
3095
|
+
const bad = row.outcome === 'failed' || row.outcome === 'killed'
|
|
3096
|
+
const title = reminderSessionTitle(sessions, row.sessionId)
|
|
3097
|
+
return el('tr', { key: String(row.ts ?? '') + String(row.childId ?? '') },
|
|
3098
|
+
el('td', { className: 'sap-adviceDetailTime' }, formatMonthDayTime(row.ts)),
|
|
3099
|
+
el('td', null, title !== '' ? el('span', { className: 'sap-adviceDetailSession' }, title) : null,
|
|
3100
|
+
sessionIdShortOf(row.sessionId) !== '' ? el('span', { className: 'sap-adviceDetailSessionId' }, sessionIdShortOf(row.sessionId)) : null),
|
|
3101
|
+
el('td', { className: 'sap-adviceDetailChild' }, childIdShortOf(row.childId)),
|
|
3102
|
+
el('td', { className: bad ? 'sap-warn-x' : '' }, outcomeZh),
|
|
3103
|
+
el('td', null, formatDurationMs(row.elapsedMs)),
|
|
3104
|
+
el('td', null, dispatchModeZh(row.mode)))
|
|
3105
|
+
}
|
|
3106
|
+
return el('div', { className: 'sap-adviceDetailBlock' },
|
|
3107
|
+
el('button', { type: 'button', className: 'sap-adviceDetailToggle', 'aria-expanded': open, onClick: actions.toggleDetail(profileKey) },
|
|
3108
|
+
el('span', null, ZH.adviceDetailTogglePrefix + rows.length + ZH.adviceDetailToggleSuffix),
|
|
3109
|
+
el('span', { className: 'sap-adviceDetailArrow' }, '▸')),
|
|
3110
|
+
open !== true ? null : (rows.length === 0
|
|
3111
|
+
? el('p', { className: 'sap-candidateHint' }, ZH.adviceDetailEmpty)
|
|
3112
|
+
: el('table', { className: 'sap-adviceDetailTable' }, el('thead', null, header), el('tbody', null, rows.map(cell))))
|
|
3113
|
+
)
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// 候选管理设置(顶部一行 + 就地浮层):更新模式与有效期用户可配;
|
|
3117
|
+
// 自定义按天数输入(1~365),提交时换算小时数。
|
|
3118
|
+
function candidateModeTextOf(mode) {
|
|
3119
|
+
return mode === 'manual' ? ZH.candidateModeManual : ZH.candidateModeAuto
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
function candidateTtlTextOf(ttlH) {
|
|
3123
|
+
if (ttlH === 0) return ZH.candidateTtl0
|
|
3124
|
+
if (ttlH === 24) return ZH.candidateTtl24
|
|
3125
|
+
if (ttlH === 72) return ZH.candidateTtl72
|
|
3126
|
+
if (ttlH === 168) return ZH.candidateTtl168
|
|
3127
|
+
return ttlH + ' 小时'
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
function candidateTtlChoiceOf(ttlH) {
|
|
3131
|
+
return ttlH === 0 || ttlH === 24 || ttlH === 72 || ttlH === 168 ? String(ttlH) : 'custom'
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
function buildCandidateSettingsRow(el, s, actions) {
|
|
3135
|
+
const settings = s.candidateSettings !== null && typeof s.candidateSettings === 'object' ? s.candidateSettings : { candidateMode: 'auto', candidateTtlH: 24 }
|
|
3136
|
+
return el('p', { className: 'sap-candidateSettingsRow' },
|
|
3137
|
+
el('b', null, ZH.candidateSettingsRow),
|
|
3138
|
+
el('span', null, candidateModeTextOf(settings.candidateMode) + ' · ' + ZH.candidateTtlLabel + ' ' + candidateTtlTextOf(settings.candidateTtlH)),
|
|
3139
|
+
el('button', { type: 'button', className: 'sap-candidateSettingsEdit', onClick: actions.toggleCandidateSettings }, ZH.candidateSettingsEdit))
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
function candidateSettingsRadio(el, label, value, current, onChange) {
|
|
3143
|
+
return el('label', { key: value, className: 'sap-candidateSettingsOption' },
|
|
3144
|
+
el('input', { type: 'radio', checked: current === value, onChange }),
|
|
3145
|
+
el('span', null, label))
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
function buildCandidateSettingsPanel(el, s, actions) {
|
|
3149
|
+
const form = s.candidateSettingsForm !== null && typeof s.candidateSettingsForm === 'object' ? s.candidateSettingsForm : {}
|
|
3150
|
+
const mode = form.candidateMode === 'manual' ? 'manual' : 'auto'
|
|
3151
|
+
const ttlChoice = typeof form.candidateTtlChoice === 'string' ? form.candidateTtlChoice : '24'
|
|
3152
|
+
const ttlOptions = [['24', ZH.candidateTtl24], ['72', ZH.candidateTtl72], ['168', ZH.candidateTtl168], ['0', ZH.candidateTtl0], ['custom', ZH.candidateTtlCustom]]
|
|
3153
|
+
return el('div', { className: 'sap-candidateSettingsPanel' },
|
|
3154
|
+
el('div', { className: 'sap-candidateSettingsField' },
|
|
3155
|
+
el('span', { className: 'sap-fieldLabel' }, ZH.candidateSettingsModeLabel),
|
|
3156
|
+
el('div', { className: 'sap-candidateSettingsOptions' },
|
|
3157
|
+
candidateSettingsRadio(el, ZH.candidateModeAuto + '(推荐)', 'auto', mode, actions.setCandidateFormMode('auto')),
|
|
3158
|
+
candidateSettingsRadio(el, ZH.candidateModeManual, 'manual', mode, actions.setCandidateFormMode('manual'))),
|
|
3159
|
+
el('p', { className: 'sap-metaValueDefault' }, mode === 'manual' ? ZH.candidateSettingsModeManualText : ZH.candidateSettingsModeAutoText)),
|
|
3160
|
+
el('div', { className: 'sap-candidateSettingsField' },
|
|
3161
|
+
el('span', { className: 'sap-fieldLabel' }, ZH.candidateTtlLabel),
|
|
3162
|
+
el('div', { className: 'sap-candidateSettingsOptions' },
|
|
3163
|
+
ttlOptions.map(([value, label]) => candidateSettingsRadio(el, label, value, ttlChoice, actions.setCandidateFormTtl(value)))),
|
|
3164
|
+
ttlChoice === 'custom'
|
|
3165
|
+
? el('div', { className: 'sap-candidateSettingsCustom' },
|
|
3166
|
+
el('input', { className: 'sap-input', value: typeof form.customDays === 'string' ? form.customDays : '', placeholder: '1~365', onChange: (event) => actions.setCandidateFormCustomDays(event && event.target ? event.target.value : '') }),
|
|
3167
|
+
el('span', { className: 'sap-metaValueDefault' }, ZH.candidateSettingsCustomLabel))
|
|
3168
|
+
: null,
|
|
3169
|
+
form.error !== '' ? el('p', { className: 'sap-notice' }, form.error) : null),
|
|
3170
|
+
el('div', { className: 'sap-candidateSettingsActions' },
|
|
3171
|
+
el('button', { type: 'button', className: 'sap-primaryButton', onClick: actions.saveCandidateSettings }, ZH.candidateSettingsSave),
|
|
3172
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', onClick: actions.cancelCandidateSettings }, ZH.candidateSettingsCancel)))
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
// 候选方案区(v10 小节):纯文字加粗小节标题「候选方案(未应用)」+ 上分隔线 →
|
|
3176
|
+
// 数量/单选说明行(手动维护附重新生成)→ 候选项列表(行点击只展开,radio 只选中)。
|
|
3177
|
+
function buildAdviceCandidates(el, s, actions, a, candidates) {
|
|
3178
|
+
const manual = s.candidateSettings !== null && typeof s.candidateSettings === 'object' && s.candidateSettings.candidateMode === 'manual'
|
|
3179
|
+
const selected = candidateSelectedIdOf(s, a.profileKey, candidates)
|
|
3180
|
+
return el('div', { className: 'sap-candidates' },
|
|
3181
|
+
el('p', { className: 'sap-candidatesTitle' }, ZH.candidateTitle, el('span', { className: 'sap-candidatesNotApplied' }, ZH.candidateNotApplied)),
|
|
3182
|
+
el('p', { className: 'sap-candidatesCount' }, candidates.length + ZH.candidateCountSuffix + ZH.candidateCountNote),
|
|
3183
|
+
manual === true
|
|
3184
|
+
? el('button', { type: 'button', className: 'sap-detailToggle', onClick: actions.regenerateCandidates(a.profileKey) }, ZH.candidateRegenerate)
|
|
3185
|
+
: null,
|
|
3186
|
+
candidates.map((candidate, index) => buildAdviceCandidateRow(el, s, actions, a, candidate, index, selected))
|
|
3187
|
+
)
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
// 默认选中第一条候选;用户选择存进状态(仅选中,行点击只展开)。
|
|
3191
|
+
function candidateSelectedIdOf(s, profileKey, candidates) {
|
|
3192
|
+
const map = s.candidateSelected !== null && typeof s.candidateSelected === 'object' ? s.candidateSelected : {}
|
|
3193
|
+
const current = typeof map[profileKey] === 'string' ? map[profileKey] : ''
|
|
3194
|
+
if (current !== '' && candidates.some((c) => c && c.id === current)) return current
|
|
3195
|
+
return candidates.length > 0 ? candidates[0].id : ''
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
function buildAdviceCandidateRow(el, s, actions, a, candidate, index, selected) {
|
|
3199
|
+
const cfg = candidate.config && typeof candidate.config === 'object' ? candidate.config : {}
|
|
3200
|
+
const openState = s.candidateOpen !== null && typeof s.candidateOpen === 'object' ? s.candidateOpen : {}
|
|
3201
|
+
const safetyState = s.candidateSafetyOpen !== null && typeof s.candidateSafetyOpen === 'object' ? s.candidateSafetyOpen : {}
|
|
3202
|
+
const open = openState[candidate.id] === true
|
|
3203
|
+
const safetyOpen = safetyState[candidate.id] === true
|
|
3204
|
+
const preview = s.draftPreviews && typeof s.draftPreviews === 'object' ? s.draftPreviews[candidate.id] : undefined
|
|
3205
|
+
const changes = candidateChangesOf(a.profileKey, cfg)
|
|
3206
|
+
const onRadio = (event) => {
|
|
3207
|
+
if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
3208
|
+
actions.selectCandidate(a.profileKey, candidate.id)()
|
|
3209
|
+
}
|
|
3210
|
+
return el('div', { key: String(candidate.id), className: 'sap-candidate' + (selected === candidate.id ? ' sap-candidateSelected' : '') },
|
|
3211
|
+
el('button', { type: 'button', className: 'sap-candidateRow', 'aria-expanded': open, onClick: actions.toggleCandidateOpen(candidate) },
|
|
3212
|
+
el('span', { role: 'radio', 'aria-checked': selected === candidate.id, className: 'sap-candidateRadio', onClick: onRadio }),
|
|
3213
|
+
el('span', { className: 'sap-candidateName' }, '候选 ' + (index + 1) + ' · ' + candidate.name),
|
|
3214
|
+
el('span', { className: 'sap-candidateValidity' }, candidateValidityText(candidate.expiry)),
|
|
3215
|
+
el('span', { className: 'sap-candidateArrow' }, '▸')),
|
|
3216
|
+
open === true ? el('div', { className: 'sap-candidateBody' },
|
|
3217
|
+
changes.length > 0 ? el('div', { className: 'sap-candidateGroup' },
|
|
3218
|
+
el('p', { className: 'sap-candidateGroupTitle' }, ZH.candidateGroupChanges),
|
|
3219
|
+
el('ul', { className: 'sap-candidateChanges' },
|
|
3220
|
+
changes.map((change) => el('li', { key: change, className: 'sap-candidateChange' }, change)))) : null,
|
|
3221
|
+
preview !== undefined
|
|
3222
|
+
? el('div', { className: 'sap-candidateGroup' },
|
|
3223
|
+
el('button', { type: 'button', className: 'sap-candidateSafetyToggle' + (safetyOpen === true ? ' sap-candidateSafetyToggleOpen' : ''), onClick: actions.toggleCandidateSafety(candidate) },
|
|
3224
|
+
ZH.candidateSafetyPrefix + (preview.ok === true ? ZH.candidateSafetyPass : ZH.candidateSafetyFail),
|
|
3225
|
+
el('span', { className: 'sap-candidateSafetyArrow' }, '▸')),
|
|
3226
|
+
safetyOpen === true ? buildCandidateSafetyChecks(el, preview) : null)
|
|
3227
|
+
: el('div', { className: 'sap-candidateGroup' },
|
|
3228
|
+
el('p', { className: 'sap-candidateHint' }, ZH.candidateSafetyPending)),
|
|
3229
|
+
el('div', { className: 'sap-candidateGroup' },
|
|
3230
|
+
el('p', { className: 'sap-candidateGroupTitle' }, ZH.candidateGroupGenerated),
|
|
3231
|
+
candidateGeneratedLine(el, candidate),
|
|
3232
|
+
el('p', { className: 'sap-candidateHint' }, ZH.candidateNotApplicable))) : null)
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
// 生成信息行(最浅灰,两分支模板):有样本 = 生成于 MM-DD · 基于最近 N 次派发 ·
|
|
3236
|
+
// 近期表现 X(满分 100,X=round(加权分×100));无样本 = 生成于 MM-DD · 暂无近期派发
|
|
3237
|
+
// 样本,仅供参考(不显示 0 分);无生成时间省去「生成于」段。
|
|
3238
|
+
function candidateGeneratedLine(el, candidate) {
|
|
3239
|
+
const basis = candidate.basis !== null && typeof candidate.basis === 'object' ? candidate.basis : {}
|
|
3240
|
+
const n = Number.isFinite(basis.n) ? basis.n : 0
|
|
3241
|
+
const score100 = Number.isFinite(basis.score) ? Math.round(basis.score * 100) : 0
|
|
3242
|
+
const generated = formatMonthDayTime(basis.generatedAt)
|
|
3243
|
+
const parts = []
|
|
3244
|
+
if (generated !== '') parts.push(ZH.candidateGeneratedPrefix + generated)
|
|
3245
|
+
if (n >= 1) {
|
|
3246
|
+
parts.push(ZH.candidateSamplesPrefix + n + ZH.candidateSamplesSuffix)
|
|
3247
|
+
parts.push(ZH.candidatePerfPrefix + score100 + ZH.candidatePerfSuffix)
|
|
3248
|
+
} else {
|
|
3249
|
+
parts.push(ZH.candidateNoSample)
|
|
3250
|
+
}
|
|
3251
|
+
return el('p', { className: 'sap-candidateHint' }, parts.join(' · '))
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
// 卡底部:无对应方案 = [保存为方案](统一主按钮)+ 备注两行;有对应方案 =
|
|
3255
|
+
// 灰置[按建议调整方案](统一次级按钮)+ 定位;说明与「生成于…」拆两行不连排。
|
|
3256
|
+
function buildAdviceFoot(el, s, actions, a, profile, candidates) {
|
|
3257
|
+
const basis = (Array.isArray(candidates) && candidates.length > 0 && candidates[0] !== null && typeof candidates[0] === 'object' && candidates[0].basis !== null && typeof candidates[0].basis === 'object') ? candidates[0].basis : {}
|
|
3258
|
+
const generatedAt = formatMonthDayTime(basis.generatedAt)
|
|
3259
|
+
const generatedText = generatedAt !== '' ? ZH.candidateGeneratedPrefix + generatedAt : ''
|
|
3260
|
+
const noteLines = (line) => el('div', { className: 'sap-adviceFootNotes' },
|
|
3261
|
+
el('p', { className: 'sap-adviceFootNote' }, line),
|
|
3262
|
+
generatedText !== '' ? el('p', { className: 'sap-adviceFootNote' }, generatedText) : null)
|
|
3263
|
+
if (profile !== null) {
|
|
3264
|
+
return el('div', { className: 'sap-adviceFoot' },
|
|
3265
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, disabled: true, title: ZH.candidateNotApplicable }, ZH.adviceApplyDisabled),
|
|
3266
|
+
el('button', { type: 'button', className: 'sap-detailToggle', onClick: actions.locateProfile(profile.id) }, ZH.adviceRowLocate),
|
|
3267
|
+
noteLines(ZH.adviceFootNoteMatchedPrefix + (profile.name || profile.id) + '(' + profile.id + ')'))
|
|
3268
|
+
}
|
|
3269
|
+
return el('div', { className: 'sap-adviceFoot' },
|
|
3270
|
+
el('button', { type: 'button', className: 'sap-primaryButton', style: smallBtn, onClick: actions.saveAdviceProfile(a.profileKey) }, ZH.adviceSaveProfile),
|
|
3271
|
+
noteLines(ZH.adviceFootNoteSave))
|
|
3272
|
+
}
|
|
3273
|
+
|
|
2539
3274
|
// 逃生舱卡片:开关 + (打开时)显式警告文案 + 放行集增删(输入框 + 添加按钮,
|
|
2540
3275
|
// 每项「移除」按钮)。增删走 /escape-add /escape-remove,成功后用响应 presets 刷新。
|
|
2541
3276
|
function buildEscapeSection(el, s, actions) {
|
|
2542
3277
|
const list = Array.isArray(s.escapePresets) ? s.escapePresets : []
|
|
2543
|
-
return el('div', { className: 'sap-
|
|
2544
|
-
el('div', { className: 'sap-
|
|
2545
|
-
el('
|
|
2546
|
-
el('
|
|
3278
|
+
return el('div', { className: 'sap-card sap-cardStatic' },
|
|
3279
|
+
el('div', { className: 'sap-cardHead' },
|
|
3280
|
+
el('div', { className: 'sap-cardTitleBox' },
|
|
3281
|
+
el('div', { className: 'sap-cardTitle' }, ZH.escapeTitle),
|
|
3282
|
+
el('div', { className: 'sap-cardDesc' }, ZH.escapeCardDesc)
|
|
2547
3283
|
),
|
|
2548
3284
|
el('span', { className: 'sap-rowActions' },
|
|
2549
3285
|
el('label', { className: 'sap-switch', title: '逃生舱开关' },
|
|
@@ -2552,26 +3288,28 @@ function buildEscapeSection(el, s, actions) {
|
|
|
2552
3288
|
)
|
|
2553
3289
|
)
|
|
2554
3290
|
),
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
el('
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
3291
|
+
el('div', { className: 'sap-cardBody' },
|
|
3292
|
+
s.escapeEnabled
|
|
3293
|
+
? el('p', { className: 'sap-notice', role: 'alert' }, ZH.escapeWarn)
|
|
3294
|
+
: null,
|
|
3295
|
+
el('div', { className: 'sap-escapeAdd' },
|
|
3296
|
+
el('input', {
|
|
3297
|
+
className: 'sap-input',
|
|
3298
|
+
style: { flex: '1 1 0', minWidth: '160px' },
|
|
3299
|
+
value: s.escapeInput,
|
|
3300
|
+
placeholder: ZH.escapeInputPlaceholder,
|
|
3301
|
+
onChange: (event) => s.setEscapeInput(event && event.target ? event.target.value : '')
|
|
3302
|
+
}),
|
|
3303
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.escapeAdd }, ZH.escapeAdd)
|
|
3304
|
+
),
|
|
3305
|
+
el('div', { className: 'sap-chips' },
|
|
3306
|
+
list.length === 0
|
|
3307
|
+
? el('span', { className: 'sap-chipValueDim' }, ZH.escapeEmpty)
|
|
3308
|
+
: list.map((id) => el('span', { key: String(id), className: 'sap-chip sap-chipWarn' },
|
|
3309
|
+
String(id),
|
|
3310
|
+
el('button', { type: 'button', className: 'sap-chipRemove', title: ZH.escapeRemove, onClick: actions.escapeRemove(id) }, ZH.escapeRemove)
|
|
3311
|
+
))
|
|
3312
|
+
)
|
|
2575
3313
|
)
|
|
2576
3314
|
)
|
|
2577
3315
|
}
|
|
@@ -2596,8 +3334,8 @@ function buildSettingsCard(el, key, title, desc, open, toggle, body, isStatic) {
|
|
|
2596
3334
|
)
|
|
2597
3335
|
}
|
|
2598
3336
|
|
|
2599
|
-
//
|
|
2600
|
-
//
|
|
3337
|
+
// 审计健康状态行:health + 审计丢失(health 唯一位置;丢失 >0 红字)。目录/告警
|
|
3338
|
+
// 计数不重复(版本告警正文在维护页签,一个数字只出现一次);现挂在安全页签审计健康卡。
|
|
2601
3339
|
function buildStatusBar(el, s) {
|
|
2602
3340
|
const degraded = s.audit.health === 'degraded'
|
|
2603
3341
|
const lost = s.audit.lostTelemetry + s.audit.lostGovernance
|
|
@@ -2617,13 +3355,14 @@ function buildSectionNotices(el, s) {
|
|
|
2617
3355
|
}
|
|
2618
3356
|
|
|
2619
3357
|
// draft 安全检查预览结果:ok 全绿/存在拒绝,逐项展示 verdict + reason。
|
|
3358
|
+
// 检查项名走 CHECK_ZH 人话映射(可见面零内部术语),title 保留原串供排查。
|
|
2620
3359
|
function buildDraftPreview(el, preview) {
|
|
2621
3360
|
const checks = Array.isArray(preview.checks) ? preview.checks : []
|
|
2622
3361
|
return el('div', { className: 'sap-draftPreview' },
|
|
2623
3362
|
el('p', { className: preview.ok ? 'sap-savedNotice' : 'sap-warn', role: 'status' }, preview.ok ? ZH.draftPreviewPass : ZH.draftPreviewFail),
|
|
2624
3363
|
el('ul', { className: 'sap-rows' },
|
|
2625
3364
|
checks.map((c) => el('li', { key: String(c.name), className: 'sap-rowCard' },
|
|
2626
|
-
el('div', { className: 'sap-rowHead' }, el('span', { className: 'sap-rowName' }, (c.verdict === 'pass' ? '✅ ' : '❌ ') + c.name)),
|
|
3365
|
+
el('div', { className: 'sap-rowHead' }, el('span', { className: 'sap-rowName', title: c.name }, (c.verdict === 'pass' ? '✅ ' : '❌ ') + (CHECK_ZH[c.name] || c.name))),
|
|
2627
3366
|
c.reason ? el('p', { className: 'sap-desc' }, c.reason) : null
|
|
2628
3367
|
))
|
|
2629
3368
|
)
|
|
@@ -2722,41 +3461,76 @@ function buildCostObservabilitySection(el, s, actions) {
|
|
|
2722
3461
|
)
|
|
2723
3462
|
}
|
|
2724
3463
|
|
|
2725
|
-
//
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
3464
|
+
// 六页签骨架:横向 tab(下划线激活 + 加粗,样式对齐官方「插件」节)+ 内容区。
|
|
3465
|
+
function buildTabBar(el, s) {
|
|
3466
|
+
return el('div', { className: 'sap-tabs', role: 'tablist' },
|
|
3467
|
+
SETTINGS_TABS.map((tab) => el('button', {
|
|
3468
|
+
key: tab.key, type: 'button', role: 'tab', 'aria-selected': s.activeTab === tab.key,
|
|
3469
|
+
className: 'sap-tab' + (s.activeTab === tab.key ? ' sap-tabActive' : ''),
|
|
3470
|
+
onClick: () => s.setActiveTab(tab.key)
|
|
3471
|
+
}, tab.label))
|
|
2733
3472
|
)
|
|
2734
3473
|
}
|
|
2735
3474
|
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
3475
|
+
// 页签内容区:全部挂载、非激活隐藏(组件状态不因切换丢失)。
|
|
3476
|
+
function buildTabZone(el, s, key, children) {
|
|
3477
|
+
return el('div', { role: 'tabpanel', className: 'sap-zone' + (s.activeTab === key ? ' sap-zoneActive' : '') }, children)
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// 安全页签:三卡一律 sap-card(与①方案管理同款)——安全保证、逃生舱、
|
|
3481
|
+
// 审计健康(状态行 + 只读审计明细折叠表,明细表类 = sap-audit*)。
|
|
3482
|
+
function buildSafetySection(el, s, actions, reminders) {
|
|
2741
3483
|
return el('div', null,
|
|
2742
|
-
|
|
3484
|
+
buildSettingsCard(el, 5, ZH.safetyCardTitle, ZH.safetyCardDesc, true, () => {}, [
|
|
2743
3485
|
el('p', { className: 'sap-desc' }, ZH.advancedSafetyLine),
|
|
2744
|
-
|
|
2745
|
-
]),
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
3486
|
+
el('p', { className: 'sap-desc' }, ZH.safetyCheckNote)
|
|
3487
|
+
], true),
|
|
3488
|
+
buildEscapeSection(el, s, actions),
|
|
3489
|
+
buildSettingsCard(el, 6, ZH.auditCardTitle, ZH.auditCardDesc, true, () => {}, [
|
|
3490
|
+
buildStatusBar(el, s),
|
|
3491
|
+
buildAuditDetails(el, s, actions, reminders)
|
|
3492
|
+
], true)
|
|
3493
|
+
)
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
// 审计明细事件与结果人话(与提醒同源;未知类别原样回退,信息不删)。
|
|
3497
|
+
function auditEventText(r) {
|
|
3498
|
+
const text = ZH.auditEventZh[r.kind] || r.kind
|
|
3499
|
+
return r.severity === 'P0' ? text + '(紧急)' : text
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
function auditResultText(r) {
|
|
3503
|
+
if (typeof r.action === 'string' && r.action !== '') {
|
|
3504
|
+
const time = formatMonthDayTime(r.actedAt)
|
|
3505
|
+
return ZH.auditResultActedPrefix + (ZH.reminderActionZh[r.action] || r.action) + (time !== '' ? ' ' + time : '')
|
|
3506
|
+
}
|
|
3507
|
+
return r.unread === true ? ZH.auditResultPending : ZH.auditResultRead
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
// 审计明细折叠表(只读展示,无导出/过滤):最近 20 条治理留痕。
|
|
3511
|
+
function buildAuditDetails(el, s, actions, reminders) {
|
|
3512
|
+
const rows = Array.isArray(reminders) ? reminders.slice(0, 20) : []
|
|
3513
|
+
const cell = (r) => el('tr', { key: String(r.id) },
|
|
3514
|
+
el('td', { className: 'sap-auditTime' }, formatMonthDayTime(r.createdAt)),
|
|
3515
|
+
el('td', { className: 'sap-auditEvent' }, auditEventText(r)),
|
|
3516
|
+
el('td', { className: 'sap-auditObject' }, r.title + ' · ' + (typeof r.sessionId === 'string' && r.sessionId !== '' ? '会话 ' + r.sessionId.slice(0, 8) + '…' : ZH.reminderGlobal)),
|
|
3517
|
+
el('td', null, auditResultText(r)))
|
|
3518
|
+
return el('div', null,
|
|
3519
|
+
el('button', { type: 'button', className: 'sap-auditCollapse', 'aria-expanded': s.auditDetailOpen === true, onClick: actions.toggleAuditDetail },
|
|
3520
|
+
el('span', null, ZH.auditDetailToggle),
|
|
3521
|
+
el('span', { className: 'sap-auditArrow' }, '▸')),
|
|
3522
|
+
s.auditDetailOpen !== true ? null : (rows.length === 0
|
|
3523
|
+
? el('p', { className: 'sap-candidateHint' }, ZH.auditDetailEmpty)
|
|
3524
|
+
: [el('p', { className: 'sap-candidateHint' }, ZH.auditDetailScope),
|
|
3525
|
+
el('table', { className: 'sap-auditTable' },
|
|
3526
|
+
el('thead', null, el('tr', null,
|
|
3527
|
+
el('th', null, ZH.auditColTime), el('th', null, ZH.auditColEvent),
|
|
3528
|
+
el('th', null, ZH.auditColObject), el('th', null, ZH.auditColResult))),
|
|
3529
|
+
el('tbody', null, rows.map(cell)))])
|
|
2756
3530
|
)
|
|
2757
3531
|
}
|
|
2758
3532
|
|
|
2759
|
-
function buildSectionReturn(el, s, actions, rows, addCard,
|
|
3533
|
+
function buildSectionReturn(el, s, actions, rows, addCard, reminderCenter, sessions) {
|
|
2760
3534
|
const notices = buildSectionNotices(el, s)
|
|
2761
3535
|
return el('div', { className: 'sap-section' },
|
|
2762
3536
|
el('div', { className: 'sap-titleRow' },
|
|
@@ -2766,27 +3540,29 @@ function buildSectionReturn(el, s, actions, rows, addCard, openSections, toggleS
|
|
|
2766
3540
|
el('span', null, s.enabled ? '已启用' : '已禁用')
|
|
2767
3541
|
)
|
|
2768
3542
|
),
|
|
2769
|
-
buildStatusBar(el, s),
|
|
2770
3543
|
...notices,
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
el
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
3544
|
+
buildTabBar(el, s),
|
|
3545
|
+
buildTabZone(el, s, 'profiles',
|
|
3546
|
+
buildSettingsCard(el, 1, '方案管理', '管理派发可用的方案:启用、编辑、删除,含成本与成功率', true, () => {}, [
|
|
3547
|
+
el('p', { className: 'sap-intro' }, ZH.profilesIntro),
|
|
3548
|
+
el('ul', { className: 'sap-rows' }, rows),
|
|
3549
|
+
buildDraftRows(el, s, actions),
|
|
3550
|
+
el('div', { className: 'sap-addBlock' },
|
|
3551
|
+
s.adding ? addCard : el('div', { className: 'sap-addActions' },
|
|
3552
|
+
el('button', { className: 'sap-addButton', onClick: actions.openAdd }, '+ 新增 profile')
|
|
3553
|
+
)
|
|
3554
|
+
),
|
|
3555
|
+
el('div', { className: 'sap-addActions', style: { marginTop: '4px' } },
|
|
3556
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.resetAll }, ZH.resetAllBuiltins)
|
|
2778
3557
|
)
|
|
2779
|
-
),
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
buildSettingsCard(el, 3, '高级', '逃生舱、建议注入、通知中心与维护项', openSections[3] === true, toggleSection, [
|
|
2788
|
-
buildAdvancedSection(el, s, actions, reminderCenter)
|
|
2789
|
-
], false)
|
|
3558
|
+
], true)),
|
|
3559
|
+
buildTabZone(el, s, 'cost',
|
|
3560
|
+
buildSettingsCard(el, 2, '全局成本', '跨全部会话的派发成本汇总,按模型与方案拆分', true, () => {}, [
|
|
3561
|
+
buildCostObservabilitySection(el, s, actions)
|
|
3562
|
+
], true)),
|
|
3563
|
+
buildTabZone(el, s, 'advice', buildAdvicePanel(el, s, actions, sessions)),
|
|
3564
|
+
buildTabZone(el, s, 'safety', buildSafetySection(el, s, actions, s.auditReminders)),
|
|
3565
|
+
buildTabZone(el, s, 'reminders', reminderCenter)
|
|
2790
3566
|
)
|
|
2791
3567
|
}
|
|
2792
3568
|
|
|
@@ -2809,7 +3585,7 @@ function attachDraftActions(api, s, refresh, applyWrite, actions) {
|
|
|
2809
3585
|
applyWrite(data, '已应用 draft:' + draft.name)
|
|
2810
3586
|
// 闭环可视化:应用成功后展开方案管理区并高亮新方案卡片。
|
|
2811
3587
|
if (typeof data.id === 'string' && data.id !== '') {
|
|
2812
|
-
s.
|
|
3588
|
+
s.setActiveTab('profiles')
|
|
2813
3589
|
s.setHighlightId(data.id)
|
|
2814
3590
|
setTimeout(() => s.setHighlightId(null), 8000)
|
|
2815
3591
|
}
|
|
@@ -2825,41 +3601,44 @@ function attachDraftActions(api, s, refresh, applyWrite, actions) {
|
|
|
2825
3601
|
}
|
|
2826
3602
|
}
|
|
2827
3603
|
|
|
2828
|
-
function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, ReminderCenter) {
|
|
3604
|
+
function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, ReminderCenter, getSessions) {
|
|
2829
3605
|
function ProfilesSection() {
|
|
2830
3606
|
const [profiles, setProfiles] = useState([]); const [options, setOptions] = useState({ models: [], efforts: {}, presets: [] }); const [enabled, setEnabled] = useState(true)
|
|
2831
3607
|
const [adding, setAdding] = useState(false); const [editingId, setEditingId] = useState(null); const [error, setError] = useState('')
|
|
2832
3608
|
const [savedNotice, setSavedNotice] = useState(''); const [persistWarning, setPersistWarning] = useState(''); const [form, setForm] = useState(EMPTY_FORM)
|
|
2833
3609
|
const [tools, setTools] = useState([]); const [versionWarnings, setVersionWarnings] = useState([]); const [pluginVersion, setPluginVersion] = useState(''); const [audit, setAudit] = useState(AUDIT_DEFAULTS)
|
|
2834
3610
|
const [escapeEnabled, setEscapeEnabled] = useState(false); const [escapePresets, setEscapePresets] = useState([]); const [escapeInput, setEscapeInput] = useState('')
|
|
2835
|
-
const [evolutionAdvice, setEvolutionAdvice] = useState(false); const [drafts, setDrafts] = useState([]); const [advice, setAdvice] = useState([])
|
|
3611
|
+
const [evolutionAdvice, setEvolutionAdvice] = useState(false); const [drafts, setDrafts] = useState([]); const [advice, setAdvice] = useState([]); const [evolutionCandidates, setEvolutionCandidates] = useState([])
|
|
2836
3612
|
const [adviceGlobal, setAdviceGlobal] = useState(null); const [costSummary, setCostSummary] = useState({ global: null, byModel: [] }); const [draftPreviews, setDraftPreviews] = useState({})
|
|
2837
|
-
const [
|
|
2838
|
-
const [
|
|
2839
|
-
const
|
|
2840
|
-
const
|
|
3613
|
+
const [candidateOpen, setCandidateOpen] = useState({}); const [candidateSelected, setCandidateSelected] = useState({}); const [candidateSafetyOpen, setCandidateSafetyOpen] = useState({})
|
|
3614
|
+
const [candidateSettings, setCandidateSettings] = useState({ candidateMode: 'auto', candidateTtlH: 24 }); const [candidateSettingsOpen, setCandidateSettingsOpen] = useState(false)
|
|
3615
|
+
const [candidateSettingsForm, setCandidateSettingsForm] = useState({ candidateMode: 'auto', candidateTtlChoice: '24', customDays: '', error: '' }); const [adviceDetails, setAdviceDetails] = useState({}); const [detailOpen, setDetailOpen] = useState({})
|
|
3616
|
+
const [activeTab, setActiveTab] = useState('profiles'); const [highlightId, setHighlightId] = useState(null)
|
|
3617
|
+
const [adviceOpen, setAdviceOpen] = useState({}); const [auditDetailOpen, setAuditDetailOpen] = useState(false); const [auditReminders, setAuditReminders] = useState([])
|
|
3618
|
+
const s = { profiles, options, enabled, adding, editingId, error, savedNotice, persistWarning, form, tools, versionWarnings, pluginVersion, audit, escapeEnabled, escapePresets, escapeInput, evolutionAdvice, drafts, advice, evolutionCandidates, adviceGlobal, costSummary, draftPreviews, candidateOpen, candidateSelected, candidateSafetyOpen, candidateSettings, candidateSettingsOpen, candidateSettingsForm, adviceDetails, detailOpen, activeTab, highlightId, adviceOpen, auditDetailOpen, auditReminders, setProfiles, setOptions, setEnabled, setAdding, setEditingId, setError, setSavedNotice, setPersistWarning, setForm, setTools, setVersionWarnings, setPluginVersion, setAudit, setEscapeEnabled, setEscapePresets, setEscapeInput, setEvolutionAdvice, setDrafts, setAdvice, setEvolutionCandidates, setAdviceGlobal, setCostSummary, setDraftPreviews, setCandidateOpen, setCandidateSelected, setCandidateSafetyOpen, setCandidateSettings, setCandidateSettingsOpen, setCandidateSettingsForm, setAdviceDetails, setDetailOpen, setActiveTab, setHighlightId, setAdviceOpen, setAuditDetailOpen, setAuditReminders }
|
|
2841
3619
|
const refresh = useCallback(() => {
|
|
2842
3620
|
api('/list')
|
|
2843
3621
|
.then((data) => {
|
|
2844
3622
|
setProfiles(Array.isArray(data.profiles) ? data.profiles : [])
|
|
2845
3623
|
setAdvice(Array.isArray(data.advice) ? data.advice : [])
|
|
3624
|
+
setEvolutionCandidates(Array.isArray(data.evolutionCandidates) ? data.evolutionCandidates : [])
|
|
2846
3625
|
setAdviceGlobal(data.adviceGlobal !== null && typeof data.adviceGlobal === 'object' ? data.adviceGlobal : null)
|
|
3626
|
+
setAdviceDetails(data.adviceDetails !== null && typeof data.adviceDetails === 'object' ? data.adviceDetails : {})
|
|
2847
3627
|
setCostSummary(data && typeof data.costSummary === 'object' && data.costSummary !== null && typeof data.costSummary.global === 'object' ? { global: data.costSummary.global, byModel: Array.isArray(data.costSummary.byModel) ? data.costSummary.byModel : [] } : { global: null, byModel: [] })
|
|
2848
3628
|
})
|
|
2849
3629
|
.catch((err) => setError(String((err && err.message) || err)))
|
|
2850
3630
|
api('/drafts')
|
|
2851
3631
|
.then((data) => setDrafts(Array.isArray(data.drafts) ? data.drafts : []))
|
|
2852
3632
|
.catch(() => {})
|
|
3633
|
+
api('/reminders').then((data) => setAuditReminders(Array.isArray(data.reminders) ? data.reminders : [])).catch(() => {})
|
|
2853
3634
|
}, [])
|
|
2854
3635
|
const loadOptions = useCallback(() => {
|
|
2855
3636
|
api('/options/summary')
|
|
2856
|
-
.then((data) => applySummaryData(data, { enabled: setEnabled, options: setOptions, audit: setAudit, escapeEnabled: setEscapeEnabled, escapePresets: setEscapePresets, evolutionAdvice: setEvolutionAdvice }))
|
|
3637
|
+
.then((data) => applySummaryData(data, { enabled: setEnabled, options: setOptions, audit: setAudit, escapeEnabled: setEscapeEnabled, escapePresets: setEscapePresets, evolutionAdvice: setEvolutionAdvice, candidateSettings: setCandidateSettings }))
|
|
2857
3638
|
.catch(() => {})
|
|
2858
3639
|
}, [])
|
|
2859
|
-
const loadEfforts = makeEffortsLoader(api, setOptions)
|
|
2860
|
-
const
|
|
2861
|
-
// 版本探测只挂载时拉一次:useCallback([]) 稳定引用,防 effect 依赖恒变导致设置页打开期间无限重取。
|
|
2862
|
-
const loadVersions = useCallback(() => makeVersionsLoader(api, setVersionWarnings, setPluginVersion)(), [])
|
|
3640
|
+
const loadEfforts = makeEffortsLoader(api, setOptions); const loadTools = makeToolsLoader(api, setTools)
|
|
3641
|
+
const loadVersions = useCallback(() => makeVersionsLoader(api, setVersionWarnings, setPluginVersion)(), []) // 只挂载时拉一次:稳定引用防 effect 依赖恒变
|
|
2863
3642
|
useEffect(() => {
|
|
2864
3643
|
refresh()
|
|
2865
3644
|
loadOptions()
|
|
@@ -2867,9 +3646,8 @@ function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, Re
|
|
|
2867
3646
|
}, [refresh, loadOptions, loadVersions])
|
|
2868
3647
|
const applyWrite = makeApplyWrite(s); const actions = makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools)
|
|
2869
3648
|
attachDraftActions(api, s, refresh, applyWrite, actions)
|
|
2870
|
-
const rows = buildProfileRows(el, chip, s, actions)
|
|
2871
|
-
|
|
2872
|
-
return buildSectionReturn(el, s, actions, rows, buildAddCard(el, s, actions, toolSection), openSections, toggleSection, el(ReminderCenter))
|
|
3649
|
+
const rows = buildProfileRows(el, chip, s, actions); const toolSection = buildToolSection(el, s, actions)
|
|
3650
|
+
return buildSectionReturn(el, s, actions, rows, buildAddCard(el, s, actions, toolSection), el(ReminderCenter), sessionsSnapshot(getSessions))
|
|
2873
3651
|
}
|
|
2874
3652
|
return ProfilesSection
|
|
2875
3653
|
}
|
|
@@ -4036,7 +4814,7 @@ window.__ModuleLoader__.load({
|
|
|
4036
4814
|
// 与派发决策台账 openChild 同一 sessions 服务来源。
|
|
4037
4815
|
const openChild = (address) => ctx.sessions.openSubagent(address)
|
|
4038
4816
|
const ReminderCenter = makeReminderCenter(el, useState, useEffect, openChild, () => ctx.sessions)
|
|
4039
|
-
const ProfilesSection = makeProfilesSection(el, api, chip, useState, useEffect, useCallback, ReminderCenter)
|
|
4817
|
+
const ProfilesSection = makeProfilesSection(el, api, chip, useState, useEffect, useCallback, ReminderCenter, () => ctx.sessions)
|
|
4040
4818
|
ensureStyles()
|
|
4041
4819
|
const SubagentHeaderUtility = makeSubagentHeaderUtility(el, useState, useEffect, () => ctx.get('modelDirectories'), () => ctx.sessions)
|
|
4042
4820
|
ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({ name: 'tool.call.toolview', key: 'dispatch' }, DispatchToolview))
|
|
@@ -4067,4 +4845,4 @@ window.__ModuleLoader__.load({
|
|
|
4067
4845
|
|
|
4068
4846
|
return { apply, inject }
|
|
4069
4847
|
},
|
|
4070
|
-
})
|
|
4848
|
+
})
|