dsh-subagent-profile 0.4.0 → 0.5.1
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 +16 -57
- package/README.zh.md +16 -57
- package/index.mjs +97 -122
- package/lib/client.js +631 -88
- package/lib/core/cleanup.mjs +33 -0
- package/lib/core/data-maintenance.mjs +139 -0
- package/lib/core/decision-trace.mjs +20 -1
- package/lib/core/delegation.mjs +16 -0
- package/lib/core/dispatch-gates.mjs +122 -9
- package/lib/core/dispatch-schema.mjs +45 -7
- package/lib/core/dispatch-tool.mjs +30 -46
- package/lib/core/evolution-advice.mjs +31 -10
- package/lib/core/evolution-assets.mjs +13 -0
- package/lib/core/evolution-canary.mjs +427 -0
- package/lib/core/evolution-draft.mjs +21 -0
- package/lib/core/evolution-engine.mjs +176 -31
- package/lib/core/evolution-generate.mjs +7 -23
- package/lib/core/evolution-lag.mjs +78 -0
- package/lib/core/evolution-ledger.mjs +4 -1
- package/lib/core/evolution-routes.mjs +157 -16
- package/lib/core/http-routes.mjs +22 -7
- package/lib/core/model-policy.mjs +104 -0
- package/lib/core/presets-sync.mjs +24 -2
- package/lib/core/profile-directory.mjs +17 -0
- package/lib/core/profile-provider.mjs +7 -2
- package/lib/core/profiles-query.mjs +178 -0
- package/lib/core/profiles-store.mjs +40 -6
- package/lib/core/pure.mjs +1 -1
- package/lib/core/reminder-store.mjs +37 -3
- package/lib/core/snapshots.mjs +148 -0
- package/package.json +2 -2
- package/presets/orchestrator-v2/NOTICE +7 -0
- package/presets/{orchestrator → orchestrator-v2}/agent.cordis.yml +78 -7
- package/presets/orchestrator-v2/custom-bash.mjs +213 -0
- package/presets/orchestrator-v2/preset.yml +2 -0
- package/presets/orchestrator-v2/tool-bootstrap.mjs +620 -0
- package/presets/orchestrator/preset.yml +0 -2
package/lib/client.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
const CSS = [
|
|
25
25
|
'.sap-section{width:100%;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}',
|
|
26
|
+
'.sap-zoneStack{flex-direction:column;gap:12px;display:flex}',
|
|
26
27
|
'.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
28
|
'.sap-statusDim{color:var(--dsw-alias-label-tertiary)}',
|
|
28
29
|
'.sap-rowHighlight{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover)}',
|
|
@@ -133,6 +134,25 @@ const CSS = [
|
|
|
133
134
|
'.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
135
|
'.sap-adviceFootNotes{flex-direction:column;gap:2px;display:flex;min-width:0}',
|
|
135
136
|
'.sap-adviceFootNote{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0}',
|
|
137
|
+
'.sap-adviceStatusLine{align-items:center;gap:8px;font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary);margin:10px 0 0;display:flex}',
|
|
138
|
+
'.sap-adviceStatusLineWarn{color:var(--dsw-alias-state-error-primary);font-weight:600}',
|
|
139
|
+
'.sap-adviceStatusDim{color:var(--dsw-alias-label-tertiary)}',
|
|
140
|
+
'.sap-candidateState{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}',
|
|
141
|
+
'.sap-adviceTimelineLine{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}',
|
|
142
|
+
'.sap-adviceTimelineStep{color:var(--dsw-alias-label-secondary)}',
|
|
143
|
+
'.sap-adviceTimelineStepDim{color:var(--dsw-alias-label-dimmed)}',
|
|
144
|
+
'.sap-rollbackBox{border:1px solid var(--dsw-alias-state-error-secondary);background:var(--dsw-alias-interactive-bg-hover-danger);border-radius:6px;padding:10px 12px;flex-direction:column;gap:4px;display:flex;margin-top:12px}',
|
|
145
|
+
'.sap-rollbackBoxLine{color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:18px;margin:0}',
|
|
146
|
+
'.sap-rollbackBoxKeep{color:var(--dsw-alias-label-tertiary);font-size:12.5px;line-height:18px;margin:0}',
|
|
147
|
+
'.sap-rollbackBoxActions{display:flex;gap:8px;margin-top:6px}',
|
|
148
|
+
'.sap-rollbackDanger{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:0 0}',
|
|
149
|
+
'.sap-applyModal{background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:10px;padding:12px 14px;margin-top:12px;display:flex}',
|
|
150
|
+
'.sap-applyModalHead{align-items:baseline;gap:8px;display:flex}',
|
|
151
|
+
'.sap-applyModalTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}',
|
|
152
|
+
'.sap-applyModalLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;margin:0}',
|
|
153
|
+
'.sap-applyModalLine{color:var(--dsw-alias-label-primary);font-size:12.5px;line-height:18px;margin:0}',
|
|
154
|
+
'.sap-applyModalLineDim{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}',
|
|
155
|
+
'.sap-applyModalActions{justify-content:flex-end;gap:8px;display:flex}',
|
|
136
156
|
'.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
157
|
'.sap-candidateSettingsRow b{font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
138
158
|
'.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}',
|
|
@@ -145,6 +165,8 @@ const CSS = [
|
|
|
145
165
|
'.sap-candidateSettingsCustom .sap-input{max-width:120px}',
|
|
146
166
|
'.sap-candidateSettingsActions{align-items:center;gap:10px;display:flex}',
|
|
147
167
|
'.sap-desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}',
|
|
168
|
+
'.sap-descMinor{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:16px}',
|
|
169
|
+
'.sap-maintenanceSwitchDesc{flex-direction:column;gap:2px;display:flex}',
|
|
148
170
|
'.sap-warn{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}',
|
|
149
171
|
'.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}',
|
|
150
172
|
'.sap-chips{flex-wrap:wrap;gap:6px;display:flex}',
|
|
@@ -286,6 +308,7 @@ const CSS = [
|
|
|
286
308
|
'.sap-ledgerRowHead:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
287
309
|
'.sap-ledgerTime{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;font-family:monospace;flex:none}',
|
|
288
310
|
'.sap-ledgerMeta{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;min-width:0;word-break:break-word}',
|
|
311
|
+
'.sap-ledgerImageText{color:var(--dsw-alias-label-tertiary);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:none}',
|
|
289
312
|
'.sap-ledgerToggle{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;transition:transform .12s;margin-left:auto;flex:none;display:inline-flex}',
|
|
290
313
|
'.sap-ledgerRowOpen .sap-ledgerToggle{transform:rotate(90deg)}',
|
|
291
314
|
'.sap-ledgerExpand{flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding:10px 14px 12px;display:flex}',
|
|
@@ -419,7 +442,7 @@ const ZH = {
|
|
|
419
442
|
// 提醒系统
|
|
420
443
|
reminderCenterTitle: '提醒中心',
|
|
421
444
|
reminderCenterDesc: '需要你关注的事件汇总,所有动作均有审计记录;处理过的条目折叠保存',
|
|
422
|
-
reminderEmpty: '
|
|
445
|
+
reminderEmpty: '暂无提醒(历史操作与事件会在这里留痕)',
|
|
423
446
|
reminderAck: '标记已读',
|
|
424
447
|
reminderAdopt: '采纳',
|
|
425
448
|
reminderIgnore: '忽略',
|
|
@@ -438,6 +461,27 @@ const ZH = {
|
|
|
438
461
|
reminderNoteLabel: '说明',
|
|
439
462
|
reminderNoSettlement: '未结算',
|
|
440
463
|
reminderNoExcerpt: '(未记录)',
|
|
464
|
+
reminderArchivedEmpty: '暂无已归档条目(数据维护区执行归档后在此分页查看)',
|
|
465
|
+
reminderArchivedMore: '加载更早',
|
|
466
|
+
// 数据维护(设置页 → 方案管理尾部)
|
|
467
|
+
maintenanceCardTitle: '数据维护',
|
|
468
|
+
maintenanceCardDesc: '候选、快照、提醒与统计数据的清理入口;每次清理都会留下记录',
|
|
469
|
+
snapshotSwitchDesc: '派发时在本机自动保存完整配置(含自定义人格),供「保存为方案」与对比使用。\n数据仅存您本机(~/.dsh/subagent-evolution/snapshots/),插件更新不影响,可随时清理。\n关闭开关即停止新快照(已存保留)。',
|
|
470
|
+
snapshotCount: '已存快照:{n} / {cap} 条',
|
|
471
|
+
snapshotCapHint: '快照已达护栏上限,停止新快照(可清理后继续)',
|
|
472
|
+
snapshotNone: '暂无快照(开启开关后每次派发保存一条)',
|
|
473
|
+
snapshotView: '查看',
|
|
474
|
+
snapshotViewClose: '收起',
|
|
475
|
+
snapshotMeta: '快照 {id} · 于 {ts}',
|
|
476
|
+
snapshotPersonaFold: '人格({n} 字符,点击展开)',
|
|
477
|
+
snapshotEmptyConfig: '(无配置字段)',
|
|
478
|
+
cleanCandidates: '清理候选/建议',
|
|
479
|
+
cleanSnapshots: '清理快照',
|
|
480
|
+
cleanReminders: '归档已处理提醒',
|
|
481
|
+
cleanStats: '重置统计',
|
|
482
|
+
maintenanceConfirm: '确认要执行「{zone}」吗?清理操作会留下记录。',
|
|
483
|
+
maintenanceDone: '已执行 {zone}:{count} 条',
|
|
484
|
+
maintenanceCleanErr: '清理失败:',
|
|
441
485
|
toolRunning: '运行中',
|
|
442
486
|
tierZh: { cheap: '省 token(cheap)', balanced: '均衡(balanced)', premium: '高成本(premium)' },
|
|
443
487
|
stopReasonZh: { completed: '完成', 'max-tokens': '超出预算', aborted: '已中止', refusal: '已拒绝', error: '出错' },
|
|
@@ -514,7 +558,7 @@ const ZH = {
|
|
|
514
558
|
ledgerCostCardScope: '本会话口径',
|
|
515
559
|
ledgerCostCardCounter: '若继承父会话',
|
|
516
560
|
ledgerCostCardSave: '省',
|
|
517
|
-
ledgerEvidenceToggle: '查看完整因果链
|
|
561
|
+
ledgerEvidenceToggle: '查看完整因果链 ▸',
|
|
518
562
|
ledgerEvidenceClose: '收起因果链 ▴',
|
|
519
563
|
ledgerVsTitle: '请求 vs 生效',
|
|
520
564
|
ledgerVsDiff: '{n} 项与请求不同(琥珀底行)',
|
|
@@ -548,14 +592,14 @@ const ZH = {
|
|
|
548
592
|
ledgerExecLiveNote: '后台执行中:过程事件随活动更新',
|
|
549
593
|
ledgerEventStart: '开始执行',
|
|
550
594
|
ledgerEventCall: '第 {n} 次工具调用',
|
|
551
|
-
ledgerEventStall: '⚠ 停滞 {s}s
|
|
595
|
+
ledgerEventStall: '⚠ 停滞 {s}s 无活动(超过 {t}s 上限)',
|
|
552
596
|
ledgerEventStalling: '⚠ 停滞中(已 {s}s 无活动)',
|
|
553
597
|
ledgerEventIntervene: '主 agent 干预',
|
|
554
598
|
ledgerEventResume: '恢复执行',
|
|
555
599
|
ledgerEventDone: '完成',
|
|
556
600
|
ledgerEventRunning: '执行中…',
|
|
557
601
|
ledgerEventMoreCalls: '…其间还有 {n} 次调用',
|
|
558
|
-
ledgerStallThresholdNote: '
|
|
602
|
+
ledgerStallThresholdNote: '停滞上限 30s(待实测)',
|
|
559
603
|
ledgerSettleOutput: '输出摘要',
|
|
560
604
|
ledgerSettleCostBreakdown: '成本分解',
|
|
561
605
|
ledgerSettleInheritNote: '未指定模型,按继承父会话估算',
|
|
@@ -592,9 +636,9 @@ const ZH = {
|
|
|
592
636
|
adviceSectionDetail: '派发明细',
|
|
593
637
|
adviceSwitchOn: '开(只提示,不自动改配置)',
|
|
594
638
|
adviceSwitchOff: '关(只提示,不自动改配置)',
|
|
595
|
-
advicePanelEmpty: '
|
|
639
|
+
advicePanelEmpty: '暂无建议(近期派发表现不佳时才会生成)',
|
|
596
640
|
advicePanelGlobal: '全局',
|
|
597
|
-
advicePanelReadonly: '
|
|
641
|
+
advicePanelReadonly: '(仅提示:统计与建议不改变派发行为)',
|
|
598
642
|
adviceDirUp: '可上调预算',
|
|
599
643
|
adviceDirDown: '建议降级',
|
|
600
644
|
adviceDirFlat: '维持现状',
|
|
@@ -633,8 +677,8 @@ const ZH = {
|
|
|
633
677
|
candidateTitle: '候选方案',
|
|
634
678
|
candidateNotApplied: '(未应用)',
|
|
635
679
|
candidateCountSuffix: ' 个 · 单选',
|
|
636
|
-
candidateCountNote: ' ·
|
|
637
|
-
candidateIntro: '
|
|
680
|
+
candidateCountNote: ' · 应用前需你确认',
|
|
681
|
+
candidateIntro: '系统基于近期表现生成的改进方案;应用前需要你确认,并进入观察期。',
|
|
638
682
|
candidateRegenerate: '重新生成候选',
|
|
639
683
|
candidateSafetyPending: '安全检查:正在核对…',
|
|
640
684
|
candidateGroupChanges: '改动',
|
|
@@ -643,6 +687,40 @@ const ZH = {
|
|
|
643
687
|
candidateSafetyPass: '全部通过 ✓',
|
|
644
688
|
candidateSafetyFail: '存在拒绝项',
|
|
645
689
|
candidateNotApplicable: '应用功能在后续版本开放(届时会先经你确认)',
|
|
690
|
+
// 观察期/撤销建议/确认流(用户可见面零内部词:观察中/表现变差/撤销/调整前)
|
|
691
|
+
adviceStatusObservingSuffix: '——表现下降会提醒你撤销',
|
|
692
|
+
adviceApplyConfirm: '确认调整方案',
|
|
693
|
+
adviceApplyCancel: '取消',
|
|
694
|
+
adviceApplyModalSummary: '候选摘要',
|
|
695
|
+
adviceApplyModalImpact: '成本与能力影响',
|
|
696
|
+
adviceApplyImpactDown: '能力配置降级,工具面不扩大,成本通常更低',
|
|
697
|
+
adviceApplyImpactUp: '预算上调,成本可能上升',
|
|
698
|
+
adviceApplyImpactFlat: '能力配置不变,成本不受影响',
|
|
699
|
+
adviceApplySafetyNote: '安全检查:',
|
|
700
|
+
adviceApplyDefaultNote: '本建议改的是方案默认值;派发时显式参数仍会覆盖它',
|
|
701
|
+
adviceApplyObserveNote: '应用后进入观察期(5 次派发),表现下降时会提醒你撤销,不会自动修改',
|
|
702
|
+
adviceApplySuccess: '已应用,进入观察期',
|
|
703
|
+
adviceApplyObservingTitle: '观察中,暂不支持再次调整',
|
|
704
|
+
adviceApplySelectTitle: '请先选择一个候选方案',
|
|
705
|
+
adviceRollbackBoxAdvice: '建议撤销这次调整,恢复调整前的配置。',
|
|
706
|
+
adviceRollbackRestorePrefix: '恢复为:',
|
|
707
|
+
adviceRollbackEvidence: '对比:调整前表现分 {before} · 调整后 {after}(近 {n} 次派发)',
|
|
708
|
+
adviceRollbackKeepDone: '已选择:继续使用当前配置(已留痕)',
|
|
709
|
+
adviceRollbackBtn: '撤销上次调整',
|
|
710
|
+
adviceRollbackKeepBtn: '继续用当前方案',
|
|
711
|
+
adviceRollbackSuccess: '已恢复调整前配置,观察期终止',
|
|
712
|
+
adviceKeepSuccess: '已选择保留当前配置(已留痕)',
|
|
713
|
+
adviceRollbackModalTitle: '撤销这次调整?',
|
|
714
|
+
adviceRollbackModalRestore: '恢复为',
|
|
715
|
+
adviceRollbackModalChange: '会改变',
|
|
716
|
+
adviceRollbackModalNote: '若方案已被手动修改,将无法一键撤销;本次撤销会记入审计',
|
|
717
|
+
adviceRollbackConfirm: '确认撤销',
|
|
718
|
+
adviceRollbackChangeSuffix: '调整前自定义项一并恢复',
|
|
719
|
+
adviceTimelineTitle: '进化轨迹',
|
|
720
|
+
adviceTimelinePending: '待你处理',
|
|
721
|
+
adviceTimelineSep: ' → ',
|
|
722
|
+
adviceRestoreFallback: '调整前的完整配置',
|
|
723
|
+
adviceReminderView: '查看建议',
|
|
646
724
|
candidateValidityForever: '⏱ 无期限',
|
|
647
725
|
candidateValidityPrefix: '⏱ 至 ',
|
|
648
726
|
candidateGeneratedPrefix: '生成于 ',
|
|
@@ -650,14 +728,14 @@ const ZH = {
|
|
|
650
728
|
candidateSamplesSuffix: ' 次派发',
|
|
651
729
|
candidatePerfPrefix: '近期表现 ',
|
|
652
730
|
candidatePerfSuffix: '(满分 100)',
|
|
653
|
-
candidateNoSample: '
|
|
731
|
+
candidateNoSample: '暂无近期派发样本',
|
|
654
732
|
// 候选卡底部:无对应方案存来源配置为新方案;有对应方案调整按钮灰置 + 定位。
|
|
655
733
|
adviceSaveProfile: '保存为方案',
|
|
656
734
|
adviceApplyDisabled: '按建议调整方案',
|
|
657
735
|
adviceSavedNotice: '已保存为新方案:{id}',
|
|
658
736
|
adviceFootNoteSave: '仅保存预设、模型与提供方;自定义人格与工具过滤无法还原(隐私保护不落盘),如需完整配置请手动编辑方案。',
|
|
659
737
|
adviceFootNoteMatchedPrefix: '对应方案:',
|
|
660
|
-
// 建议摘要行(v8
|
|
738
|
+
// 建议摘要行(v8:对象/建议/置信度;展开后见理由与明细)
|
|
661
739
|
adviceNoProfileTag: '无方案',
|
|
662
740
|
// 安全页签:安全保证卡 + 审计健康卡(只读明细表)
|
|
663
741
|
safetyCardTitle: '安全保证',
|
|
@@ -666,7 +744,7 @@ const ZH = {
|
|
|
666
744
|
auditCardTitle: '审计健康',
|
|
667
745
|
auditCardDesc: '治理审计状态与留痕,异常红字提示',
|
|
668
746
|
auditDetailToggle: '查看审计明细(点击展开)',
|
|
669
|
-
auditDetailEmpty: '
|
|
747
|
+
auditDetailEmpty: '暂无审计明细(历史操作与事件会在这里留痕)',
|
|
670
748
|
auditDetailScope: '只读展示,暂不提供导出与过滤',
|
|
671
749
|
auditColTime: '时间',
|
|
672
750
|
auditColEvent: '事件',
|
|
@@ -1047,6 +1125,27 @@ function readDispatchResult(block) {
|
|
|
1047
1125
|
}
|
|
1048
1126
|
|
|
1049
1127
|
|
|
1128
|
+
// 生效画像一行(派发卡片/台账行展示):优先 B3 交付的 meta.imageText(后端同源
|
|
1129
|
+
// 格式,;分隔转 UI 的 · 分隔、路由段加「模型」前缀);旧结果无 imageText 时按
|
|
1130
|
+
// 生效字段同格式合成。未绑定方案 → 「继承父方案 · …」;两者都不可得返回空串
|
|
1131
|
+
// (不渲染该行)。fail-soft:坏输入绝不 throw。
|
|
1132
|
+
function imageTextOfBlock(meta, eff) {
|
|
1133
|
+
const e = eff && typeof eff === 'object' ? eff : {}
|
|
1134
|
+
const m = meta && typeof meta === 'object' ? meta : {}
|
|
1135
|
+
let base = typeof m.imageText === 'string' && m.imageText !== '' ? m.imageText : ''
|
|
1136
|
+
if (base === '') {
|
|
1137
|
+
const profile = typeof e.profile === 'string' && e.profile !== '' && e.profile !== '(inline)' ? e.profile : ''
|
|
1138
|
+
const provider = typeof e.provider === 'string' && e.provider !== '' && e.provider !== '(parent)' ? e.provider : ''
|
|
1139
|
+
const model = typeof e.model === 'string' && e.model !== '' && e.model !== '(parent)' ? e.model : ''
|
|
1140
|
+
const route = provider !== '' && model !== '' ? provider + '/' + model : '继承父'
|
|
1141
|
+
const tier = typeof m.tokenTier === 'string' && m.tokenTier !== '' ? m.tokenTier : 'balanced'
|
|
1142
|
+
base = profile !== '' ? `方案 ${profile};${route};档位 ${tier}` : `继承父方案;${route};档位 ${tier}`
|
|
1143
|
+
}
|
|
1144
|
+
const parts = base.split(';').map((p) => p.trim()).filter((p) => p !== '')
|
|
1145
|
+
if (parts.length >= 2) parts[1] = '模型 ' + parts[1]
|
|
1146
|
+
return parts.join(' · ')
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1050
1149
|
// 结算数字提取:tokens 数与缓存命中分别取,供「≈N tokens(缓存命中 M)」
|
|
1051
1150
|
// 单括号合并展示。tokens 不可得返回 null。
|
|
1052
1151
|
function summaryNumbersOf(result, block) {
|
|
@@ -1409,10 +1508,14 @@ function buildSubagentBanner(el, subagent, parentPreset, modelState, bannerMode)
|
|
|
1409
1508
|
// 通知中心(设置页通知与提醒页签):读 /reminders(与审计同源 host store),全动作
|
|
1410
1509
|
// (标记已读/采纳/忽略/拒绝/关闭)经 POST 回写 host 并刷新——一条提醒 = 一条审计记录。
|
|
1411
1510
|
// sessions 服务供条目主/子会话标题查询与跳转;跳转失败静默(设置页不被导航异常打断)。
|
|
1511
|
+
// 归档视图每页条数(防大量已处理条目一次性渲染卡 UI)。
|
|
1512
|
+
const ARCHIVED_PAGE_SIZE = 20
|
|
1513
|
+
|
|
1412
1514
|
function makeReminderCenter(el, useState, useEffect, openChild, getSessions) {
|
|
1413
|
-
function ReminderCenter() {
|
|
1515
|
+
function ReminderCenter(props) {
|
|
1414
1516
|
const [reminders, setReminders] = useState([])
|
|
1415
1517
|
const [error, setError] = useState('')
|
|
1518
|
+
const [archived, setArchived] = useState({ open: false, list: [], total: 0, offset: 0, loading: false })
|
|
1416
1519
|
const refresh = () => {
|
|
1417
1520
|
api('/reminders').then((data) => setReminders(Array.isArray(data.reminders) ? data.reminders : [])).catch(() => {})
|
|
1418
1521
|
}
|
|
@@ -1422,29 +1525,66 @@ function makeReminderCenter(el, useState, useEffect, openChild, getSessions) {
|
|
|
1422
1525
|
const request = action === 'ack' ? api('/reminders/ack', { ids: [id] }) : api('/reminders/action', { id, action })
|
|
1423
1526
|
request.then(refresh).catch((err) => setError(String((err && err.message) || err)))
|
|
1424
1527
|
}
|
|
1425
|
-
|
|
1528
|
+
// 归档视图分页加载:打开时取第一页,「加载更早」按 offset 追加(存储只读)。
|
|
1529
|
+
const loadArchivedPage = (offset) => {
|
|
1530
|
+
setArchived((prev) => ({ ...prev, loading: true }))
|
|
1531
|
+
api('/reminders/archived?offset=' + offset + '&limit=' + ARCHIVED_PAGE_SIZE)
|
|
1532
|
+
.then((data) => {
|
|
1533
|
+
const rows = Array.isArray(data.reminders) ? data.reminders : []
|
|
1534
|
+
setArchived((prev) => ({ ...prev, loading: false, total: typeof data.total === 'number' ? data.total : prev.total, offset, list: offset === 0 ? rows : [...prev.list, ...rows] }))
|
|
1535
|
+
})
|
|
1536
|
+
.catch(() => setArchived((prev) => ({ ...prev, loading: false })))
|
|
1537
|
+
}
|
|
1538
|
+
const toggleArchived = () => {
|
|
1539
|
+
const nextOpen = !archived.open
|
|
1540
|
+
setArchived((prev) => ({ ...prev, open: nextOpen }))
|
|
1541
|
+
if (nextOpen && archived.list.length === 0) loadArchivedPage(0)
|
|
1542
|
+
}
|
|
1543
|
+
const jumpToAdvice = props !== null && typeof props === 'object' && typeof props.jumpToAdvice === 'function' ? props.jumpToAdvice : null
|
|
1544
|
+
return buildReminderCenter(el, reminders, error, act, getSessions(), openChild, jumpToAdvice, archived, toggleArchived, loadArchivedPage)
|
|
1426
1545
|
}
|
|
1427
1546
|
return ReminderCenter
|
|
1428
1547
|
}
|
|
1429
1548
|
|
|
1549
|
+
// 归档视图区:折叠开关(总条数)+ 只读行列表(已处理条目复渲染)+ 加载更早。
|
|
1550
|
+
function buildArchivedReminderSection(el, archived, act, sessions, openChild, jumpToAdvice, toggleArchived, loadArchivedPage) {
|
|
1551
|
+
const rows = Array.isArray(archived.list) ? archived.list : []
|
|
1552
|
+
const hasMore = archived.list.length < archived.total
|
|
1553
|
+
return el('details', { className: 'sap-customized sap-customizedArrowEnd sap-reminderHandled', open: archived.open === true },
|
|
1554
|
+
el('summary', { className: 'sap-customizedSummary', onClick: toggleArchived }, '已归档(' + archived.total + ')', el('span', { className: 'sap-customizedEndArrow' }, '▸')),
|
|
1555
|
+
el('div', { className: 'sap-customizedBody' },
|
|
1556
|
+
rows.length === 0
|
|
1557
|
+
? el('p', { className: 'sap-desc' }, ZH.reminderArchivedEmpty)
|
|
1558
|
+
: [
|
|
1559
|
+
el('ul', { className: 'sap-rows' }, rows.map((r) => buildReminderRow(el, r, act, sessions, openChild, jumpToAdvice))),
|
|
1560
|
+
hasMore
|
|
1561
|
+
? el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, disabled: archived.loading === true, onClick: () => loadArchivedPage(archived.offset + ARCHIVED_PAGE_SIZE) }, ZH.reminderArchivedMore)
|
|
1562
|
+
: null
|
|
1563
|
+
]
|
|
1564
|
+
)
|
|
1565
|
+
)
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1430
1568
|
// 提醒中心分层:未终态条目(action 为空)进默认列表;已终态条目折叠为「已处理(N)」
|
|
1431
|
-
// 可展开区(原生 details
|
|
1432
|
-
//
|
|
1433
|
-
|
|
1434
|
-
function buildReminderCenter(el, reminders, error, act, sessions, openChild) {
|
|
1569
|
+
// 可展开区(原生 details,展开后仍按现有行渲染并保留已处理标注,可追溯);归档后的
|
|
1570
|
+
// 条目进入「已归档(N)」只读分页视图(存储与审计保留)。审计留痕由 host 侧动作回写。
|
|
1571
|
+
function buildReminderCenter(el, reminders, error, act, sessions, openChild, jumpToAdvice, archived, toggleArchived, loadArchivedPage) {
|
|
1435
1572
|
const handled = reminders.filter((r) => typeof r.action === 'string' && r.action !== '')
|
|
1436
1573
|
const pending = reminders.filter((r) => !(typeof r.action === 'string' && r.action !== ''))
|
|
1437
1574
|
const pendingList = pending.length > 0
|
|
1438
|
-
? el('ul', { className: 'sap-rows' }, pending.map((r) => buildReminderRow(el, r, act, sessions, openChild)))
|
|
1575
|
+
? el('ul', { className: 'sap-rows' }, pending.map((r) => buildReminderRow(el, r, act, sessions, openChild, jumpToAdvice)))
|
|
1439
1576
|
: null
|
|
1440
1577
|
const handledList = handled.length > 0
|
|
1441
1578
|
? el('details', { className: 'sap-customized sap-customizedArrowEnd sap-reminderHandled' },
|
|
1442
1579
|
el('summary', { className: 'sap-customizedSummary' }, '已处理(' + handled.length + ')', el('span', { className: 'sap-customizedEndArrow' }, '▸')),
|
|
1443
1580
|
el('div', { className: 'sap-customizedBody' },
|
|
1444
|
-
el('ul', { className: 'sap-rows' }, handled.map((r) => buildReminderRow(el, r, act, sessions, openChild)))
|
|
1581
|
+
el('ul', { className: 'sap-rows' }, handled.map((r) => buildReminderRow(el, r, act, sessions, openChild, jumpToAdvice)))
|
|
1445
1582
|
)
|
|
1446
1583
|
)
|
|
1447
1584
|
: null
|
|
1585
|
+
const archivedSection = archived !== null && typeof archived === 'object'
|
|
1586
|
+
? buildArchivedReminderSection(el, archived, act, sessions, openChild, jumpToAdvice, toggleArchived, loadArchivedPage)
|
|
1587
|
+
: null
|
|
1448
1588
|
return el('div', { className: 'sap-card sap-cardStatic' },
|
|
1449
1589
|
el('div', { className: 'sap-cardHead' },
|
|
1450
1590
|
el('div', { className: 'sap-cardTitleBox' },
|
|
@@ -1456,7 +1596,8 @@ function buildReminderCenter(el, reminders, error, act, sessions, openChild) {
|
|
|
1456
1596
|
error !== '' ? el('p', { className: 'sap-notice' }, error) : null,
|
|
1457
1597
|
pendingList === null && handledList === null
|
|
1458
1598
|
? el('p', { className: 'sap-desc' }, ZH.reminderEmpty)
|
|
1459
|
-
: [pendingList, handledList]
|
|
1599
|
+
: [pendingList, handledList],
|
|
1600
|
+
archivedSection
|
|
1460
1601
|
)
|
|
1461
1602
|
)
|
|
1462
1603
|
}
|
|
@@ -1523,10 +1664,20 @@ function reminderJumpButton(el, jump, disabled, onClick) {
|
|
|
1523
1664
|
)
|
|
1524
1665
|
}
|
|
1525
1666
|
|
|
1667
|
+
// 会话存在性探测(孤儿灰置):跳转引用指向已删会话(binding 拿不到)时灰置跳转
|
|
1668
|
+
// 按钮——回溯安全,不点进不可达会话。空 id/异常一律视为不可用(fail-soft)。
|
|
1669
|
+
function sessionExists(sessions, id) {
|
|
1670
|
+
if (typeof id !== 'string' || id === '') return false
|
|
1671
|
+
try { return sessions.binding(id) !== undefined } catch { return false }
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1526
1674
|
// 未采纳条目五字段区:主会话/子会话可点击跳转,任务/结果/说明只读展示。
|
|
1527
1675
|
function buildUnadoptedFields(el, r, sessions, openChild) {
|
|
1676
|
+
const childId = r.childSessionId !== null && r.childSessionId !== undefined && r.childSessionId !== '' ? r.childSessionId : r.childId
|
|
1528
1677
|
const parentJump = reminderJumpText(sessions, r.sessionId, ZH.reminderParentLabel)
|
|
1529
|
-
const childJump = reminderJumpText(sessions,
|
|
1678
|
+
const childJump = reminderJumpText(sessions, childId, ZH.reminderChildLabel)
|
|
1679
|
+
const parentGone = !sessionExists(sessions, r.sessionId)
|
|
1680
|
+
const childGone = !sessionExists(sessions, childId)
|
|
1530
1681
|
const openParent = () => { try { sessions.open(r.sessionId) } catch { /* 会话不可用:静默 */ } }
|
|
1531
1682
|
const childAddress = typeof r.sessionId === 'string' && r.sessionId !== '' && typeof r.childSessionId === 'string' && r.childSessionId !== ''
|
|
1532
1683
|
? { parentSessionId: r.sessionId, childSessionId: r.childSessionId, mode: reminderChildMode(r.childMode) }
|
|
@@ -1534,8 +1685,8 @@ function buildUnadoptedFields(el, r, sessions, openChild) {
|
|
|
1534
1685
|
const jumpChild = () => { if (childAddress !== null) { try { openChild(childAddress) } catch { /* 子会话不可用:静默 */ } } }
|
|
1535
1686
|
const excerpt = typeof r.promptExcerpt === 'string' && r.promptExcerpt !== '' ? '「' + r.promptExcerpt + '」' : ZH.reminderNoExcerpt
|
|
1536
1687
|
return el('div', { className: 'sap-reminderFields' },
|
|
1537
|
-
reminderJumpButton(el, parentJump,
|
|
1538
|
-
reminderJumpButton(el, childJump, childAddress === null, jumpChild),
|
|
1688
|
+
reminderJumpButton(el, parentJump, parentGone, openParent),
|
|
1689
|
+
reminderJumpButton(el, childJump, childAddress === null || childGone, jumpChild),
|
|
1539
1690
|
reminderFieldLine(el, ZH.reminderTaskLabel, excerpt),
|
|
1540
1691
|
reminderFieldLine(el, ZH.reminderResultLabel, reminderResultText(r)),
|
|
1541
1692
|
reminderFieldLine(el, ZH.reminderNoteLabel, r.detail)
|
|
@@ -1545,10 +1696,11 @@ function buildUnadoptedFields(el, r, sessions, openChild) {
|
|
|
1545
1696
|
// 未采纳条目按钮三件套:查看子会话(跳转,恒保留)/ 标记已读(未读显示)/ 关闭。
|
|
1546
1697
|
// 已终态条目隐藏标记已读与关闭(只留只读跳转),防对终态条目重复处置产生重复审计;
|
|
1547
1698
|
// 提醒上的处置只改变提醒自身状态(已读/终态),不改变采纳判定——采纳与否由事件流自动汇总。
|
|
1548
|
-
|
|
1699
|
+
// 子会话已删(孤儿)时查看按钮灰置(回溯安全)。
|
|
1700
|
+
function buildUnadoptedActions(el, r, act, childAddress, jumpChild, childGone) {
|
|
1549
1701
|
const terminated = typeof r.action === 'string' && r.action !== ''
|
|
1550
1702
|
return el('div', { className: 'sap-reminderActions' }, [
|
|
1551
|
-
el('button', { type: 'button', className: 'sap-detailToggle', disabled: childAddress === null, onClick: jumpChild }, ZH.reminderViewChild),
|
|
1703
|
+
el('button', { type: 'button', className: 'sap-detailToggle', disabled: childAddress === null || childGone === true, onClick: jumpChild }, ZH.reminderViewChild),
|
|
1552
1704
|
terminated ? null : r.unread === true ? el('button', { type: 'button', className: 'sap-detailToggle', onClick: act(r.id, 'ack') }, ZH.reminderAck) : null,
|
|
1553
1705
|
terminated ? null : el('button', { type: 'button', className: 'sap-dangerButton', onClick: act(r.id, 'dismiss') }, ZH.reminderDismiss)
|
|
1554
1706
|
])
|
|
@@ -1558,9 +1710,11 @@ function buildUnadoptedActions(el, r, act, childAddress, jumpChild) {
|
|
|
1558
1710
|
function buildUnadoptedReminderRow(el, r, act, sessions, openChild) {
|
|
1559
1711
|
const severityCls = r.severity === 'P0' ? 'sap-reminderP0' : 'sap-reminderP1'
|
|
1560
1712
|
const severityText = r.severity === 'P0' ? '紧急' : '注意'
|
|
1713
|
+
const childId = r.childSessionId !== null && r.childSessionId !== undefined && r.childSessionId !== '' ? r.childSessionId : r.childId
|
|
1561
1714
|
const childAddress = typeof r.sessionId === 'string' && r.sessionId !== '' && typeof r.childSessionId === 'string' && r.childSessionId !== ''
|
|
1562
1715
|
? { parentSessionId: r.sessionId, childSessionId: r.childSessionId, mode: reminderChildMode(r.childMode) }
|
|
1563
1716
|
: null
|
|
1717
|
+
const childGone = !sessionExists(sessions, childId)
|
|
1564
1718
|
const jumpChild = () => { if (childAddress !== null) { try { openChild(childAddress) } catch { /* 子会话不可用:静默 */ } } }
|
|
1565
1719
|
return el('li', { key: String(r.id), className: 'sap-rowCard' + (r.unread === true ? ' sap-reminderUnread' : '') },
|
|
1566
1720
|
el('div', { className: 'sap-reminderHead' },
|
|
@@ -1568,7 +1722,34 @@ function buildUnadoptedReminderRow(el, r, act, sessions, openChild) {
|
|
|
1568
1722
|
el('span', { className: 'sap-reminderTitle', title: r.title }, r.title)
|
|
1569
1723
|
),
|
|
1570
1724
|
buildUnadoptedFields(el, r, sessions, openChild),
|
|
1571
|
-
buildUnadoptedActions(el, r, act, childAddress, jumpChild),
|
|
1725
|
+
buildUnadoptedActions(el, r, act, childAddress, jumpChild, childGone),
|
|
1726
|
+
typeof r.action === 'string' && r.action !== ''
|
|
1727
|
+
? el('p', { className: 'sap-metaValueDefault' }, ZH.reminderActed.replace('{action}', ZH.reminderActionZh[r.action] || r.action))
|
|
1728
|
+
: null
|
|
1729
|
+
)
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// 回滚建议提醒行:标题/说明只读 + 「查看建议」跳转到自进化与建议页签(只读跳转,
|
|
1733
|
+
// 无采纳/忽略按钮);与通知类提醒同款「标记已读/关闭」。
|
|
1734
|
+
function buildRollbackReminderRow(el, r, act, jumpToAdvice) {
|
|
1735
|
+
const severityCls = r.severity === 'P0' ? 'sap-reminderP0' : 'sap-reminderP1'
|
|
1736
|
+
const severityText = r.severity === 'P0' ? '紧急' : '注意'
|
|
1737
|
+
const jump = () => { if (typeof jumpToAdvice === 'function') { try { jumpToAdvice() } catch { /* 页签不可用:静默 */ } } }
|
|
1738
|
+
return el('li', { key: String(r.id), className: 'sap-rowCard' + (r.unread === true ? ' sap-reminderUnread' : '') },
|
|
1739
|
+
el('div', { className: 'sap-reminderHead' },
|
|
1740
|
+
el('span', { className: 'sap-chip ' + severityCls }, severityText),
|
|
1741
|
+
el('span', { className: 'sap-reminderTitle', title: r.title }, r.title)
|
|
1742
|
+
),
|
|
1743
|
+
el('p', { className: 'sap-desc' }, r.detail),
|
|
1744
|
+
typeof r.action === 'string' && r.action !== ''
|
|
1745
|
+
? null
|
|
1746
|
+
: el('div', { className: 'sap-reminderActions' }, [
|
|
1747
|
+
typeof jumpToAdvice === 'function' ? el('button', { type: 'button', className: 'sap-detailToggle', onClick: jump }, ZH.adviceReminderView) : null,
|
|
1748
|
+
r.unread === true ? el('button', { type: 'button', className: 'sap-detailToggle', onClick: act(r.id, 'ack') }, ZH.reminderAck) : null,
|
|
1749
|
+
r.severity !== 'P0' || r.unread !== true
|
|
1750
|
+
? el('button', { type: 'button', className: 'sap-dangerButton', onClick: act(r.id, 'dismiss') }, ZH.reminderDismiss)
|
|
1751
|
+
: null
|
|
1752
|
+
]),
|
|
1572
1753
|
typeof r.action === 'string' && r.action !== ''
|
|
1573
1754
|
? el('p', { className: 'sap-metaValueDefault' }, ZH.reminderActed.replace('{action}', ZH.reminderActionZh[r.action] || r.action))
|
|
1574
1755
|
: null
|
|
@@ -1578,7 +1759,8 @@ function buildUnadoptedReminderRow(el, r, act, sessions, openChild) {
|
|
|
1578
1759
|
// 通知类提醒行:P0 未读只保留「标记已读」(常驻至已读,不可跳过未读);已读后出现
|
|
1579
1760
|
// 「关闭」(与 P1/P2 同);P1/P2 恒保留「标记已读 / 关闭」。已终态条目隐藏动作按钮,
|
|
1580
1761
|
// 只显示已处理灰字标注(折叠区可追溯、防对终态条目误点产生重复审计)。
|
|
1581
|
-
function buildReminderRow(el, r, act, sessions, openChild) {
|
|
1762
|
+
function buildReminderRow(el, r, act, sessions, openChild, jumpToAdvice) {
|
|
1763
|
+
if (r.kind === 'suggestion-rollback') return buildRollbackReminderRow(el, r, act, jumpToAdvice)
|
|
1582
1764
|
if (r.kind === 'adoption-false') return buildUnadoptedReminderRow(el, r, act, sessions, openChild)
|
|
1583
1765
|
const severityCls = r.severity === 'P0' ? 'sap-reminderP0' : 'sap-reminderP1'
|
|
1584
1766
|
const severityText = r.severity === 'P0' ? '紧急' : '注意'
|
|
@@ -1712,7 +1894,8 @@ function collectDispatchRecords(nodes) {
|
|
|
1712
1894
|
request: readDispatchRequest(node),
|
|
1713
1895
|
result: readDispatchResult(node),
|
|
1714
1896
|
argsSummary: summarizeArgsRaw(node.call.argsRaw),
|
|
1715
|
-
contentFirstLine: firstContentLine(node.content)
|
|
1897
|
+
contentFirstLine: firstContentLine(node.content),
|
|
1898
|
+
imageText: node.meta !== null && typeof node.meta === 'object' && typeof node.meta.imageText === 'string' ? node.meta.imageText : ''
|
|
1716
1899
|
})
|
|
1717
1900
|
}
|
|
1718
1901
|
return records
|
|
@@ -2222,17 +2405,17 @@ function useBackgroundJobPoll(useState, useEffect, api, sessionId, callId, settl
|
|
|
2222
2405
|
function makeDispatchToolview(el, chip, useState, useEffect) {
|
|
2223
2406
|
function DispatchToolview(props) {
|
|
2224
2407
|
const block = props && props.block; const request = readDispatchRequest(block); const result = readDispatchResult(block)
|
|
2225
|
-
const settled = !!(block && typeof block === 'object' && 'kind' in block)
|
|
2226
|
-
const trace = block && block.meta ? normalizeDecisionTrace(block.meta) : null
|
|
2408
|
+
const settled = !!(block && typeof block === 'object' && 'kind' in block); const trace = block && block.meta ? normalizeDecisionTrace(block.meta) : null
|
|
2227
2409
|
const { eff, ignored, kindLabel } = resolveDispatchState(request, result, settled)
|
|
2228
2410
|
const chips = buildDispatchChips(el, chip, eff, result, block, ignored, request)
|
|
2229
|
-
|
|
2230
|
-
const
|
|
2411
|
+
// 生效画像行并入 meta 行(B3 imageText 回读;旧结果按生效字段合成,空则不出现)。
|
|
2412
|
+
const safetyLine = safetyLineText(trace); const toolLine = toolLineText(request, trace); const imageText = imageTextOfBlock(block && typeof block === 'object' ? block.meta : null, eff)
|
|
2413
|
+
const metaLine = (safetyLine !== '' || toolLine !== '' || imageText !== '') ? el('div', { className: 'sap-toolMeta' },
|
|
2231
2414
|
safetyLine !== '' ? el('span', { className: 'sap-toolMetaItem' }, safetyLine) : null,
|
|
2232
|
-
toolLine !== '' ? el('span', { className: 'sap-toolMetaItem' }, toolLine) : null
|
|
2415
|
+
toolLine !== '' ? el('span', { className: 'sap-toolMetaItem' }, toolLine) : null,
|
|
2416
|
+
imageText !== '' ? el('span', { className: 'sap-toolMetaItem' }, imageText) : null
|
|
2233
2417
|
) : null
|
|
2234
2418
|
const usage = usageFrom(result, block)
|
|
2235
|
-
// 后台运行中按 callId 轮询 /ledger/jobs 配对结算(前台/continuable 静态终态)。
|
|
2236
2419
|
const jobSettled = useBackgroundJobPoll(useState, useEffect, api, props.sessionId, props.callId, settled, request)
|
|
2237
2420
|
const [open, setOpen] = useState(false); const suggestion = draftSuggestionFromDispatch(request, trace, result); const draft = useDraftCapture(useState, suggestion)
|
|
2238
2421
|
// 摘要行只承载动态结果(成本/耗时/结束原因/缓存命中);静态配置(方案/预设/
|
|
@@ -2657,6 +2840,45 @@ function makeCandidateSettingsActions(api, s, refresh, applyWrite) {
|
|
|
2657
2840
|
}
|
|
2658
2841
|
}
|
|
2659
2842
|
|
|
2843
|
+
// 应用确认流动作:按建议调整方案(POST /evolution/apply,humanConfirmed 由确认按钮
|
|
2844
|
+
// 触发)、撤销上次调整(action=rollback)、继续用当前方案(action=keep)。成功即刷新
|
|
2845
|
+
// 并置顶部成功提示;失败原因回显在各自弹窗内(不静默)。
|
|
2846
|
+
function makeApplyFlowActions(api, s, refresh) {
|
|
2847
|
+
const closeApply = () => s.setApplyModal((prev) => ({ ...(prev ?? {}), open: false, error: '' }))
|
|
2848
|
+
const closeRollback = () => s.setRollbackModal((prev) => ({ ...(prev ?? {}), open: false, error: '' }))
|
|
2849
|
+
const applyCandidate = () => {
|
|
2850
|
+
const modal = s.applyModal ?? {}
|
|
2851
|
+
if (modal.candidateId === '') return
|
|
2852
|
+
s.setError('')
|
|
2853
|
+
api('/evolution/apply', { assetId: modal.candidateId, human_confirmed: true })
|
|
2854
|
+
.then(() => { closeApply(); s.setSavedNotice(ZH.adviceApplySuccess); refresh() })
|
|
2855
|
+
.catch((err) => s.setApplyModal((prev) => ({ ...(prev ?? {}), error: String((err && err.message) || err) })))
|
|
2856
|
+
}
|
|
2857
|
+
const rollbackAsset = () => {
|
|
2858
|
+
const modal = s.rollbackModal ?? {}
|
|
2859
|
+
if (modal.assetId === '') return
|
|
2860
|
+
s.setError('')
|
|
2861
|
+
api('/evolution/rollback', { assetId: modal.assetId, human_confirmed: true, action: 'rollback' })
|
|
2862
|
+
.then(() => { closeRollback(); s.setSavedNotice(ZH.adviceRollbackSuccess); refresh() })
|
|
2863
|
+
.catch((err) => s.setRollbackModal((prev) => ({ ...(prev ?? {}), error: String((err && err.message) || err) })))
|
|
2864
|
+
}
|
|
2865
|
+
const keepAsset = (assetId) => () => {
|
|
2866
|
+
s.setError('')
|
|
2867
|
+
api('/evolution/rollback', { assetId, human_confirmed: true, action: 'keep' })
|
|
2868
|
+
.then(() => { s.setSavedNotice(ZH.adviceKeepSuccess); refresh() })
|
|
2869
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
2870
|
+
}
|
|
2871
|
+
return {
|
|
2872
|
+
openApplyModal: (profileKey, candidateId) => () => s.setApplyModal({ profileKey, candidateId, open: true, error: '' }),
|
|
2873
|
+
closeApplyModal: closeApply,
|
|
2874
|
+
applyCandidate,
|
|
2875
|
+
openRollbackModal: (assetId) => () => s.setRollbackModal({ assetId, open: true, error: '' }),
|
|
2876
|
+
closeRollbackModal: closeRollback,
|
|
2877
|
+
rollbackAsset,
|
|
2878
|
+
keepAsset,
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2660
2882
|
function makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools) {
|
|
2661
2883
|
return {
|
|
2662
2884
|
...makeFormActions(s, loadEfforts, loadTools),
|
|
@@ -2666,6 +2888,7 @@ function makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools
|
|
|
2666
2888
|
...makeEscapeActions(api, s),
|
|
2667
2889
|
...makeCandidateActions(api, s),
|
|
2668
2890
|
...makeCandidateSettingsActions(api, s, refresh, applyWrite),
|
|
2891
|
+
...makeApplyFlowActions(api, s, refresh),
|
|
2669
2892
|
...makeTabActions(s),
|
|
2670
2893
|
locateProfile: makeLocateProfile(s),
|
|
2671
2894
|
}
|
|
@@ -2798,7 +3021,7 @@ function buildProfileRows(el, chip, s, actions) {
|
|
|
2798
3021
|
? el('p', { className: 'sap-costLine' },
|
|
2799
3022
|
[
|
|
2800
3023
|
typeof profile.avgCost === 'number' ? `平均 ≈${profile.avgCost} 元/次` : '',
|
|
2801
|
-
typeof profile.successRate === 'number' ?
|
|
3024
|
+
typeof profile.successRate === 'number' ? `近期表现 ${Math.round(profile.successRate * 100)}/100${typeof profile.n === 'number' ? `(基于最近 ${profile.n} 次派发)` : ''}` : ''
|
|
2802
3025
|
].filter(Boolean).join(' · '))
|
|
2803
3026
|
: null
|
|
2804
3027
|
)
|
|
@@ -2907,14 +3130,20 @@ function candidateValidityText(expiry) {
|
|
|
2907
3130
|
return ZH.candidateValidityPrefix + formatMonthDayTime(expiry)
|
|
2908
3131
|
}
|
|
2909
3132
|
|
|
2910
|
-
// 指标行:把「平均耗时 <ms>ms」换算成短时(40.4s
|
|
3133
|
+
// 指标行:把「平均耗时 <ms>ms」换算成短时(40.4s 等);第 1 个指标恒为成功分行,
|
|
3134
|
+
// 历史台账里的旧标签「加权成功分」在此显示层映射为「近期表现分」(值保持 0~1
|
|
3135
|
+
// 原样不改口径,0~100 视图在候选生成信息行)。其余指标原样(信息不删)。
|
|
2911
3136
|
function adviceMetricsOf(performanceText) {
|
|
2912
|
-
return adviceEvidenceOf(performanceText).metrics.map((metric) => {
|
|
3137
|
+
return adviceEvidenceOf(performanceText).metrics.map((metric, index) => {
|
|
2913
3138
|
const hit = /^平均耗时 (.+)$/.exec(metric)
|
|
2914
3139
|
if (hit !== null) {
|
|
2915
3140
|
const ms = Number(hit[1])
|
|
2916
3141
|
return Number.isFinite(ms) ? '平均耗时 ' + formatDurationMs(ms) : metric
|
|
2917
3142
|
}
|
|
3143
|
+
if (index === 0) {
|
|
3144
|
+
const space = metric.indexOf(' ')
|
|
3145
|
+
return space > 0 ? '近期表现分 ' + metric.slice(space + 1) : metric
|
|
3146
|
+
}
|
|
2918
3147
|
return metric
|
|
2919
3148
|
})
|
|
2920
3149
|
}
|
|
@@ -2968,7 +3197,7 @@ function dispatchModeZh(mode) {
|
|
|
2968
3197
|
return mode
|
|
2969
3198
|
}
|
|
2970
3199
|
|
|
2971
|
-
//
|
|
3200
|
+
// 表现摘要拆两层:计数(完成/失败/终止)与指标(近期表现分/平均耗时/平均输出),
|
|
2972
3201
|
// 各渲染一遍;解析不出的部分原样保留(信息不删)。
|
|
2973
3202
|
function adviceEvidenceOf(performanceText) {
|
|
2974
3203
|
const text = typeof performanceText === 'string' ? performanceText : ''
|
|
@@ -3058,13 +3287,19 @@ function adviceSectionOf(el, title, children) {
|
|
|
3058
3287
|
}
|
|
3059
3288
|
|
|
3060
3289
|
// 单张建议卡(v10):摘要行 = 对象/建议/置信度/▸(结论三件套,无加权分);
|
|
3061
|
-
// 展开区 =
|
|
3290
|
+
// 展开区 = 观察期状态行 → 小节序列(理由/近期表现/派发明细/候选方案)→ 撤销建议区
|
|
3291
|
+
// (regress)→ 进化轨迹 → 操作区;确认/撤销弹窗就地渲染在卡内。
|
|
3062
3292
|
function buildAdviceCard(el, s, actions, a, candidates, sessions) {
|
|
3063
3293
|
const profile = matchingProfileOf(s.profiles, a.profileKey)
|
|
3064
3294
|
const metrics = adviceMetricsOf(a.performanceText)
|
|
3065
3295
|
const rows = s.adviceDetails && typeof s.adviceDetails === 'object' && Array.isArray(s.adviceDetails[a.profileKey]) ? s.adviceDetails[a.profileKey] : []
|
|
3066
3296
|
const openMap = s.adviceOpen !== null && typeof s.adviceOpen === 'object' ? s.adviceOpen : {}
|
|
3067
3297
|
const open = openMap[a.profileKey] === true
|
|
3298
|
+
const assetView = adviceAssetViewOf(s, a.profileKey)
|
|
3299
|
+
const selectedId = candidateSelectedIdOf(s, a.profileKey, candidates)
|
|
3300
|
+
const selected = candidates.find((c) => c !== null && typeof c === 'object' && c.id === selectedId)
|
|
3301
|
+
const applyModal = s.applyModal ?? {}
|
|
3302
|
+
const rollbackModal = s.rollbackModal ?? {}
|
|
3068
3303
|
const summary = el('button', { type: 'button', className: 'sap-adviceSummary', 'aria-expanded': open, onClick: actions.toggleAdviceOpen(a.profileKey) },
|
|
3069
3304
|
el('span', { className: 'sap-adviceObjLine' },
|
|
3070
3305
|
el('span', { className: 'sap-adviceObj' }, adviceSummaryObjectText(profile, a.profileKey)),
|
|
@@ -3075,11 +3310,16 @@ function buildAdviceCard(el, s, actions, a, candidates, sessions) {
|
|
|
3075
3310
|
el('span', { className: 'sap-adviceConf' }, ZH.adviceConfidence + confidenceZhOf(a.confidence)),
|
|
3076
3311
|
el('span', { className: 'sap-adviceSummaryArrow' }, '▸')))
|
|
3077
3312
|
const detail = open !== true ? null : el('div', { className: 'sap-adviceDetail' },
|
|
3313
|
+
buildAdviceStatusLine(el, assetView),
|
|
3078
3314
|
typeof a.reasonText === 'string' && a.reasonText !== '' ? adviceSectionOf(el, ZH.adviceSectionReason, el('p', { className: 'sap-adviceSectionBody' }, a.reasonText)) : null,
|
|
3079
3315
|
metrics.length > 0 ? adviceSectionOf(el, ZH.adviceSectionPerf, el('p', { className: 'sap-adviceSectionBody' }, metrics.join(' · '))) : null,
|
|
3080
3316
|
adviceSectionOf(el, ZH.adviceSectionDetail, buildAdviceDetails(el, s, actions, a.profileKey, rows, sessions)),
|
|
3081
3317
|
candidates.length > 0 ? buildAdviceCandidates(el, s, actions, a, candidates) : null,
|
|
3082
|
-
|
|
3318
|
+
assetView !== null ? buildRollbackSuggestion(el, s, actions, assetView) : null,
|
|
3319
|
+
buildAdviceTimeline(el, assetView),
|
|
3320
|
+
buildAdviceFoot(el, s, actions, a, profile, candidates, assetView),
|
|
3321
|
+
applyModal.open === true && applyModal.profileKey === a.profileKey && selected !== undefined ? buildApplyModal(el, s, actions, a, profile, selected) : null,
|
|
3322
|
+
rollbackModal.open === true && assetView !== null && rollbackModal.assetId === assetView.id ? buildRollbackModal(el, s, actions, assetView) : null)
|
|
3083
3323
|
return el('li', { key: String(a.profileKey), className: 'sap-adviceItem' + (open === true ? ' sap-adviceItemOpen' : '') }, summary, detail)
|
|
3084
3324
|
}
|
|
3085
3325
|
|
|
@@ -3113,6 +3353,173 @@ function buildAdviceDetails(el, s, actions, profileKey, rows, sessions) {
|
|
|
3113
3353
|
)
|
|
3114
3354
|
}
|
|
3115
3355
|
|
|
3356
|
+
// ── 观察期/撤销建议/进化轨迹(数据源 = /evolution/assets 派生视图)──────────
|
|
3357
|
+
|
|
3358
|
+
// 建议卡对应资产视图:同键资产取最近一条(观察期/已应用优先于纯草稿);无资产 → null。
|
|
3359
|
+
function adviceAssetViewOf(s, profileKey) {
|
|
3360
|
+
const assets = Array.isArray(s.evolutionAssets) ? s.evolutionAssets : []
|
|
3361
|
+
const owned = assets.filter((a) => a && typeof a === 'object' && a.profile_key === profileKey)
|
|
3362
|
+
if (owned.length === 0) return null
|
|
3363
|
+
const rank = (a) => (a && a.canary && typeof a.canary === 'object' && a.canary.state !== 'pendingApply' ? 1 : 0)
|
|
3364
|
+
return owned.sort((a, b) => rank(b) - rank(a) || (Number.isFinite(b.updatedAt) ? b.updatedAt : 0) - (Number.isFinite(a.updatedAt) ? a.updatedAt : 0))[0]
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
// 候选行徽标对应资产(按候选 id = 资产 id)。
|
|
3368
|
+
function candidateAssetByIdOf(s, id) {
|
|
3369
|
+
const assets = Array.isArray(s.evolutionAssets) ? s.evolutionAssets : []
|
|
3370
|
+
return assets.find((a) => a && a.id === id) ?? null
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
// 观察期状态行:regress 警示强调(结论先行);其余次级色;pendingApply 不显示。
|
|
3374
|
+
function buildAdviceStatusLine(el, view) {
|
|
3375
|
+
const canary = view !== null && typeof view === 'object' ? view.canary : null
|
|
3376
|
+
if (canary === null || typeof canary !== 'object' || canary.state === 'pendingApply') return null
|
|
3377
|
+
const warn = canary.state === 'regress'
|
|
3378
|
+
const suffix = canary.state === 'observing' ? ZH.adviceStatusObservingSuffix : ''
|
|
3379
|
+
return el('div', { className: 'sap-adviceStatusLine' + (warn ? ' sap-adviceStatusLineWarn' : '') },
|
|
3380
|
+
el('span', null, String(canary.label ?? '') + suffix))
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
// 恢复目标名:fromSnapshot 方案名列表;空快照回退通用文案(回滚 = 移除应用方案)。
|
|
3384
|
+
function rollbackRestoreNameOf(asset) {
|
|
3385
|
+
const snapshot = asset !== null && typeof asset === 'object' ? asset.fromSnapshot : null
|
|
3386
|
+
const profiles = snapshot !== null && typeof snapshot === 'object' && Array.isArray(snapshot.profiles) ? snapshot.profiles : []
|
|
3387
|
+
const names = profiles.map((p) => (p && p.config && typeof p.config.name === 'string' && p.config.name !== '' ? p.config.name : (p && p.config && p.config.id))).filter((n) => typeof n === 'string' && n !== '')
|
|
3388
|
+
return names.length > 0 ? names.join('、') : ZH.adviceRestoreFallback
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// 表现分 → 百分制整数(证据对照行口径,与「满分 100」一致)。
|
|
3392
|
+
function adviceScore100(score) {
|
|
3393
|
+
return Number.isFinite(score) ? Math.round(score * 100) : '—'
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
function rollbackEvidenceTextOf(asset) {
|
|
3397
|
+
const canary = asset !== null && typeof asset === 'object' && asset.canary !== null && typeof asset.canary === 'object' ? asset.canary : {}
|
|
3398
|
+
const post = canary.post !== null && typeof canary.post === 'object' ? canary.post : null
|
|
3399
|
+
const base = canary.baseline !== null && typeof canary.baseline === 'object' ? canary.baseline : null
|
|
3400
|
+
const n = post !== null && Number.isFinite(post.sampleN) ? post.sampleN : 0
|
|
3401
|
+
return ZH.adviceRollbackEvidence.replace('{before}', adviceScore100(base === null ? null : base.weighted_success)).replace('{after}', adviceScore100(post === null ? null : post.weighted_success)).replace('{n}', n)
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function tierZhOf(tier) {
|
|
3405
|
+
return tier === 'cheap' ? '低' : tier === 'premium' ? '高' : '中'
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
// 撤销确认弹窗「会改变」行:模型/预算档位/推理强度变化 + 自定义项一并恢复。
|
|
3409
|
+
function rollbackChangeTextOf(asset) {
|
|
3410
|
+
const parts = []
|
|
3411
|
+
const applied = asset !== null && typeof asset === 'object' && asset.applied_config !== null && typeof asset.applied_config === 'object' ? asset.applied_config : {}
|
|
3412
|
+
const snapshot = asset !== null && typeof asset === 'object' && asset.fromSnapshot !== null && typeof asset.fromSnapshot === 'object' && Array.isArray(asset.fromSnapshot.profiles) ? asset.fromSnapshot.profiles : []
|
|
3413
|
+
const before = snapshot.length > 0 && snapshot[0].config !== null && typeof snapshot[0].config === 'object' ? snapshot[0].config : {}
|
|
3414
|
+
if (before.model !== undefined || applied.model !== undefined) parts.push('模型 ' + (before.model || '未指定') + ' → ' + (applied.model || '未指定'))
|
|
3415
|
+
if (before.tokenTier !== undefined && applied.tokenTier !== undefined && before.tokenTier !== applied.tokenTier) {
|
|
3416
|
+
parts.push('预算档位 ' + tierZhOf(before.tokenTier) + ' → ' + tierZhOf(applied.tokenTier) + ',成本可能回升')
|
|
3417
|
+
} else if (before.reasoningEffort !== undefined && applied.reasoningEffort !== undefined && before.reasoningEffort !== applied.reasoningEffort) {
|
|
3418
|
+
parts.push('推理强度已上调,成本可能回升')
|
|
3419
|
+
}
|
|
3420
|
+
parts.push(ZH.adviceRollbackChangeSuffix)
|
|
3421
|
+
return parts.join(';')
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
// 撤销建议区(regress):结论先行在状态行,本区 = 建议/恢复为/证据 + 双按钮;
|
|
3425
|
+
// 用户已选择保留 → 只读「已选择」行;无表态前常驻只读(不自动执行任何动作)。
|
|
3426
|
+
function buildRollbackSuggestion(el, s, actions, asset) {
|
|
3427
|
+
const canary = asset !== null && typeof asset === 'object' && asset.canary !== null && typeof asset.canary === 'object' ? asset.canary : {}
|
|
3428
|
+
if (canary.state !== 'regress') return null
|
|
3429
|
+
if (canary.kept === true) {
|
|
3430
|
+
return el('div', { className: 'sap-rollbackBox' }, el('p', { className: 'sap-rollbackBoxKeep' }, ZH.adviceRollbackKeepDone))
|
|
3431
|
+
}
|
|
3432
|
+
return el('div', { className: 'sap-rollbackBox' },
|
|
3433
|
+
el('p', { className: 'sap-applyModalLine' }, ZH.adviceRollbackBoxAdvice),
|
|
3434
|
+
el('p', { className: 'sap-rollbackBoxLine' }, ZH.adviceRollbackRestorePrefix, el('b', null, rollbackRestoreNameOf(asset))),
|
|
3435
|
+
el('p', { className: 'sap-rollbackBoxLine' }, rollbackEvidenceTextOf(asset)),
|
|
3436
|
+
el('div', { className: 'sap-rollbackBoxActions' },
|
|
3437
|
+
el('button', { type: 'button', className: 'sap-secondaryButton sap-rollbackDanger', style: smallBtn, onClick: actions.openRollbackModal(asset.id) }, ZH.adviceRollbackBtn),
|
|
3438
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.keepAsset(asset.id) }, ZH.adviceRollbackKeepBtn)))
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
// 成本与能力影响行(确认弹窗):降级/上调方向 + 预算档位变化。
|
|
3442
|
+
function applyImpactTextOf(profile, candidate, suggestion) {
|
|
3443
|
+
const cfg = candidate !== null && typeof candidate === 'object' && candidate.config !== null && typeof candidate.config === 'object' ? candidate.config : {}
|
|
3444
|
+
const parts = [suggestion === '降' ? ZH.adviceApplyImpactDown : suggestion === '升' ? ZH.adviceApplyImpactUp : ZH.adviceApplyImpactFlat]
|
|
3445
|
+
if (profile !== null && cfg.tokenTier !== undefined && profile.tokenTier !== undefined && cfg.tokenTier !== profile.tokenTier) {
|
|
3446
|
+
parts.push('预算档位 ' + tierZhOf(profile.tokenTier) + ' → ' + tierZhOf(cfg.tokenTier))
|
|
3447
|
+
}
|
|
3448
|
+
return parts.join(';')
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
// 应用确认弹窗:候选摘要(名称/方向/安全检查)/成本与能力影响/两条说明行;
|
|
3452
|
+
// 主按钮「确认调整方案」即执行(humanConfirmed 由点击触发,无强制勾选),失败红字回显。
|
|
3453
|
+
function buildApplyModal(el, s, actions, a, profile, candidate) {
|
|
3454
|
+
const modal = s.applyModal ?? {}
|
|
3455
|
+
const preview = s.draftPreviews !== null && typeof s.draftPreviews === 'object' ? s.draftPreviews[candidate.id] : undefined
|
|
3456
|
+
const checks = preview !== undefined && preview.checks !== null && typeof preview.checks === 'object' && Array.isArray(preview.checks) ? preview.checks : []
|
|
3457
|
+
const safety = preview === undefined ? ZH.candidateSafetyPending
|
|
3458
|
+
: preview.ok === true ? ZH.adviceApplySafetyNote + ZH.candidateSafetyPass.replace(' ✓', '') + ' ' + checks.length + '/' + checks.length
|
|
3459
|
+
: ZH.adviceApplySafetyNote + ZH.candidateSafetyFail
|
|
3460
|
+
return el('div', { className: 'sap-applyModal' },
|
|
3461
|
+
el('div', { className: 'sap-applyModalHead' }, el('span', { className: 'sap-applyModalTitle' }, ZH.adviceApplyConfirm)),
|
|
3462
|
+
el('div', null,
|
|
3463
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceApplyModalSummary),
|
|
3464
|
+
el('p', { className: 'sap-applyModalLine' }, candidate.name + ' · ' + directionZh(a.suggestion)),
|
|
3465
|
+
el('p', { className: 'sap-applyModalLineDim' }, safety)),
|
|
3466
|
+
el('div', null,
|
|
3467
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceApplyModalImpact),
|
|
3468
|
+
el('p', { className: 'sap-applyModalLine' }, applyImpactTextOf(profile, candidate, a.suggestion))),
|
|
3469
|
+
el('p', { className: 'sap-applyModalLineDim' }, ZH.adviceApplyDefaultNote),
|
|
3470
|
+
el('p', { className: 'sap-applyModalLineDim' }, ZH.adviceApplyObserveNote),
|
|
3471
|
+
modal.error !== '' ? el('p', { className: 'sap-notice' }, modal.error) : null,
|
|
3472
|
+
el('div', { className: 'sap-applyModalActions' },
|
|
3473
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', onClick: actions.closeApplyModal }, ZH.adviceApplyCancel),
|
|
3474
|
+
el('button', { type: 'button', className: 'sap-primaryButton', onClick: actions.applyCandidate }, ZH.adviceApplyConfirm)))
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
// 撤销确认弹窗:恢复为/会改变/说明 + 确认撤销(朱砂描边)/取消;失败红字回显。
|
|
3478
|
+
function buildRollbackModal(el, s, actions, asset) {
|
|
3479
|
+
const modal = s.rollbackModal ?? {}
|
|
3480
|
+
return el('div', { className: 'sap-applyModal' },
|
|
3481
|
+
el('div', { className: 'sap-applyModalHead' }, el('span', { className: 'sap-applyModalTitle' }, ZH.adviceRollbackModalTitle)),
|
|
3482
|
+
el('div', null,
|
|
3483
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceRollbackModalRestore),
|
|
3484
|
+
el('p', { className: 'sap-applyModalLine' }, rollbackRestoreNameOf(asset))),
|
|
3485
|
+
el('div', null,
|
|
3486
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceRollbackModalChange),
|
|
3487
|
+
el('p', { className: 'sap-applyModalLine' }, rollbackChangeTextOf(asset))),
|
|
3488
|
+
el('p', { className: 'sap-applyModalLineDim' }, ZH.adviceRollbackModalNote),
|
|
3489
|
+
modal.error !== '' ? el('p', { className: 'sap-notice' }, modal.error) : null,
|
|
3490
|
+
el('div', { className: 'sap-applyModalActions' },
|
|
3491
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', onClick: actions.closeRollbackModal }, ZH.adviceApplyCancel),
|
|
3492
|
+
el('button', { type: 'button', className: 'sap-secondaryButton sap-rollbackDanger', onClick: actions.rollbackAsset }, ZH.adviceRollbackConfirm)))
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// 轨迹单事件文案:生成(MM-DD)/应用(MM-DD · 人工确认)/观察(k/kTotal)/
|
|
3496
|
+
// 判定:…/撤销·保留:…(未发生步骤灰字由 buildAdviceTimeline 补)。
|
|
3497
|
+
function timelineEventTextOf(event) {
|
|
3498
|
+
if (event.event === 'generated') return '生成(' + formatMonthDayTime(event.ts) + ')'
|
|
3499
|
+
if (event.event === 'applied') return '应用(' + formatMonthDayTime(event.ts) + (typeof event.meta === 'string' && event.meta !== '' ? ' · ' + event.meta : '') + ')'
|
|
3500
|
+
if (event.event === 'observed') return '观察(' + (typeof event.meta === 'string' ? event.meta : '') + ')'
|
|
3501
|
+
if (event.event === 'judged') return '判定:' + (typeof event.meta === 'string' && event.meta !== '' ? event.meta : ZH.adviceTimelinePending)
|
|
3502
|
+
if (event.event === 'resolved') return '撤销/保留:' + (typeof event.meta === 'string' && event.meta !== '' ? event.meta : ZH.adviceTimelinePending)
|
|
3503
|
+
return event.event
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// 进化轨迹小节:assets lifecycle 行式时间线;未应用(无 applied 事件)或缺生命周期
|
|
3507
|
+
// 数据 → 隐藏小节。未发生步骤(判定/处理)灰字补位。
|
|
3508
|
+
function buildAdviceTimeline(el, view) {
|
|
3509
|
+
const lifecycle = view !== null && typeof view === 'object' && Array.isArray(view.lifecycle) ? view.lifecycle : []
|
|
3510
|
+
if (lifecycle.length === 0 || !lifecycle.some((e) => e.event === 'applied')) return null
|
|
3511
|
+
const chain = []
|
|
3512
|
+
const push = (text, dim) => chain.push(el('span', { key: String(chain.length), className: dim === true ? 'sap-adviceTimelineStepDim' : 'sap-adviceTimelineStep' }, text))
|
|
3513
|
+
for (const event of lifecycle) {
|
|
3514
|
+
if (chain.length > 0) chain.push(el('span', { key: 'sep' + chain.length }, ZH.adviceTimelineSep))
|
|
3515
|
+
push(timelineEventTextOf(event), false)
|
|
3516
|
+
}
|
|
3517
|
+
const has = (kind) => lifecycle.some((e) => e.event === kind)
|
|
3518
|
+
if (!has('judged')) { chain.push(el('span', { key: 'sepd' }, ZH.adviceTimelineSep)); push('判定:' + ZH.adviceTimelinePending, true) }
|
|
3519
|
+
if (!has('resolved')) { chain.push(el('span', { key: 'sepr' }, ZH.adviceTimelineSep)); push('撤销/保留:' + ZH.adviceTimelinePending, true) }
|
|
3520
|
+
return adviceSectionOf(el, ZH.adviceTimelineTitle, el('p', { className: 'sap-adviceTimelineLine' }, chain))
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3116
3523
|
// 候选管理设置(顶部一行 + 就地浮层):更新模式与有效期用户可配;
|
|
3117
3524
|
// 自定义按天数输入(1~365),提交时换算小时数。
|
|
3118
3525
|
function candidateModeTextOf(mode) {
|
|
@@ -3207,10 +3614,13 @@ function buildAdviceCandidateRow(el, s, actions, a, candidate, index, selected)
|
|
|
3207
3614
|
if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
3208
3615
|
actions.selectCandidate(a.profileKey, candidate.id)()
|
|
3209
3616
|
}
|
|
3617
|
+
const asset = candidateAssetByIdOf(s, candidate.id)
|
|
3618
|
+
const stateLabel = asset !== null && typeof asset.stateLabel === 'string' && asset.stateLabel !== '' ? asset.stateLabel : null
|
|
3210
3619
|
return el('div', { key: String(candidate.id), className: 'sap-candidate' + (selected === candidate.id ? ' sap-candidateSelected' : '') },
|
|
3211
3620
|
el('button', { type: 'button', className: 'sap-candidateRow', 'aria-expanded': open, onClick: actions.toggleCandidateOpen(candidate) },
|
|
3212
3621
|
el('span', { role: 'radio', 'aria-checked': selected === candidate.id, className: 'sap-candidateRadio', onClick: onRadio }),
|
|
3213
3622
|
el('span', { className: 'sap-candidateName' }, '候选 ' + (index + 1) + ' · ' + candidate.name),
|
|
3623
|
+
stateLabel !== null ? el('span', { className: 'sap-candidateState' }, stateLabel) : null,
|
|
3214
3624
|
el('span', { className: 'sap-candidateValidity' }, candidateValidityText(candidate.expiry)),
|
|
3215
3625
|
el('span', { className: 'sap-candidateArrow' }, '▸')),
|
|
3216
3626
|
open === true ? el('div', { className: 'sap-candidateBody' },
|
|
@@ -3233,7 +3643,7 @@ function buildAdviceCandidateRow(el, s, actions, a, candidate, index, selected)
|
|
|
3233
3643
|
}
|
|
3234
3644
|
|
|
3235
3645
|
// 生成信息行(最浅灰,两分支模板):有样本 = 生成于 MM-DD · 基于最近 N 次派发 ·
|
|
3236
|
-
// 近期表现 X(满分 100,X=round(
|
|
3646
|
+
// 近期表现 X(满分 100,X=round(表现分×100));无样本 = 生成于 MM-DD · 暂无近期派发
|
|
3237
3647
|
// 样本,仅供参考(不显示 0 分);无生成时间省去「生成于」段。
|
|
3238
3648
|
function candidateGeneratedLine(el, candidate) {
|
|
3239
3649
|
const basis = candidate.basis !== null && typeof candidate.basis === 'object' ? candidate.basis : {}
|
|
@@ -3251,24 +3661,36 @@ function candidateGeneratedLine(el, candidate) {
|
|
|
3251
3661
|
return el('p', { className: 'sap-candidateHint' }, parts.join(' · '))
|
|
3252
3662
|
}
|
|
3253
3663
|
|
|
3254
|
-
// 卡底部:无对应方案 = [保存为方案](统一主按钮)+
|
|
3255
|
-
//
|
|
3256
|
-
|
|
3664
|
+
// 卡底部:无对应方案 = [保存为方案](统一主按钮)+ 备注;有对应方案 = [按建议调整
|
|
3665
|
+
// 方案] + 定位。按钮启用条件 = applyEnabled && 已选候选;禁用 title = 禁用原因
|
|
3666
|
+
// (冷启动 = reasons.join(';') 并附卡脚一行灰字;观察中 = 暂不支持再次调整)。
|
|
3667
|
+
function buildAdviceFoot(el, s, actions, a, profile, candidates, assetView) {
|
|
3257
3668
|
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
3669
|
const generatedAt = formatMonthDayTime(basis.generatedAt)
|
|
3259
3670
|
const generatedText = generatedAt !== '' ? ZH.candidateGeneratedPrefix + generatedAt : ''
|
|
3260
|
-
const noteLines = (
|
|
3261
|
-
|
|
3262
|
-
generatedText !== '' ? el('p', { className: 'sap-adviceFootNote' }, generatedText) : null)
|
|
3263
|
-
if (profile !== null) {
|
|
3671
|
+
const noteLines = (lines) => el('div', { className: 'sap-adviceFootNotes' }, lines.map((line) => el('p', { key: line, className: 'sap-adviceFootNote' }, line)))
|
|
3672
|
+
if (profile === null) {
|
|
3264
3673
|
return el('div', { className: 'sap-adviceFoot' },
|
|
3265
|
-
el('button', { type: 'button', className: 'sap-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
}
|
|
3674
|
+
el('button', { type: 'button', className: 'sap-primaryButton', style: smallBtn, onClick: actions.saveAdviceProfile(a.profileKey) }, ZH.adviceSaveProfile),
|
|
3675
|
+
noteLines([ZH.adviceFootNoteSave]))
|
|
3676
|
+
}
|
|
3677
|
+
const status = s.evolutionStatus ?? {}
|
|
3678
|
+
const applyEnabled = status.applyEnabled === true
|
|
3679
|
+
const selectedId = candidateSelectedIdOf(s, a.profileKey, candidates)
|
|
3680
|
+
const canaryState = assetView !== null && assetView.canary !== null && typeof assetView.canary === 'object' ? assetView.canary.state : 'pendingApply'
|
|
3681
|
+
const observing = canaryState === 'observing' || canaryState === 'stalled' || canaryState === 'inconclusive' || canaryState === 'regress' || canaryState === 'aborted'
|
|
3682
|
+
let disabledTitle = ''
|
|
3683
|
+
if (observing) disabledTitle = ZH.adviceApplyObservingTitle
|
|
3684
|
+
else if (!applyEnabled) disabledTitle = Array.isArray(status.reasons) && status.reasons.length > 0 ? status.reasons.join(';') : ZH.candidateNotApplicable
|
|
3685
|
+
else if (selectedId === '') disabledTitle = ZH.adviceApplySelectTitle
|
|
3686
|
+
const disabled = disabledTitle !== ''
|
|
3687
|
+
const notes = [ZH.adviceFootNoteMatchedPrefix + (profile.name || profile.id) + '(' + profile.id + ')']
|
|
3688
|
+
if (generatedText !== '') notes.push(generatedText)
|
|
3689
|
+
if (!applyEnabled && !observing) notes.push(disabledTitle)
|
|
3269
3690
|
return el('div', { className: 'sap-adviceFoot' },
|
|
3270
|
-
el('button', { type: 'button', className: 'sap-
|
|
3271
|
-
|
|
3691
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, disabled, title: disabledTitle, onClick: disabled ? undefined : actions.openApplyModal(a.profileKey, selectedId) }, ZH.adviceApplyDisabled),
|
|
3692
|
+
el('button', { type: 'button', className: 'sap-detailToggle', onClick: actions.locateProfile(profile.id) }, ZH.adviceRowLocate),
|
|
3693
|
+
noteLines(notes))
|
|
3272
3694
|
}
|
|
3273
3695
|
|
|
3274
3696
|
// 逃生舱卡片:开关 + (打开时)显式警告文案 + 放行集增删(输入框 + 添加按钮,
|
|
@@ -3543,26 +3965,29 @@ function buildSectionReturn(el, s, actions, rows, addCard, reminderCenter, sessi
|
|
|
3543
3965
|
...notices,
|
|
3544
3966
|
buildTabBar(el, s),
|
|
3545
3967
|
buildTabZone(el, s, 'profiles',
|
|
3546
|
-
|
|
3547
|
-
el
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
el('
|
|
3968
|
+
el('div', { className: 'sap-zoneStack' },
|
|
3969
|
+
buildSettingsCard(el, 1, '方案管理', '管理派发可用的方案:启用、编辑、删除,含成本与成功率', true, () => {}, [
|
|
3970
|
+
el('p', { className: 'sap-intro' }, ZH.profilesIntro),
|
|
3971
|
+
el('ul', { className: 'sap-rows' }, rows),
|
|
3972
|
+
buildDraftRows(el, s, actions),
|
|
3973
|
+
el('div', { className: 'sap-addBlock' },
|
|
3974
|
+
s.adding ? addCard : el('div', { className: 'sap-addActions' },
|
|
3975
|
+
el('button', { className: 'sap-addButton', onClick: actions.openAdd }, '+ 新增 profile')
|
|
3976
|
+
)
|
|
3977
|
+
),
|
|
3978
|
+
el('div', { className: 'sap-addActions', style: { marginTop: '4px' } },
|
|
3979
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.resetAll }, ZH.resetAllBuiltins)
|
|
3553
3980
|
)
|
|
3554
|
-
),
|
|
3555
|
-
el
|
|
3556
|
-
|
|
3557
|
-
)
|
|
3558
|
-
], true)),
|
|
3981
|
+
], true),
|
|
3982
|
+
buildMaintenanceCard(el, s, actions)
|
|
3983
|
+
)),
|
|
3559
3984
|
buildTabZone(el, s, 'cost',
|
|
3560
3985
|
buildSettingsCard(el, 2, '全局成本', '跨全部会话的派发成本汇总,按模型与方案拆分', true, () => {}, [
|
|
3561
3986
|
buildCostObservabilitySection(el, s, actions)
|
|
3562
3987
|
], true)),
|
|
3563
3988
|
buildTabZone(el, s, 'advice', buildAdvicePanel(el, s, actions, sessions)),
|
|
3564
3989
|
buildTabZone(el, s, 'safety', buildSafetySection(el, s, actions, s.auditReminders)),
|
|
3565
|
-
buildTabZone(el, s, 'reminders', reminderCenter)
|
|
3990
|
+
buildTabZone(el, s, 'reminders', el(reminderCenter, { jumpToAdvice: () => s.setActiveTab('advice') }))
|
|
3566
3991
|
)
|
|
3567
3992
|
}
|
|
3568
3993
|
|
|
@@ -3601,21 +4026,138 @@ function attachDraftActions(api, s, refresh, applyWrite, actions) {
|
|
|
3601
4026
|
}
|
|
3602
4027
|
}
|
|
3603
4028
|
|
|
4029
|
+
// ── 设置页:数据维护区(快照开关/单条查看 + 四区清理按钮组)───────────────────
|
|
4030
|
+
|
|
4031
|
+
// 快照区数据装载 hook:挂载拉一次,开关切换与清理后回读(单条查看状态同驻)。
|
|
4032
|
+
function useSnapshotData(useState, useEffect) {
|
|
4033
|
+
const [snapshots, setSnapshots] = useState({ snapshotEnabled: false, count: 0, cap: 200, entries: [] })
|
|
4034
|
+
const [snapshotView, setSnapshotView] = useState({})
|
|
4035
|
+
const [maintenanceNotice, setMaintenanceNotice] = useState('')
|
|
4036
|
+
const refreshSnapshots = () => {
|
|
4037
|
+
api('/snapshots').then((data) => {
|
|
4038
|
+
setSnapshots({
|
|
4039
|
+
snapshotEnabled: data !== null && typeof data === 'object' && data.snapshotEnabled === true,
|
|
4040
|
+
count: data !== null && typeof data === 'object' && typeof data.count === 'number' ? data.count : 0,
|
|
4041
|
+
cap: data !== null && typeof data === 'object' && typeof data.cap === 'number' ? data.cap : 200,
|
|
4042
|
+
entries: data !== null && typeof data === 'object' && Array.isArray(data.entries) ? data.entries : []
|
|
4043
|
+
})
|
|
4044
|
+
}).catch(() => {})
|
|
4045
|
+
}
|
|
4046
|
+
useEffect(() => { refreshSnapshots() }, [])
|
|
4047
|
+
return { snapshots, refreshSnapshots, snapshotView, setSnapshotView, maintenanceNotice, setMaintenanceNotice }
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
// 数据维护动作:快照开关、单条查看(展开/收起)、四区清理(确认一次、结果 echo)。
|
|
4051
|
+
function attachMaintenanceActions(api, s, refresh, actions) {
|
|
4052
|
+
actions.toggleSnapshots = () => {
|
|
4053
|
+
api('/snapshots/set-enabled', { snapshotEnabled: !s.snapshots.snapshotEnabled })
|
|
4054
|
+
.then((data) => {
|
|
4055
|
+
if (data !== null && typeof data === 'object' && data.persisted === false) s.setPersistWarning(ZH.persistFail)
|
|
4056
|
+
else s.setPersistWarning('')
|
|
4057
|
+
s.refreshSnapshots()
|
|
4058
|
+
})
|
|
4059
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
4060
|
+
}
|
|
4061
|
+
actions.viewSnapshot = (id) => () => {
|
|
4062
|
+
const next = { ...s.snapshotView }
|
|
4063
|
+
if (next[id] !== undefined) { delete next[id]; s.setSnapshotView(next); return }
|
|
4064
|
+
api('/snapshot?id=' + encodeURIComponent(id))
|
|
4065
|
+
.then((data) => {
|
|
4066
|
+
if (data !== null && typeof data === 'object' && data.ok === true && data.snapshot !== undefined) {
|
|
4067
|
+
s.setSnapshotView({ ...s.snapshotView, [id]: data.snapshot })
|
|
4068
|
+
}
|
|
4069
|
+
})
|
|
4070
|
+
.catch(() => {})
|
|
4071
|
+
}
|
|
4072
|
+
actions.cleanZone = (zone, label) => () => {
|
|
4073
|
+
if (!window.confirm(ZH.maintenanceConfirm.replace('{zone}', label))) return
|
|
4074
|
+
api('/maintenance/clean', { zone })
|
|
4075
|
+
.then((data) => {
|
|
4076
|
+
const count = data !== null && typeof data === 'object' && typeof data.count === 'number' ? data.count : 0
|
|
4077
|
+
s.setMaintenanceNotice(ZH.maintenanceDone.replace('{zone}', label).replace('{count}', String(count)))
|
|
4078
|
+
s.refreshSnapshots()
|
|
4079
|
+
refresh()
|
|
4080
|
+
})
|
|
4081
|
+
.catch((err) => s.setError(ZH.maintenanceCleanErr + String((err && err.message) || err)))
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
// 单条快照只读体:persona 默认折叠(本地只读允许看原文,UI 不默认展开);其余
|
|
4086
|
+
// 字段等宽 JSON 展示。
|
|
4087
|
+
function snapshotViewBody(el, payload) {
|
|
4088
|
+
const cfg = payload !== null && typeof payload === 'object' && payload.config !== null && typeof payload.config === 'object' ? payload.config : {}
|
|
4089
|
+
const keys = Object.keys(cfg)
|
|
4090
|
+
const persona = typeof cfg.persona === 'string' ? cfg.persona : ''
|
|
4091
|
+
return el('div', { className: 'sap-reminderFields' },
|
|
4092
|
+
keys.length === 0 ? el('p', { className: 'sap-desc' }, ZH.snapshotEmptyConfig) : null,
|
|
4093
|
+
keys.map((key) => {
|
|
4094
|
+
if (key === 'persona') {
|
|
4095
|
+
return el('details', { key: 'snap-persona', className: 'sap-customized' },
|
|
4096
|
+
el('summary', { className: 'sap-customizedSummary' }, ZH.snapshotPersonaFold.replace('{n}', String(persona.length))),
|
|
4097
|
+
el('pre', { className: 'sap-metaValueDefault' }, persona)
|
|
4098
|
+
)
|
|
4099
|
+
}
|
|
4100
|
+
return reminderFieldLine(el, key, formatGateValue(cfg[key]))
|
|
4101
|
+
})
|
|
4102
|
+
)
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
// 快照开关说明三行分层:首行主说明,后两行小字次要色(数据落点与关闭语义)。
|
|
4106
|
+
function snapshotSwitchDescRows(el) {
|
|
4107
|
+
return ZH.snapshotSwitchDesc.split('\n').filter((line) => line !== '').map((line, index) => el('p', { key: 'snap-desc-' + index, className: index === 0 ? 'sap-desc' : 'sap-descMinor' }, line))
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
// 数据维护卡(方案管理尾部):快照开关 + B7 文案 + 数量/护栏提示 + 单条查看 +
|
|
4111
|
+
// 维护按钮组(四区各自独立、一次确认、结果 echo、DOM 一行)。
|
|
4112
|
+
function buildMaintenanceCard(el, s, actions) {
|
|
4113
|
+
const snap = s.snapshots
|
|
4114
|
+
const atCap = snap.count >= snap.cap && snap.count > 0
|
|
4115
|
+
const entries = Array.isArray(snap.entries) ? snap.entries : []
|
|
4116
|
+
const zoneButtons = [
|
|
4117
|
+
{ zone: 'candidates', label: ZH.cleanCandidates },
|
|
4118
|
+
{ zone: 'snapshots', label: ZH.cleanSnapshots },
|
|
4119
|
+
{ zone: 'reminders', label: ZH.cleanReminders },
|
|
4120
|
+
{ zone: 'stats', label: ZH.cleanStats }
|
|
4121
|
+
].map((z) => el('button', { key: 'clean-' + z.zone, type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.cleanZone(z.zone, z.label) }, z.label))
|
|
4122
|
+
return buildSettingsCard(el, 11, ZH.maintenanceCardTitle, ZH.maintenanceCardDesc, true, () => {}, [
|
|
4123
|
+
el('label', { className: 'sap-switch' },
|
|
4124
|
+
el('input', { type: 'checkbox', checked: snap.snapshotEnabled === true, onChange: actions.toggleSnapshots }),
|
|
4125
|
+
el('span', null, '配置快照')
|
|
4126
|
+
),
|
|
4127
|
+
el('div', { className: 'sap-maintenanceSwitchDesc' }, snapshotSwitchDescRows(el)),
|
|
4128
|
+
el('p', { className: 'sap-desc' },
|
|
4129
|
+
snap.count > 0 ? ZH.snapshotCount.replace('{n}', String(snap.count)).replace('{cap}', String(snap.cap)) : ZH.snapshotNone
|
|
4130
|
+
),
|
|
4131
|
+
atCap ? el('p', { className: 'sap-notice' }, ZH.snapshotCapHint) : null,
|
|
4132
|
+
entries.map((entry) => {
|
|
4133
|
+
const payload = s.snapshotView[entry.id]
|
|
4134
|
+
return el('div', { key: 'snap-' + entry.id, className: 'sap-candidateSettingsPanel' },
|
|
4135
|
+
el('div', { className: 'sap-candidateSettingsRow' },
|
|
4136
|
+
el('b', null, ZH.snapshotMeta.replace('{id}', entry.id).replace('{ts}', formatMonthDayTime(entry.ts))),
|
|
4137
|
+
el('button', { type: 'button', className: 'sap-candidateSettingsEdit', onClick: actions.viewSnapshot(entry.id) }, payload !== undefined ? ZH.snapshotViewClose : ZH.snapshotView)
|
|
4138
|
+
),
|
|
4139
|
+
payload !== undefined ? snapshotViewBody(el, payload) : null
|
|
4140
|
+
)
|
|
4141
|
+
}),
|
|
4142
|
+
s.maintenanceNotice !== '' ? el('p', { className: 'sap-notice' }, s.maintenanceNotice) : null,
|
|
4143
|
+
el('div', { className: 'sap-candidateSettingsActions' }, zoneButtons)
|
|
4144
|
+
], true)
|
|
4145
|
+
}
|
|
4146
|
+
|
|
3604
4147
|
function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, ReminderCenter, getSessions) {
|
|
3605
4148
|
function ProfilesSection() {
|
|
3606
4149
|
const [profiles, setProfiles] = useState([]); const [options, setOptions] = useState({ models: [], efforts: {}, presets: [] }); const [enabled, setEnabled] = useState(true)
|
|
3607
|
-
const [adding, setAdding] = useState(false); const [editingId, setEditingId] = useState(null); const [error, setError] = useState('')
|
|
3608
|
-
const [
|
|
3609
|
-
const [
|
|
3610
|
-
const [escapeEnabled, setEscapeEnabled] = useState(false); const [escapePresets, setEscapePresets] = useState([]); const [escapeInput, setEscapeInput] = useState('')
|
|
4150
|
+
const [adding, setAdding] = useState(false); const [editingId, setEditingId] = useState(null); const [error, setError] = useState(''); const [savedNotice, setSavedNotice] = useState('')
|
|
4151
|
+
const [persistWarning, setPersistWarning] = useState(''); const [form, setForm] = useState(EMPTY_FORM); const [tools, setTools] = useState([]); const [versionWarnings, setVersionWarnings] = useState([])
|
|
4152
|
+
const [pluginVersion, setPluginVersion] = useState(''); const [audit, setAudit] = useState(AUDIT_DEFAULTS); const [escapeEnabled, setEscapeEnabled] = useState(false); const [escapePresets, setEscapePresets] = useState([]); const [escapeInput, setEscapeInput] = useState('')
|
|
3611
4153
|
const [evolutionAdvice, setEvolutionAdvice] = useState(false); const [drafts, setDrafts] = useState([]); const [advice, setAdvice] = useState([]); const [evolutionCandidates, setEvolutionCandidates] = useState([])
|
|
3612
|
-
const [adviceGlobal, setAdviceGlobal] = useState(null); const [costSummary, setCostSummary] = useState({ global: null, byModel: [] }); const [draftPreviews, setDraftPreviews] = useState({})
|
|
3613
|
-
const [
|
|
3614
|
-
const [candidateSettings, setCandidateSettings] = useState({ candidateMode: 'auto', candidateTtlH: 24 }); const [candidateSettingsOpen, setCandidateSettingsOpen] = useState(false)
|
|
4154
|
+
const [adviceGlobal, setAdviceGlobal] = useState(null); const [costSummary, setCostSummary] = useState({ global: null, byModel: [] }); const [draftPreviews, setDraftPreviews] = useState({}); const [candidateOpen, setCandidateOpen] = useState({})
|
|
4155
|
+
const [candidateSelected, setCandidateSelected] = useState({}); const [candidateSafetyOpen, setCandidateSafetyOpen] = useState({}); const [candidateSettings, setCandidateSettings] = useState({ candidateMode: 'auto', candidateTtlH: 24 }); const [candidateSettingsOpen, setCandidateSettingsOpen] = useState(false)
|
|
3615
4156
|
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 [
|
|
3618
|
-
const
|
|
4157
|
+
const [activeTab, setActiveTab] = useState('profiles'); const [highlightId, setHighlightId] = useState(null); const [adviceOpen, setAdviceOpen] = useState({}); const [auditDetailOpen, setAuditDetailOpen] = useState(false); const [auditReminders, setAuditReminders] = useState([])
|
|
4158
|
+
const [evolutionStatus, setEvolutionStatus] = useState({ applyEnabled: false, canaryEnabled: false, reasons: [] }); const [evolutionAssets, setEvolutionAssets] = useState([])
|
|
4159
|
+
const [applyModal, setApplyModal] = useState({ profileKey: '', candidateId: '', open: false, error: '' }); const [rollbackModal, setRollbackModal] = useState({ assetId: '', open: false, error: '' }); const snapshotCtl = useSnapshotData(useState, useEffect)
|
|
4160
|
+
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, evolutionStatus, evolutionAssets, applyModal, rollbackModal, snapshots: snapshotCtl.snapshots, snapshotView: snapshotCtl.snapshotView, maintenanceNotice: snapshotCtl.maintenanceNotice, refreshSnapshots: snapshotCtl.refreshSnapshots, setSnapshotView: snapshotCtl.setSnapshotView, setMaintenanceNotice: snapshotCtl.setMaintenanceNotice, 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, setEvolutionStatus, setEvolutionAssets, setApplyModal, setRollbackModal }
|
|
3619
4161
|
const refresh = useCallback(() => {
|
|
3620
4162
|
api('/list')
|
|
3621
4163
|
.then((data) => {
|
|
@@ -3625,11 +4167,12 @@ function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, Re
|
|
|
3625
4167
|
setAdviceGlobal(data.adviceGlobal !== null && typeof data.adviceGlobal === 'object' ? data.adviceGlobal : null)
|
|
3626
4168
|
setAdviceDetails(data.adviceDetails !== null && typeof data.adviceDetails === 'object' ? data.adviceDetails : {})
|
|
3627
4169
|
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: [] })
|
|
4170
|
+
const status = data.evolutionStatus !== null && typeof data.evolutionStatus === 'object' ? data.evolutionStatus : {}
|
|
4171
|
+
setEvolutionStatus({ applyEnabled: status.applyEnabled === true, canaryEnabled: status.canaryEnabled === true, reasons: Array.isArray(status.reasons) ? status.reasons : [] })
|
|
3628
4172
|
})
|
|
3629
4173
|
.catch((err) => setError(String((err && err.message) || err)))
|
|
3630
|
-
api('/
|
|
3631
|
-
|
|
3632
|
-
.catch(() => {})
|
|
4174
|
+
api('/evolution/assets').then((data) => setEvolutionAssets(Array.isArray(data.assets) ? data.assets : [])).catch(() => {})
|
|
4175
|
+
api('/drafts').then((data) => setDrafts(Array.isArray(data.drafts) ? data.drafts : [])).catch(() => {})
|
|
3633
4176
|
api('/reminders').then((data) => setAuditReminders(Array.isArray(data.reminders) ? data.reminders : [])).catch(() => {})
|
|
3634
4177
|
}, [])
|
|
3635
4178
|
const loadOptions = useCallback(() => {
|
|
@@ -3639,15 +4182,11 @@ function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, Re
|
|
|
3639
4182
|
}, [])
|
|
3640
4183
|
const loadEfforts = makeEffortsLoader(api, setOptions); const loadTools = makeToolsLoader(api, setTools)
|
|
3641
4184
|
const loadVersions = useCallback(() => makeVersionsLoader(api, setVersionWarnings, setPluginVersion)(), []) // 只挂载时拉一次:稳定引用防 effect 依赖恒变
|
|
3642
|
-
useEffect(() => {
|
|
3643
|
-
refresh()
|
|
3644
|
-
loadOptions()
|
|
3645
|
-
loadVersions()
|
|
3646
|
-
}, [refresh, loadOptions, loadVersions])
|
|
4185
|
+
useEffect(() => { refresh(); loadOptions(); loadVersions() }, [refresh, loadOptions, loadVersions])
|
|
3647
4186
|
const applyWrite = makeApplyWrite(s); const actions = makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools)
|
|
3648
|
-
attachDraftActions(api, s, refresh, applyWrite, actions)
|
|
4187
|
+
attachDraftActions(api, s, refresh, applyWrite, actions); attachMaintenanceActions(api, s, refresh, actions)
|
|
3649
4188
|
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),
|
|
4189
|
+
return buildSectionReturn(el, s, actions, rows, buildAddCard(el, s, actions, toolSection), ReminderCenter, sessionsSnapshot(getSessions))
|
|
3651
4190
|
}
|
|
3652
4191
|
return ProfilesSection
|
|
3653
4192
|
}
|
|
@@ -3961,7 +4500,7 @@ function buildJsonBody(el, kv) {
|
|
|
3961
4500
|
|
|
3962
4501
|
// ── 三层信息架构(结论层 / 证据层)纯渲染 ─────────────────────────
|
|
3963
4502
|
|
|
3964
|
-
//
|
|
4503
|
+
// 停滞检测上限(待实测:measured-params 回填惯例,展示层同步标注「待实测」)。
|
|
3965
4504
|
const STALL_THRESHOLD_MS = 30000
|
|
3966
4505
|
|
|
3967
4506
|
// 折叠态状态 chip:拒绝(红)/执行中(琥珀脉冲)/异常(琥珀)/成功(绿)。
|
|
@@ -4631,6 +5170,7 @@ function buildLedgerRowHead(el, record, profileNames, proc, open, onToggle) {
|
|
|
4631
5170
|
const costText = rejected ? '—' : (!settled ? ZH.ledgerCostRunning : (costInfo !== null ? '≈' + costInfo.actual + ' 元' : ''))
|
|
4632
5171
|
const profileId = recordProfile(record)
|
|
4633
5172
|
const profileText = profileId !== '' ? profileDisplay(profileId, profileNames) : ZH.inlineProfile
|
|
5173
|
+
const imageText = imageTextOfBlock({ imageText: record.imageText }, record.trace !== null && typeof record.trace === 'object' ? record.trace.effective : null)
|
|
4634
5174
|
const toolsText = ledgerToolsCountOf(record, proc)
|
|
4635
5175
|
const interveneText = proc !== null && proc.interventions > 0 ? ZH.ledgerInterventionBadge.replace('{n}', String(proc.interventions)) : ''
|
|
4636
5176
|
return el('button', {
|
|
@@ -4641,6 +5181,9 @@ function buildLedgerRowHead(el, record, profileNames, proc, open, onToggle) {
|
|
|
4641
5181
|
},
|
|
4642
5182
|
el('span', { className: 'sap-ledgerTime' }, formatClockTime(record.time)),
|
|
4643
5183
|
el('span', { className: 'sap-ledgerMeta' }, profileText),
|
|
5184
|
+
imageText !== ''
|
|
5185
|
+
? el('span', { className: 'sap-ledgerMeta sap-ledgerImageText' }, imageText)
|
|
5186
|
+
: null,
|
|
4644
5187
|
el('span', { className: 'sap-ledgerStatus ' + status.cls }, status.label),
|
|
4645
5188
|
costText !== '' ? el('span', { className: 'sap-ledgerCostText' }, costText) : null,
|
|
4646
5189
|
toolsText !== '' ? el('span', { className: 'sap-ledgerToolsText' }, toolsText) : null,
|