dsh-subagent-profile 0.4.0 → 0.5.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 +12 -8
- package/README.zh.md +12 -8
- package/index.mjs +90 -95
- package/lib/client.js +372 -51
- package/lib/core/decision-trace.mjs +20 -1
- package/lib/core/dispatch-gates.mjs +122 -9
- package/lib/core/dispatch-schema.mjs +45 -7
- package/lib/core/dispatch-tool.mjs +24 -25
- package/lib/core/evolution-advice.mjs +31 -10
- package/lib/core/evolution-canary.mjs +427 -0
- package/lib/core/evolution-draft.mjs +21 -0
- package/lib/core/evolution-engine.mjs +175 -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 +134 -9
- package/lib/core/http-routes.mjs +16 -5
- package/lib/core/model-policy.mjs +77 -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 +33 -3
- package/lib/core/pure.mjs +1 -1
- 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
|
@@ -133,6 +133,25 @@ const CSS = [
|
|
|
133
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
134
|
'.sap-adviceFootNotes{flex-direction:column;gap:2px;display:flex;min-width:0}',
|
|
135
135
|
'.sap-adviceFootNote{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0}',
|
|
136
|
+
'.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}',
|
|
137
|
+
'.sap-adviceStatusLineWarn{color:var(--dsw-alias-state-error-primary);font-weight:600}',
|
|
138
|
+
'.sap-adviceStatusDim{color:var(--dsw-alias-label-tertiary)}',
|
|
139
|
+
'.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}',
|
|
140
|
+
'.sap-adviceTimelineLine{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}',
|
|
141
|
+
'.sap-adviceTimelineStep{color:var(--dsw-alias-label-secondary)}',
|
|
142
|
+
'.sap-adviceTimelineStepDim{color:var(--dsw-alias-label-dimmed)}',
|
|
143
|
+
'.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}',
|
|
144
|
+
'.sap-rollbackBoxLine{color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:18px;margin:0}',
|
|
145
|
+
'.sap-rollbackBoxKeep{color:var(--dsw-alias-label-tertiary);font-size:12.5px;line-height:18px;margin:0}',
|
|
146
|
+
'.sap-rollbackBoxActions{display:flex;gap:8px;margin-top:6px}',
|
|
147
|
+
'.sap-rollbackDanger{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:0 0}',
|
|
148
|
+
'.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}',
|
|
149
|
+
'.sap-applyModalHead{align-items:baseline;gap:8px;display:flex}',
|
|
150
|
+
'.sap-applyModalTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}',
|
|
151
|
+
'.sap-applyModalLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;margin:0}',
|
|
152
|
+
'.sap-applyModalLine{color:var(--dsw-alias-label-primary);font-size:12.5px;line-height:18px;margin:0}',
|
|
153
|
+
'.sap-applyModalLineDim{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}',
|
|
154
|
+
'.sap-applyModalActions{justify-content:flex-end;gap:8px;display:flex}',
|
|
136
155
|
'.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
156
|
'.sap-candidateSettingsRow b{font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
138
157
|
'.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}',
|
|
@@ -514,7 +533,7 @@ const ZH = {
|
|
|
514
533
|
ledgerCostCardScope: '本会话口径',
|
|
515
534
|
ledgerCostCardCounter: '若继承父会话',
|
|
516
535
|
ledgerCostCardSave: '省',
|
|
517
|
-
ledgerEvidenceToggle: '查看完整因果链
|
|
536
|
+
ledgerEvidenceToggle: '查看完整因果链 ▸',
|
|
518
537
|
ledgerEvidenceClose: '收起因果链 ▴',
|
|
519
538
|
ledgerVsTitle: '请求 vs 生效',
|
|
520
539
|
ledgerVsDiff: '{n} 项与请求不同(琥珀底行)',
|
|
@@ -548,14 +567,14 @@ const ZH = {
|
|
|
548
567
|
ledgerExecLiveNote: '后台执行中:过程事件随活动更新',
|
|
549
568
|
ledgerEventStart: '开始执行',
|
|
550
569
|
ledgerEventCall: '第 {n} 次工具调用',
|
|
551
|
-
ledgerEventStall: '⚠ 停滞 {s}s
|
|
570
|
+
ledgerEventStall: '⚠ 停滞 {s}s 无活动(超过 {t}s 上限)',
|
|
552
571
|
ledgerEventStalling: '⚠ 停滞中(已 {s}s 无活动)',
|
|
553
572
|
ledgerEventIntervene: '主 agent 干预',
|
|
554
573
|
ledgerEventResume: '恢复执行',
|
|
555
574
|
ledgerEventDone: '完成',
|
|
556
575
|
ledgerEventRunning: '执行中…',
|
|
557
576
|
ledgerEventMoreCalls: '…其间还有 {n} 次调用',
|
|
558
|
-
ledgerStallThresholdNote: '
|
|
577
|
+
ledgerStallThresholdNote: '停滞上限 30s(待实测)',
|
|
559
578
|
ledgerSettleOutput: '输出摘要',
|
|
560
579
|
ledgerSettleCostBreakdown: '成本分解',
|
|
561
580
|
ledgerSettleInheritNote: '未指定模型,按继承父会话估算',
|
|
@@ -592,9 +611,9 @@ const ZH = {
|
|
|
592
611
|
adviceSectionDetail: '派发明细',
|
|
593
612
|
adviceSwitchOn: '开(只提示,不自动改配置)',
|
|
594
613
|
adviceSwitchOff: '关(只提示,不自动改配置)',
|
|
595
|
-
advicePanelEmpty: '
|
|
614
|
+
advicePanelEmpty: '暂无建议(近期派发表现不佳时才会生成)',
|
|
596
615
|
advicePanelGlobal: '全局',
|
|
597
|
-
advicePanelReadonly: '
|
|
616
|
+
advicePanelReadonly: '(仅提示:统计与建议不改变派发行为)',
|
|
598
617
|
adviceDirUp: '可上调预算',
|
|
599
618
|
adviceDirDown: '建议降级',
|
|
600
619
|
adviceDirFlat: '维持现状',
|
|
@@ -633,8 +652,8 @@ const ZH = {
|
|
|
633
652
|
candidateTitle: '候选方案',
|
|
634
653
|
candidateNotApplied: '(未应用)',
|
|
635
654
|
candidateCountSuffix: ' 个 · 单选',
|
|
636
|
-
candidateCountNote: ' ·
|
|
637
|
-
candidateIntro: '
|
|
655
|
+
candidateCountNote: ' · 应用前需你确认',
|
|
656
|
+
candidateIntro: '系统基于近期表现生成的改进方案;应用前需要你确认,并进入观察期。',
|
|
638
657
|
candidateRegenerate: '重新生成候选',
|
|
639
658
|
candidateSafetyPending: '安全检查:正在核对…',
|
|
640
659
|
candidateGroupChanges: '改动',
|
|
@@ -643,6 +662,40 @@ const ZH = {
|
|
|
643
662
|
candidateSafetyPass: '全部通过 ✓',
|
|
644
663
|
candidateSafetyFail: '存在拒绝项',
|
|
645
664
|
candidateNotApplicable: '应用功能在后续版本开放(届时会先经你确认)',
|
|
665
|
+
// 观察期/撤销建议/确认流(用户可见面零内部词:观察中/表现变差/撤销/调整前)
|
|
666
|
+
adviceStatusObservingSuffix: '——表现下降会提醒你撤销',
|
|
667
|
+
adviceApplyConfirm: '确认调整方案',
|
|
668
|
+
adviceApplyCancel: '取消',
|
|
669
|
+
adviceApplyModalSummary: '候选摘要',
|
|
670
|
+
adviceApplyModalImpact: '成本与能力影响',
|
|
671
|
+
adviceApplyImpactDown: '能力配置降级,工具面不扩大,成本通常更低',
|
|
672
|
+
adviceApplyImpactUp: '预算上调,成本可能上升',
|
|
673
|
+
adviceApplyImpactFlat: '能力配置不变,成本不受影响',
|
|
674
|
+
adviceApplySafetyNote: '安全检查:',
|
|
675
|
+
adviceApplyDefaultNote: '本建议改的是方案默认值;派发时显式参数仍会覆盖它',
|
|
676
|
+
adviceApplyObserveNote: '应用后进入观察期(5 次派发),表现下降时会提醒你撤销,不会自动修改',
|
|
677
|
+
adviceApplySuccess: '已应用,进入观察期',
|
|
678
|
+
adviceApplyObservingTitle: '观察中,暂不支持再次调整',
|
|
679
|
+
adviceApplySelectTitle: '请先选择一个候选方案',
|
|
680
|
+
adviceRollbackBoxAdvice: '建议撤销这次调整,恢复调整前的配置。',
|
|
681
|
+
adviceRollbackRestorePrefix: '恢复为:',
|
|
682
|
+
adviceRollbackEvidence: '对比:调整前表现分 {before} · 调整后 {after}(近 {n} 次派发)',
|
|
683
|
+
adviceRollbackKeepDone: '已选择:继续使用当前配置(已留痕)',
|
|
684
|
+
adviceRollbackBtn: '撤销上次调整',
|
|
685
|
+
adviceRollbackKeepBtn: '继续用当前方案',
|
|
686
|
+
adviceRollbackSuccess: '已恢复调整前配置,观察期终止',
|
|
687
|
+
adviceKeepSuccess: '已选择保留当前配置(已留痕)',
|
|
688
|
+
adviceRollbackModalTitle: '撤销这次调整?',
|
|
689
|
+
adviceRollbackModalRestore: '恢复为',
|
|
690
|
+
adviceRollbackModalChange: '会改变',
|
|
691
|
+
adviceRollbackModalNote: '若方案已被手动修改,将无法一键撤销;本次撤销会记入审计',
|
|
692
|
+
adviceRollbackConfirm: '确认撤销',
|
|
693
|
+
adviceRollbackChangeSuffix: '调整前自定义项一并恢复',
|
|
694
|
+
adviceTimelineTitle: '进化轨迹',
|
|
695
|
+
adviceTimelinePending: '待你处理',
|
|
696
|
+
adviceTimelineSep: ' → ',
|
|
697
|
+
adviceRestoreFallback: '调整前的完整配置',
|
|
698
|
+
adviceReminderView: '查看建议',
|
|
646
699
|
candidateValidityForever: '⏱ 无期限',
|
|
647
700
|
candidateValidityPrefix: '⏱ 至 ',
|
|
648
701
|
candidateGeneratedPrefix: '生成于 ',
|
|
@@ -650,14 +703,14 @@ const ZH = {
|
|
|
650
703
|
candidateSamplesSuffix: ' 次派发',
|
|
651
704
|
candidatePerfPrefix: '近期表现 ',
|
|
652
705
|
candidatePerfSuffix: '(满分 100)',
|
|
653
|
-
candidateNoSample: '
|
|
706
|
+
candidateNoSample: '暂无近期派发样本',
|
|
654
707
|
// 候选卡底部:无对应方案存来源配置为新方案;有对应方案调整按钮灰置 + 定位。
|
|
655
708
|
adviceSaveProfile: '保存为方案',
|
|
656
709
|
adviceApplyDisabled: '按建议调整方案',
|
|
657
710
|
adviceSavedNotice: '已保存为新方案:{id}',
|
|
658
711
|
adviceFootNoteSave: '仅保存预设、模型与提供方;自定义人格与工具过滤无法还原(隐私保护不落盘),如需完整配置请手动编辑方案。',
|
|
659
712
|
adviceFootNoteMatchedPrefix: '对应方案:',
|
|
660
|
-
// 建议摘要行(v8
|
|
713
|
+
// 建议摘要行(v8:对象/建议/置信度;展开后见理由与明细)
|
|
661
714
|
adviceNoProfileTag: '无方案',
|
|
662
715
|
// 安全页签:安全保证卡 + 审计健康卡(只读明细表)
|
|
663
716
|
safetyCardTitle: '安全保证',
|
|
@@ -1410,7 +1463,7 @@ function buildSubagentBanner(el, subagent, parentPreset, modelState, bannerMode)
|
|
|
1410
1463
|
// (标记已读/采纳/忽略/拒绝/关闭)经 POST 回写 host 并刷新——一条提醒 = 一条审计记录。
|
|
1411
1464
|
// sessions 服务供条目主/子会话标题查询与跳转;跳转失败静默(设置页不被导航异常打断)。
|
|
1412
1465
|
function makeReminderCenter(el, useState, useEffect, openChild, getSessions) {
|
|
1413
|
-
function ReminderCenter() {
|
|
1466
|
+
function ReminderCenter(props) {
|
|
1414
1467
|
const [reminders, setReminders] = useState([])
|
|
1415
1468
|
const [error, setError] = useState('')
|
|
1416
1469
|
const refresh = () => {
|
|
@@ -1422,7 +1475,8 @@ function makeReminderCenter(el, useState, useEffect, openChild, getSessions) {
|
|
|
1422
1475
|
const request = action === 'ack' ? api('/reminders/ack', { ids: [id] }) : api('/reminders/action', { id, action })
|
|
1423
1476
|
request.then(refresh).catch((err) => setError(String((err && err.message) || err)))
|
|
1424
1477
|
}
|
|
1425
|
-
|
|
1478
|
+
const jumpToAdvice = props !== null && typeof props === 'object' && typeof props.jumpToAdvice === 'function' ? props.jumpToAdvice : null
|
|
1479
|
+
return buildReminderCenter(el, reminders, error, act, getSessions(), openChild, jumpToAdvice)
|
|
1426
1480
|
}
|
|
1427
1481
|
return ReminderCenter
|
|
1428
1482
|
}
|
|
@@ -1431,17 +1485,17 @@ function makeReminderCenter(el, useState, useEffect, openChild, getSessions) {
|
|
|
1431
1485
|
// 可展开区(原生 details,展开后仍按现有行渲染并保留已处理标注,可追溯)。审计留痕
|
|
1432
1486
|
// 由 host 侧动作回写,已处理条目不再堆在默认列表里。全空才显示空态文案;仅剩已处理时
|
|
1433
1487
|
// 只显示折叠区,不显示空态。
|
|
1434
|
-
function buildReminderCenter(el, reminders, error, act, sessions, openChild) {
|
|
1488
|
+
function buildReminderCenter(el, reminders, error, act, sessions, openChild, jumpToAdvice) {
|
|
1435
1489
|
const handled = reminders.filter((r) => typeof r.action === 'string' && r.action !== '')
|
|
1436
1490
|
const pending = reminders.filter((r) => !(typeof r.action === 'string' && r.action !== ''))
|
|
1437
1491
|
const pendingList = pending.length > 0
|
|
1438
|
-
? el('ul', { className: 'sap-rows' }, pending.map((r) => buildReminderRow(el, r, act, sessions, openChild)))
|
|
1492
|
+
? el('ul', { className: 'sap-rows' }, pending.map((r) => buildReminderRow(el, r, act, sessions, openChild, jumpToAdvice)))
|
|
1439
1493
|
: null
|
|
1440
1494
|
const handledList = handled.length > 0
|
|
1441
1495
|
? el('details', { className: 'sap-customized sap-customizedArrowEnd sap-reminderHandled' },
|
|
1442
1496
|
el('summary', { className: 'sap-customizedSummary' }, '已处理(' + handled.length + ')', el('span', { className: 'sap-customizedEndArrow' }, '▸')),
|
|
1443
1497
|
el('div', { className: 'sap-customizedBody' },
|
|
1444
|
-
el('ul', { className: 'sap-rows' }, handled.map((r) => buildReminderRow(el, r, act, sessions, openChild)))
|
|
1498
|
+
el('ul', { className: 'sap-rows' }, handled.map((r) => buildReminderRow(el, r, act, sessions, openChild, jumpToAdvice)))
|
|
1445
1499
|
)
|
|
1446
1500
|
)
|
|
1447
1501
|
: null
|
|
@@ -1575,10 +1629,38 @@ function buildUnadoptedReminderRow(el, r, act, sessions, openChild) {
|
|
|
1575
1629
|
)
|
|
1576
1630
|
}
|
|
1577
1631
|
|
|
1632
|
+
// 回滚建议提醒行:标题/说明只读 + 「查看建议」跳转到自进化与建议页签(只读跳转,
|
|
1633
|
+
// 无采纳/忽略按钮);与通知类提醒同款「标记已读/关闭」。
|
|
1634
|
+
function buildRollbackReminderRow(el, r, act, jumpToAdvice) {
|
|
1635
|
+
const severityCls = r.severity === 'P0' ? 'sap-reminderP0' : 'sap-reminderP1'
|
|
1636
|
+
const severityText = r.severity === 'P0' ? '紧急' : '注意'
|
|
1637
|
+
const jump = () => { if (typeof jumpToAdvice === 'function') { try { jumpToAdvice() } catch { /* 页签不可用:静默 */ } } }
|
|
1638
|
+
return el('li', { key: String(r.id), className: 'sap-rowCard' + (r.unread === true ? ' sap-reminderUnread' : '') },
|
|
1639
|
+
el('div', { className: 'sap-reminderHead' },
|
|
1640
|
+
el('span', { className: 'sap-chip ' + severityCls }, severityText),
|
|
1641
|
+
el('span', { className: 'sap-reminderTitle', title: r.title }, r.title)
|
|
1642
|
+
),
|
|
1643
|
+
el('p', { className: 'sap-desc' }, r.detail),
|
|
1644
|
+
typeof r.action === 'string' && r.action !== ''
|
|
1645
|
+
? null
|
|
1646
|
+
: el('div', { className: 'sap-reminderActions' }, [
|
|
1647
|
+
typeof jumpToAdvice === 'function' ? el('button', { type: 'button', className: 'sap-detailToggle', onClick: jump }, ZH.adviceReminderView) : null,
|
|
1648
|
+
r.unread === true ? el('button', { type: 'button', className: 'sap-detailToggle', onClick: act(r.id, 'ack') }, ZH.reminderAck) : null,
|
|
1649
|
+
r.severity !== 'P0' || r.unread !== true
|
|
1650
|
+
? el('button', { type: 'button', className: 'sap-dangerButton', onClick: act(r.id, 'dismiss') }, ZH.reminderDismiss)
|
|
1651
|
+
: null
|
|
1652
|
+
]),
|
|
1653
|
+
typeof r.action === 'string' && r.action !== ''
|
|
1654
|
+
? el('p', { className: 'sap-metaValueDefault' }, ZH.reminderActed.replace('{action}', ZH.reminderActionZh[r.action] || r.action))
|
|
1655
|
+
: null
|
|
1656
|
+
)
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1578
1659
|
// 通知类提醒行:P0 未读只保留「标记已读」(常驻至已读,不可跳过未读);已读后出现
|
|
1579
1660
|
// 「关闭」(与 P1/P2 同);P1/P2 恒保留「标记已读 / 关闭」。已终态条目隐藏动作按钮,
|
|
1580
1661
|
// 只显示已处理灰字标注(折叠区可追溯、防对终态条目误点产生重复审计)。
|
|
1581
|
-
function buildReminderRow(el, r, act, sessions, openChild) {
|
|
1662
|
+
function buildReminderRow(el, r, act, sessions, openChild, jumpToAdvice) {
|
|
1663
|
+
if (r.kind === 'suggestion-rollback') return buildRollbackReminderRow(el, r, act, jumpToAdvice)
|
|
1582
1664
|
if (r.kind === 'adoption-false') return buildUnadoptedReminderRow(el, r, act, sessions, openChild)
|
|
1583
1665
|
const severityCls = r.severity === 'P0' ? 'sap-reminderP0' : 'sap-reminderP1'
|
|
1584
1666
|
const severityText = r.severity === 'P0' ? '紧急' : '注意'
|
|
@@ -2657,6 +2739,45 @@ function makeCandidateSettingsActions(api, s, refresh, applyWrite) {
|
|
|
2657
2739
|
}
|
|
2658
2740
|
}
|
|
2659
2741
|
|
|
2742
|
+
// 应用确认流动作:按建议调整方案(POST /evolution/apply,humanConfirmed 由确认按钮
|
|
2743
|
+
// 触发)、撤销上次调整(action=rollback)、继续用当前方案(action=keep)。成功即刷新
|
|
2744
|
+
// 并置顶部成功提示;失败原因回显在各自弹窗内(不静默)。
|
|
2745
|
+
function makeApplyFlowActions(api, s, refresh) {
|
|
2746
|
+
const closeApply = () => s.setApplyModal((prev) => ({ ...(prev ?? {}), open: false, error: '' }))
|
|
2747
|
+
const closeRollback = () => s.setRollbackModal((prev) => ({ ...(prev ?? {}), open: false, error: '' }))
|
|
2748
|
+
const applyCandidate = () => {
|
|
2749
|
+
const modal = s.applyModal ?? {}
|
|
2750
|
+
if (modal.candidateId === '') return
|
|
2751
|
+
s.setError('')
|
|
2752
|
+
api('/evolution/apply', { assetId: modal.candidateId, human_confirmed: true })
|
|
2753
|
+
.then(() => { closeApply(); s.setSavedNotice(ZH.adviceApplySuccess); refresh() })
|
|
2754
|
+
.catch((err) => s.setApplyModal((prev) => ({ ...(prev ?? {}), error: String((err && err.message) || err) })))
|
|
2755
|
+
}
|
|
2756
|
+
const rollbackAsset = () => {
|
|
2757
|
+
const modal = s.rollbackModal ?? {}
|
|
2758
|
+
if (modal.assetId === '') return
|
|
2759
|
+
s.setError('')
|
|
2760
|
+
api('/evolution/rollback', { assetId: modal.assetId, human_confirmed: true, action: 'rollback' })
|
|
2761
|
+
.then(() => { closeRollback(); s.setSavedNotice(ZH.adviceRollbackSuccess); refresh() })
|
|
2762
|
+
.catch((err) => s.setRollbackModal((prev) => ({ ...(prev ?? {}), error: String((err && err.message) || err) })))
|
|
2763
|
+
}
|
|
2764
|
+
const keepAsset = (assetId) => () => {
|
|
2765
|
+
s.setError('')
|
|
2766
|
+
api('/evolution/rollback', { assetId, human_confirmed: true, action: 'keep' })
|
|
2767
|
+
.then(() => { s.setSavedNotice(ZH.adviceKeepSuccess); refresh() })
|
|
2768
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
2769
|
+
}
|
|
2770
|
+
return {
|
|
2771
|
+
openApplyModal: (profileKey, candidateId) => () => s.setApplyModal({ profileKey, candidateId, open: true, error: '' }),
|
|
2772
|
+
closeApplyModal: closeApply,
|
|
2773
|
+
applyCandidate,
|
|
2774
|
+
openRollbackModal: (assetId) => () => s.setRollbackModal({ assetId, open: true, error: '' }),
|
|
2775
|
+
closeRollbackModal: closeRollback,
|
|
2776
|
+
rollbackAsset,
|
|
2777
|
+
keepAsset,
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2660
2781
|
function makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools) {
|
|
2661
2782
|
return {
|
|
2662
2783
|
...makeFormActions(s, loadEfforts, loadTools),
|
|
@@ -2666,6 +2787,7 @@ function makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools
|
|
|
2666
2787
|
...makeEscapeActions(api, s),
|
|
2667
2788
|
...makeCandidateActions(api, s),
|
|
2668
2789
|
...makeCandidateSettingsActions(api, s, refresh, applyWrite),
|
|
2790
|
+
...makeApplyFlowActions(api, s, refresh),
|
|
2669
2791
|
...makeTabActions(s),
|
|
2670
2792
|
locateProfile: makeLocateProfile(s),
|
|
2671
2793
|
}
|
|
@@ -2798,7 +2920,7 @@ function buildProfileRows(el, chip, s, actions) {
|
|
|
2798
2920
|
? el('p', { className: 'sap-costLine' },
|
|
2799
2921
|
[
|
|
2800
2922
|
typeof profile.avgCost === 'number' ? `平均 ≈${profile.avgCost} 元/次` : '',
|
|
2801
|
-
typeof profile.successRate === 'number' ?
|
|
2923
|
+
typeof profile.successRate === 'number' ? `近期表现 ${Math.round(profile.successRate * 100)}/100${typeof profile.n === 'number' ? `(基于最近 ${profile.n} 次派发)` : ''}` : ''
|
|
2802
2924
|
].filter(Boolean).join(' · '))
|
|
2803
2925
|
: null
|
|
2804
2926
|
)
|
|
@@ -2907,14 +3029,20 @@ function candidateValidityText(expiry) {
|
|
|
2907
3029
|
return ZH.candidateValidityPrefix + formatMonthDayTime(expiry)
|
|
2908
3030
|
}
|
|
2909
3031
|
|
|
2910
|
-
// 指标行:把「平均耗时 <ms>ms」换算成短时(40.4s
|
|
3032
|
+
// 指标行:把「平均耗时 <ms>ms」换算成短时(40.4s 等);第 1 个指标恒为成功分行,
|
|
3033
|
+
// 历史台账里的旧标签「加权成功分」在此显示层映射为「近期表现分」(值保持 0~1
|
|
3034
|
+
// 原样不改口径,0~100 视图在候选生成信息行)。其余指标原样(信息不删)。
|
|
2911
3035
|
function adviceMetricsOf(performanceText) {
|
|
2912
|
-
return adviceEvidenceOf(performanceText).metrics.map((metric) => {
|
|
3036
|
+
return adviceEvidenceOf(performanceText).metrics.map((metric, index) => {
|
|
2913
3037
|
const hit = /^平均耗时 (.+)$/.exec(metric)
|
|
2914
3038
|
if (hit !== null) {
|
|
2915
3039
|
const ms = Number(hit[1])
|
|
2916
3040
|
return Number.isFinite(ms) ? '平均耗时 ' + formatDurationMs(ms) : metric
|
|
2917
3041
|
}
|
|
3042
|
+
if (index === 0) {
|
|
3043
|
+
const space = metric.indexOf(' ')
|
|
3044
|
+
return space > 0 ? '近期表现分 ' + metric.slice(space + 1) : metric
|
|
3045
|
+
}
|
|
2918
3046
|
return metric
|
|
2919
3047
|
})
|
|
2920
3048
|
}
|
|
@@ -2968,7 +3096,7 @@ function dispatchModeZh(mode) {
|
|
|
2968
3096
|
return mode
|
|
2969
3097
|
}
|
|
2970
3098
|
|
|
2971
|
-
//
|
|
3099
|
+
// 表现摘要拆两层:计数(完成/失败/终止)与指标(近期表现分/平均耗时/平均输出),
|
|
2972
3100
|
// 各渲染一遍;解析不出的部分原样保留(信息不删)。
|
|
2973
3101
|
function adviceEvidenceOf(performanceText) {
|
|
2974
3102
|
const text = typeof performanceText === 'string' ? performanceText : ''
|
|
@@ -3058,13 +3186,19 @@ function adviceSectionOf(el, title, children) {
|
|
|
3058
3186
|
}
|
|
3059
3187
|
|
|
3060
3188
|
// 单张建议卡(v10):摘要行 = 对象/建议/置信度/▸(结论三件套,无加权分);
|
|
3061
|
-
// 展开区 =
|
|
3189
|
+
// 展开区 = 观察期状态行 → 小节序列(理由/近期表现/派发明细/候选方案)→ 撤销建议区
|
|
3190
|
+
// (regress)→ 进化轨迹 → 操作区;确认/撤销弹窗就地渲染在卡内。
|
|
3062
3191
|
function buildAdviceCard(el, s, actions, a, candidates, sessions) {
|
|
3063
3192
|
const profile = matchingProfileOf(s.profiles, a.profileKey)
|
|
3064
3193
|
const metrics = adviceMetricsOf(a.performanceText)
|
|
3065
3194
|
const rows = s.adviceDetails && typeof s.adviceDetails === 'object' && Array.isArray(s.adviceDetails[a.profileKey]) ? s.adviceDetails[a.profileKey] : []
|
|
3066
3195
|
const openMap = s.adviceOpen !== null && typeof s.adviceOpen === 'object' ? s.adviceOpen : {}
|
|
3067
3196
|
const open = openMap[a.profileKey] === true
|
|
3197
|
+
const assetView = adviceAssetViewOf(s, a.profileKey)
|
|
3198
|
+
const selectedId = candidateSelectedIdOf(s, a.profileKey, candidates)
|
|
3199
|
+
const selected = candidates.find((c) => c !== null && typeof c === 'object' && c.id === selectedId)
|
|
3200
|
+
const applyModal = s.applyModal ?? {}
|
|
3201
|
+
const rollbackModal = s.rollbackModal ?? {}
|
|
3068
3202
|
const summary = el('button', { type: 'button', className: 'sap-adviceSummary', 'aria-expanded': open, onClick: actions.toggleAdviceOpen(a.profileKey) },
|
|
3069
3203
|
el('span', { className: 'sap-adviceObjLine' },
|
|
3070
3204
|
el('span', { className: 'sap-adviceObj' }, adviceSummaryObjectText(profile, a.profileKey)),
|
|
@@ -3075,11 +3209,16 @@ function buildAdviceCard(el, s, actions, a, candidates, sessions) {
|
|
|
3075
3209
|
el('span', { className: 'sap-adviceConf' }, ZH.adviceConfidence + confidenceZhOf(a.confidence)),
|
|
3076
3210
|
el('span', { className: 'sap-adviceSummaryArrow' }, '▸')))
|
|
3077
3211
|
const detail = open !== true ? null : el('div', { className: 'sap-adviceDetail' },
|
|
3212
|
+
buildAdviceStatusLine(el, assetView),
|
|
3078
3213
|
typeof a.reasonText === 'string' && a.reasonText !== '' ? adviceSectionOf(el, ZH.adviceSectionReason, el('p', { className: 'sap-adviceSectionBody' }, a.reasonText)) : null,
|
|
3079
3214
|
metrics.length > 0 ? adviceSectionOf(el, ZH.adviceSectionPerf, el('p', { className: 'sap-adviceSectionBody' }, metrics.join(' · '))) : null,
|
|
3080
3215
|
adviceSectionOf(el, ZH.adviceSectionDetail, buildAdviceDetails(el, s, actions, a.profileKey, rows, sessions)),
|
|
3081
3216
|
candidates.length > 0 ? buildAdviceCandidates(el, s, actions, a, candidates) : null,
|
|
3082
|
-
|
|
3217
|
+
assetView !== null ? buildRollbackSuggestion(el, s, actions, assetView) : null,
|
|
3218
|
+
buildAdviceTimeline(el, assetView),
|
|
3219
|
+
buildAdviceFoot(el, s, actions, a, profile, candidates, assetView),
|
|
3220
|
+
applyModal.open === true && applyModal.profileKey === a.profileKey && selected !== undefined ? buildApplyModal(el, s, actions, a, profile, selected) : null,
|
|
3221
|
+
rollbackModal.open === true && assetView !== null && rollbackModal.assetId === assetView.id ? buildRollbackModal(el, s, actions, assetView) : null)
|
|
3083
3222
|
return el('li', { key: String(a.profileKey), className: 'sap-adviceItem' + (open === true ? ' sap-adviceItemOpen' : '') }, summary, detail)
|
|
3084
3223
|
}
|
|
3085
3224
|
|
|
@@ -3113,6 +3252,173 @@ function buildAdviceDetails(el, s, actions, profileKey, rows, sessions) {
|
|
|
3113
3252
|
)
|
|
3114
3253
|
}
|
|
3115
3254
|
|
|
3255
|
+
// ── 观察期/撤销建议/进化轨迹(数据源 = /evolution/assets 派生视图)──────────
|
|
3256
|
+
|
|
3257
|
+
// 建议卡对应资产视图:同键资产取最近一条(观察期/已应用优先于纯草稿);无资产 → null。
|
|
3258
|
+
function adviceAssetViewOf(s, profileKey) {
|
|
3259
|
+
const assets = Array.isArray(s.evolutionAssets) ? s.evolutionAssets : []
|
|
3260
|
+
const owned = assets.filter((a) => a && typeof a === 'object' && a.profile_key === profileKey)
|
|
3261
|
+
if (owned.length === 0) return null
|
|
3262
|
+
const rank = (a) => (a && a.canary && typeof a.canary === 'object' && a.canary.state !== 'pendingApply' ? 1 : 0)
|
|
3263
|
+
return owned.sort((a, b) => rank(b) - rank(a) || (Number.isFinite(b.updatedAt) ? b.updatedAt : 0) - (Number.isFinite(a.updatedAt) ? a.updatedAt : 0))[0]
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
// 候选行徽标对应资产(按候选 id = 资产 id)。
|
|
3267
|
+
function candidateAssetByIdOf(s, id) {
|
|
3268
|
+
const assets = Array.isArray(s.evolutionAssets) ? s.evolutionAssets : []
|
|
3269
|
+
return assets.find((a) => a && a.id === id) ?? null
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// 观察期状态行:regress 警示强调(结论先行);其余次级色;pendingApply 不显示。
|
|
3273
|
+
function buildAdviceStatusLine(el, view) {
|
|
3274
|
+
const canary = view !== null && typeof view === 'object' ? view.canary : null
|
|
3275
|
+
if (canary === null || typeof canary !== 'object' || canary.state === 'pendingApply') return null
|
|
3276
|
+
const warn = canary.state === 'regress'
|
|
3277
|
+
const suffix = canary.state === 'observing' ? ZH.adviceStatusObservingSuffix : ''
|
|
3278
|
+
return el('div', { className: 'sap-adviceStatusLine' + (warn ? ' sap-adviceStatusLineWarn' : '') },
|
|
3279
|
+
el('span', null, String(canary.label ?? '') + suffix))
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// 恢复目标名:fromSnapshot 方案名列表;空快照回退通用文案(回滚 = 移除应用方案)。
|
|
3283
|
+
function rollbackRestoreNameOf(asset) {
|
|
3284
|
+
const snapshot = asset !== null && typeof asset === 'object' ? asset.fromSnapshot : null
|
|
3285
|
+
const profiles = snapshot !== null && typeof snapshot === 'object' && Array.isArray(snapshot.profiles) ? snapshot.profiles : []
|
|
3286
|
+
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 !== '')
|
|
3287
|
+
return names.length > 0 ? names.join('、') : ZH.adviceRestoreFallback
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
// 表现分 → 百分制整数(证据对照行口径,与「满分 100」一致)。
|
|
3291
|
+
function adviceScore100(score) {
|
|
3292
|
+
return Number.isFinite(score) ? Math.round(score * 100) : '—'
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
function rollbackEvidenceTextOf(asset) {
|
|
3296
|
+
const canary = asset !== null && typeof asset === 'object' && asset.canary !== null && typeof asset.canary === 'object' ? asset.canary : {}
|
|
3297
|
+
const post = canary.post !== null && typeof canary.post === 'object' ? canary.post : null
|
|
3298
|
+
const base = canary.baseline !== null && typeof canary.baseline === 'object' ? canary.baseline : null
|
|
3299
|
+
const n = post !== null && Number.isFinite(post.sampleN) ? post.sampleN : 0
|
|
3300
|
+
return ZH.adviceRollbackEvidence.replace('{before}', adviceScore100(base === null ? null : base.weighted_success)).replace('{after}', adviceScore100(post === null ? null : post.weighted_success)).replace('{n}', n)
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
function tierZhOf(tier) {
|
|
3304
|
+
return tier === 'cheap' ? '低' : tier === 'premium' ? '高' : '中'
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
// 撤销确认弹窗「会改变」行:模型/预算档位/推理强度变化 + 自定义项一并恢复。
|
|
3308
|
+
function rollbackChangeTextOf(asset) {
|
|
3309
|
+
const parts = []
|
|
3310
|
+
const applied = asset !== null && typeof asset === 'object' && asset.applied_config !== null && typeof asset.applied_config === 'object' ? asset.applied_config : {}
|
|
3311
|
+
const snapshot = asset !== null && typeof asset === 'object' && asset.fromSnapshot !== null && typeof asset.fromSnapshot === 'object' && Array.isArray(asset.fromSnapshot.profiles) ? asset.fromSnapshot.profiles : []
|
|
3312
|
+
const before = snapshot.length > 0 && snapshot[0].config !== null && typeof snapshot[0].config === 'object' ? snapshot[0].config : {}
|
|
3313
|
+
if (before.model !== undefined || applied.model !== undefined) parts.push('模型 ' + (before.model || '未指定') + ' → ' + (applied.model || '未指定'))
|
|
3314
|
+
if (before.tokenTier !== undefined && applied.tokenTier !== undefined && before.tokenTier !== applied.tokenTier) {
|
|
3315
|
+
parts.push('预算档位 ' + tierZhOf(before.tokenTier) + ' → ' + tierZhOf(applied.tokenTier) + ',成本可能回升')
|
|
3316
|
+
} else if (before.reasoningEffort !== undefined && applied.reasoningEffort !== undefined && before.reasoningEffort !== applied.reasoningEffort) {
|
|
3317
|
+
parts.push('推理强度已上调,成本可能回升')
|
|
3318
|
+
}
|
|
3319
|
+
parts.push(ZH.adviceRollbackChangeSuffix)
|
|
3320
|
+
return parts.join(';')
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
// 撤销建议区(regress):结论先行在状态行,本区 = 建议/恢复为/证据 + 双按钮;
|
|
3324
|
+
// 用户已选择保留 → 只读「已选择」行;无表态前常驻只读(不自动执行任何动作)。
|
|
3325
|
+
function buildRollbackSuggestion(el, s, actions, asset) {
|
|
3326
|
+
const canary = asset !== null && typeof asset === 'object' && asset.canary !== null && typeof asset.canary === 'object' ? asset.canary : {}
|
|
3327
|
+
if (canary.state !== 'regress') return null
|
|
3328
|
+
if (canary.kept === true) {
|
|
3329
|
+
return el('div', { className: 'sap-rollbackBox' }, el('p', { className: 'sap-rollbackBoxKeep' }, ZH.adviceRollbackKeepDone))
|
|
3330
|
+
}
|
|
3331
|
+
return el('div', { className: 'sap-rollbackBox' },
|
|
3332
|
+
el('p', { className: 'sap-applyModalLine' }, ZH.adviceRollbackBoxAdvice),
|
|
3333
|
+
el('p', { className: 'sap-rollbackBoxLine' }, ZH.adviceRollbackRestorePrefix, el('b', null, rollbackRestoreNameOf(asset))),
|
|
3334
|
+
el('p', { className: 'sap-rollbackBoxLine' }, rollbackEvidenceTextOf(asset)),
|
|
3335
|
+
el('div', { className: 'sap-rollbackBoxActions' },
|
|
3336
|
+
el('button', { type: 'button', className: 'sap-secondaryButton sap-rollbackDanger', style: smallBtn, onClick: actions.openRollbackModal(asset.id) }, ZH.adviceRollbackBtn),
|
|
3337
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.keepAsset(asset.id) }, ZH.adviceRollbackKeepBtn)))
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
// 成本与能力影响行(确认弹窗):降级/上调方向 + 预算档位变化。
|
|
3341
|
+
function applyImpactTextOf(profile, candidate, suggestion) {
|
|
3342
|
+
const cfg = candidate !== null && typeof candidate === 'object' && candidate.config !== null && typeof candidate.config === 'object' ? candidate.config : {}
|
|
3343
|
+
const parts = [suggestion === '降' ? ZH.adviceApplyImpactDown : suggestion === '升' ? ZH.adviceApplyImpactUp : ZH.adviceApplyImpactFlat]
|
|
3344
|
+
if (profile !== null && cfg.tokenTier !== undefined && profile.tokenTier !== undefined && cfg.tokenTier !== profile.tokenTier) {
|
|
3345
|
+
parts.push('预算档位 ' + tierZhOf(profile.tokenTier) + ' → ' + tierZhOf(cfg.tokenTier))
|
|
3346
|
+
}
|
|
3347
|
+
return parts.join(';')
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
// 应用确认弹窗:候选摘要(名称/方向/安全检查)/成本与能力影响/两条说明行;
|
|
3351
|
+
// 主按钮「确认调整方案」即执行(humanConfirmed 由点击触发,无强制勾选),失败红字回显。
|
|
3352
|
+
function buildApplyModal(el, s, actions, a, profile, candidate) {
|
|
3353
|
+
const modal = s.applyModal ?? {}
|
|
3354
|
+
const preview = s.draftPreviews !== null && typeof s.draftPreviews === 'object' ? s.draftPreviews[candidate.id] : undefined
|
|
3355
|
+
const checks = preview !== undefined && preview.checks !== null && typeof preview.checks === 'object' && Array.isArray(preview.checks) ? preview.checks : []
|
|
3356
|
+
const safety = preview === undefined ? ZH.candidateSafetyPending
|
|
3357
|
+
: preview.ok === true ? ZH.adviceApplySafetyNote + ZH.candidateSafetyPass.replace(' ✓', '') + ' ' + checks.length + '/' + checks.length
|
|
3358
|
+
: ZH.adviceApplySafetyNote + ZH.candidateSafetyFail
|
|
3359
|
+
return el('div', { className: 'sap-applyModal' },
|
|
3360
|
+
el('div', { className: 'sap-applyModalHead' }, el('span', { className: 'sap-applyModalTitle' }, ZH.adviceApplyConfirm)),
|
|
3361
|
+
el('div', null,
|
|
3362
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceApplyModalSummary),
|
|
3363
|
+
el('p', { className: 'sap-applyModalLine' }, candidate.name + ' · ' + directionZh(a.suggestion)),
|
|
3364
|
+
el('p', { className: 'sap-applyModalLineDim' }, safety)),
|
|
3365
|
+
el('div', null,
|
|
3366
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceApplyModalImpact),
|
|
3367
|
+
el('p', { className: 'sap-applyModalLine' }, applyImpactTextOf(profile, candidate, a.suggestion))),
|
|
3368
|
+
el('p', { className: 'sap-applyModalLineDim' }, ZH.adviceApplyDefaultNote),
|
|
3369
|
+
el('p', { className: 'sap-applyModalLineDim' }, ZH.adviceApplyObserveNote),
|
|
3370
|
+
modal.error !== '' ? el('p', { className: 'sap-notice' }, modal.error) : null,
|
|
3371
|
+
el('div', { className: 'sap-applyModalActions' },
|
|
3372
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', onClick: actions.closeApplyModal }, ZH.adviceApplyCancel),
|
|
3373
|
+
el('button', { type: 'button', className: 'sap-primaryButton', onClick: actions.applyCandidate }, ZH.adviceApplyConfirm)))
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
// 撤销确认弹窗:恢复为/会改变/说明 + 确认撤销(朱砂描边)/取消;失败红字回显。
|
|
3377
|
+
function buildRollbackModal(el, s, actions, asset) {
|
|
3378
|
+
const modal = s.rollbackModal ?? {}
|
|
3379
|
+
return el('div', { className: 'sap-applyModal' },
|
|
3380
|
+
el('div', { className: 'sap-applyModalHead' }, el('span', { className: 'sap-applyModalTitle' }, ZH.adviceRollbackModalTitle)),
|
|
3381
|
+
el('div', null,
|
|
3382
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceRollbackModalRestore),
|
|
3383
|
+
el('p', { className: 'sap-applyModalLine' }, rollbackRestoreNameOf(asset))),
|
|
3384
|
+
el('div', null,
|
|
3385
|
+
el('p', { className: 'sap-applyModalLabel' }, ZH.adviceRollbackModalChange),
|
|
3386
|
+
el('p', { className: 'sap-applyModalLine' }, rollbackChangeTextOf(asset))),
|
|
3387
|
+
el('p', { className: 'sap-applyModalLineDim' }, ZH.adviceRollbackModalNote),
|
|
3388
|
+
modal.error !== '' ? el('p', { className: 'sap-notice' }, modal.error) : null,
|
|
3389
|
+
el('div', { className: 'sap-applyModalActions' },
|
|
3390
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', onClick: actions.closeRollbackModal }, ZH.adviceApplyCancel),
|
|
3391
|
+
el('button', { type: 'button', className: 'sap-secondaryButton sap-rollbackDanger', onClick: actions.rollbackAsset }, ZH.adviceRollbackConfirm)))
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
// 轨迹单事件文案:生成(MM-DD)/应用(MM-DD · 人工确认)/观察(k/kTotal)/
|
|
3395
|
+
// 判定:…/撤销·保留:…(未发生步骤灰字由 buildAdviceTimeline 补)。
|
|
3396
|
+
function timelineEventTextOf(event) {
|
|
3397
|
+
if (event.event === 'generated') return '生成(' + formatMonthDayTime(event.ts) + ')'
|
|
3398
|
+
if (event.event === 'applied') return '应用(' + formatMonthDayTime(event.ts) + (typeof event.meta === 'string' && event.meta !== '' ? ' · ' + event.meta : '') + ')'
|
|
3399
|
+
if (event.event === 'observed') return '观察(' + (typeof event.meta === 'string' ? event.meta : '') + ')'
|
|
3400
|
+
if (event.event === 'judged') return '判定:' + (typeof event.meta === 'string' && event.meta !== '' ? event.meta : ZH.adviceTimelinePending)
|
|
3401
|
+
if (event.event === 'resolved') return '撤销/保留:' + (typeof event.meta === 'string' && event.meta !== '' ? event.meta : ZH.adviceTimelinePending)
|
|
3402
|
+
return event.event
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
// 进化轨迹小节:assets lifecycle 行式时间线;未应用(无 applied 事件)或缺生命周期
|
|
3406
|
+
// 数据 → 隐藏小节。未发生步骤(判定/处理)灰字补位。
|
|
3407
|
+
function buildAdviceTimeline(el, view) {
|
|
3408
|
+
const lifecycle = view !== null && typeof view === 'object' && Array.isArray(view.lifecycle) ? view.lifecycle : []
|
|
3409
|
+
if (lifecycle.length === 0 || !lifecycle.some((e) => e.event === 'applied')) return null
|
|
3410
|
+
const chain = []
|
|
3411
|
+
const push = (text, dim) => chain.push(el('span', { key: String(chain.length), className: dim === true ? 'sap-adviceTimelineStepDim' : 'sap-adviceTimelineStep' }, text))
|
|
3412
|
+
for (const event of lifecycle) {
|
|
3413
|
+
if (chain.length > 0) chain.push(el('span', { key: 'sep' + chain.length }, ZH.adviceTimelineSep))
|
|
3414
|
+
push(timelineEventTextOf(event), false)
|
|
3415
|
+
}
|
|
3416
|
+
const has = (kind) => lifecycle.some((e) => e.event === kind)
|
|
3417
|
+
if (!has('judged')) { chain.push(el('span', { key: 'sepd' }, ZH.adviceTimelineSep)); push('判定:' + ZH.adviceTimelinePending, true) }
|
|
3418
|
+
if (!has('resolved')) { chain.push(el('span', { key: 'sepr' }, ZH.adviceTimelineSep)); push('撤销/保留:' + ZH.adviceTimelinePending, true) }
|
|
3419
|
+
return adviceSectionOf(el, ZH.adviceTimelineTitle, el('p', { className: 'sap-adviceTimelineLine' }, chain))
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3116
3422
|
// 候选管理设置(顶部一行 + 就地浮层):更新模式与有效期用户可配;
|
|
3117
3423
|
// 自定义按天数输入(1~365),提交时换算小时数。
|
|
3118
3424
|
function candidateModeTextOf(mode) {
|
|
@@ -3207,10 +3513,13 @@ function buildAdviceCandidateRow(el, s, actions, a, candidate, index, selected)
|
|
|
3207
3513
|
if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
3208
3514
|
actions.selectCandidate(a.profileKey, candidate.id)()
|
|
3209
3515
|
}
|
|
3516
|
+
const asset = candidateAssetByIdOf(s, candidate.id)
|
|
3517
|
+
const stateLabel = asset !== null && typeof asset.stateLabel === 'string' && asset.stateLabel !== '' ? asset.stateLabel : null
|
|
3210
3518
|
return el('div', { key: String(candidate.id), className: 'sap-candidate' + (selected === candidate.id ? ' sap-candidateSelected' : '') },
|
|
3211
3519
|
el('button', { type: 'button', className: 'sap-candidateRow', 'aria-expanded': open, onClick: actions.toggleCandidateOpen(candidate) },
|
|
3212
3520
|
el('span', { role: 'radio', 'aria-checked': selected === candidate.id, className: 'sap-candidateRadio', onClick: onRadio }),
|
|
3213
3521
|
el('span', { className: 'sap-candidateName' }, '候选 ' + (index + 1) + ' · ' + candidate.name),
|
|
3522
|
+
stateLabel !== null ? el('span', { className: 'sap-candidateState' }, stateLabel) : null,
|
|
3214
3523
|
el('span', { className: 'sap-candidateValidity' }, candidateValidityText(candidate.expiry)),
|
|
3215
3524
|
el('span', { className: 'sap-candidateArrow' }, '▸')),
|
|
3216
3525
|
open === true ? el('div', { className: 'sap-candidateBody' },
|
|
@@ -3233,7 +3542,7 @@ function buildAdviceCandidateRow(el, s, actions, a, candidate, index, selected)
|
|
|
3233
3542
|
}
|
|
3234
3543
|
|
|
3235
3544
|
// 生成信息行(最浅灰,两分支模板):有样本 = 生成于 MM-DD · 基于最近 N 次派发 ·
|
|
3236
|
-
// 近期表现 X(满分 100,X=round(
|
|
3545
|
+
// 近期表现 X(满分 100,X=round(表现分×100));无样本 = 生成于 MM-DD · 暂无近期派发
|
|
3237
3546
|
// 样本,仅供参考(不显示 0 分);无生成时间省去「生成于」段。
|
|
3238
3547
|
function candidateGeneratedLine(el, candidate) {
|
|
3239
3548
|
const basis = candidate.basis !== null && typeof candidate.basis === 'object' ? candidate.basis : {}
|
|
@@ -3251,24 +3560,36 @@ function candidateGeneratedLine(el, candidate) {
|
|
|
3251
3560
|
return el('p', { className: 'sap-candidateHint' }, parts.join(' · '))
|
|
3252
3561
|
}
|
|
3253
3562
|
|
|
3254
|
-
// 卡底部:无对应方案 = [保存为方案](统一主按钮)+
|
|
3255
|
-
//
|
|
3256
|
-
|
|
3563
|
+
// 卡底部:无对应方案 = [保存为方案](统一主按钮)+ 备注;有对应方案 = [按建议调整
|
|
3564
|
+
// 方案] + 定位。按钮启用条件 = applyEnabled && 已选候选;禁用 title = 禁用原因
|
|
3565
|
+
// (冷启动 = reasons.join(';') 并附卡脚一行灰字;观察中 = 暂不支持再次调整)。
|
|
3566
|
+
function buildAdviceFoot(el, s, actions, a, profile, candidates, assetView) {
|
|
3257
3567
|
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
3568
|
const generatedAt = formatMonthDayTime(basis.generatedAt)
|
|
3259
3569
|
const generatedText = generatedAt !== '' ? ZH.candidateGeneratedPrefix + generatedAt : ''
|
|
3260
|
-
const noteLines = (
|
|
3261
|
-
|
|
3262
|
-
generatedText !== '' ? el('p', { className: 'sap-adviceFootNote' }, generatedText) : null)
|
|
3263
|
-
if (profile !== null) {
|
|
3570
|
+
const noteLines = (lines) => el('div', { className: 'sap-adviceFootNotes' }, lines.map((line) => el('p', { key: line, className: 'sap-adviceFootNote' }, line)))
|
|
3571
|
+
if (profile === null) {
|
|
3264
3572
|
return el('div', { className: 'sap-adviceFoot' },
|
|
3265
|
-
el('button', { type: 'button', className: 'sap-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
}
|
|
3573
|
+
el('button', { type: 'button', className: 'sap-primaryButton', style: smallBtn, onClick: actions.saveAdviceProfile(a.profileKey) }, ZH.adviceSaveProfile),
|
|
3574
|
+
noteLines([ZH.adviceFootNoteSave]))
|
|
3575
|
+
}
|
|
3576
|
+
const status = s.evolutionStatus ?? {}
|
|
3577
|
+
const applyEnabled = status.applyEnabled === true
|
|
3578
|
+
const selectedId = candidateSelectedIdOf(s, a.profileKey, candidates)
|
|
3579
|
+
const canaryState = assetView !== null && assetView.canary !== null && typeof assetView.canary === 'object' ? assetView.canary.state : 'pendingApply'
|
|
3580
|
+
const observing = canaryState === 'observing' || canaryState === 'stalled' || canaryState === 'inconclusive' || canaryState === 'regress' || canaryState === 'aborted'
|
|
3581
|
+
let disabledTitle = ''
|
|
3582
|
+
if (observing) disabledTitle = ZH.adviceApplyObservingTitle
|
|
3583
|
+
else if (!applyEnabled) disabledTitle = Array.isArray(status.reasons) && status.reasons.length > 0 ? status.reasons.join(';') : ZH.candidateNotApplicable
|
|
3584
|
+
else if (selectedId === '') disabledTitle = ZH.adviceApplySelectTitle
|
|
3585
|
+
const disabled = disabledTitle !== ''
|
|
3586
|
+
const notes = [ZH.adviceFootNoteMatchedPrefix + (profile.name || profile.id) + '(' + profile.id + ')']
|
|
3587
|
+
if (generatedText !== '') notes.push(generatedText)
|
|
3588
|
+
if (!applyEnabled && !observing) notes.push(disabledTitle)
|
|
3269
3589
|
return el('div', { className: 'sap-adviceFoot' },
|
|
3270
|
-
el('button', { type: 'button', className: 'sap-
|
|
3271
|
-
|
|
3590
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, disabled, title: disabledTitle, onClick: disabled ? undefined : actions.openApplyModal(a.profileKey, selectedId) }, ZH.adviceApplyDisabled),
|
|
3591
|
+
el('button', { type: 'button', className: 'sap-detailToggle', onClick: actions.locateProfile(profile.id) }, ZH.adviceRowLocate),
|
|
3592
|
+
noteLines(notes))
|
|
3272
3593
|
}
|
|
3273
3594
|
|
|
3274
3595
|
// 逃生舱卡片:开关 + (打开时)显式警告文案 + 放行集增删(输入框 + 添加按钮,
|
|
@@ -3562,7 +3883,7 @@ function buildSectionReturn(el, s, actions, rows, addCard, reminderCenter, sessi
|
|
|
3562
3883
|
], true)),
|
|
3563
3884
|
buildTabZone(el, s, 'advice', buildAdvicePanel(el, s, actions, sessions)),
|
|
3564
3885
|
buildTabZone(el, s, 'safety', buildSafetySection(el, s, actions, s.auditReminders)),
|
|
3565
|
-
buildTabZone(el, s, 'reminders', reminderCenter)
|
|
3886
|
+
buildTabZone(el, s, 'reminders', el(reminderCenter, { jumpToAdvice: () => s.setActiveTab('advice') }))
|
|
3566
3887
|
)
|
|
3567
3888
|
}
|
|
3568
3889
|
|
|
@@ -3604,18 +3925,17 @@ function attachDraftActions(api, s, refresh, applyWrite, actions) {
|
|
|
3604
3925
|
function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, ReminderCenter, getSessions) {
|
|
3605
3926
|
function ProfilesSection() {
|
|
3606
3927
|
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('')
|
|
3928
|
+
const [adding, setAdding] = useState(false); const [editingId, setEditingId] = useState(null); const [error, setError] = useState(''); const [savedNotice, setSavedNotice] = useState('')
|
|
3929
|
+
const [persistWarning, setPersistWarning] = useState(''); const [form, setForm] = useState(EMPTY_FORM); const [tools, setTools] = useState([]); const [versionWarnings, setVersionWarnings] = useState([])
|
|
3930
|
+
const [pluginVersion, setPluginVersion] = useState(''); const [audit, setAudit] = useState(AUDIT_DEFAULTS); const [escapeEnabled, setEscapeEnabled] = useState(false); const [escapePresets, setEscapePresets] = useState([]); const [escapeInput, setEscapeInput] = useState('')
|
|
3611
3931
|
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)
|
|
3932
|
+
const [adviceGlobal, setAdviceGlobal] = useState(null); const [costSummary, setCostSummary] = useState({ global: null, byModel: [] }); const [draftPreviews, setDraftPreviews] = useState({}); const [candidateOpen, setCandidateOpen] = useState({})
|
|
3933
|
+
const [candidateSelected, setCandidateSelected] = useState({}); const [candidateSafetyOpen, setCandidateSafetyOpen] = useState({}); const [candidateSettings, setCandidateSettings] = useState({ candidateMode: 'auto', candidateTtlH: 24 }); const [candidateSettingsOpen, setCandidateSettingsOpen] = useState(false)
|
|
3615
3934
|
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
|
|
3935
|
+
const [activeTab, setActiveTab] = useState('profiles'); const [highlightId, setHighlightId] = useState(null); const [adviceOpen, setAdviceOpen] = useState({}); const [auditDetailOpen, setAuditDetailOpen] = useState(false); const [auditReminders, setAuditReminders] = useState([])
|
|
3936
|
+
const [evolutionStatus, setEvolutionStatus] = useState({ applyEnabled: false, canaryEnabled: false, reasons: [] }); const [evolutionAssets, setEvolutionAssets] = useState([])
|
|
3937
|
+
const [applyModal, setApplyModal] = useState({ profileKey: '', candidateId: '', open: false, error: '' }); const [rollbackModal, setRollbackModal] = useState({ assetId: '', open: false, error: '' })
|
|
3938
|
+
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, 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
3939
|
const refresh = useCallback(() => {
|
|
3620
3940
|
api('/list')
|
|
3621
3941
|
.then((data) => {
|
|
@@ -3625,11 +3945,12 @@ function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, Re
|
|
|
3625
3945
|
setAdviceGlobal(data.adviceGlobal !== null && typeof data.adviceGlobal === 'object' ? data.adviceGlobal : null)
|
|
3626
3946
|
setAdviceDetails(data.adviceDetails !== null && typeof data.adviceDetails === 'object' ? data.adviceDetails : {})
|
|
3627
3947
|
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: [] })
|
|
3948
|
+
const status = data.evolutionStatus !== null && typeof data.evolutionStatus === 'object' ? data.evolutionStatus : {}
|
|
3949
|
+
setEvolutionStatus({ applyEnabled: status.applyEnabled === true, canaryEnabled: status.canaryEnabled === true, reasons: Array.isArray(status.reasons) ? status.reasons : [] })
|
|
3628
3950
|
})
|
|
3629
3951
|
.catch((err) => setError(String((err && err.message) || err)))
|
|
3630
|
-
api('/
|
|
3631
|
-
|
|
3632
|
-
.catch(() => {})
|
|
3952
|
+
api('/evolution/assets').then((data) => setEvolutionAssets(Array.isArray(data.assets) ? data.assets : [])).catch(() => {})
|
|
3953
|
+
api('/drafts').then((data) => setDrafts(Array.isArray(data.drafts) ? data.drafts : [])).catch(() => {})
|
|
3633
3954
|
api('/reminders').then((data) => setAuditReminders(Array.isArray(data.reminders) ? data.reminders : [])).catch(() => {})
|
|
3634
3955
|
}, [])
|
|
3635
3956
|
const loadOptions = useCallback(() => {
|
|
@@ -3647,7 +3968,7 @@ function makeProfilesSection(el, api, chip, useState, useEffect, useCallback, Re
|
|
|
3647
3968
|
const applyWrite = makeApplyWrite(s); const actions = makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools)
|
|
3648
3969
|
attachDraftActions(api, s, refresh, applyWrite, actions)
|
|
3649
3970
|
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),
|
|
3971
|
+
return buildSectionReturn(el, s, actions, rows, buildAddCard(el, s, actions, toolSection), ReminderCenter, sessionsSnapshot(getSessions))
|
|
3651
3972
|
}
|
|
3652
3973
|
return ProfilesSection
|
|
3653
3974
|
}
|
|
@@ -3961,7 +4282,7 @@ function buildJsonBody(el, kv) {
|
|
|
3961
4282
|
|
|
3962
4283
|
// ── 三层信息架构(结论层 / 证据层)纯渲染 ─────────────────────────
|
|
3963
4284
|
|
|
3964
|
-
//
|
|
4285
|
+
// 停滞检测上限(待实测:measured-params 回填惯例,展示层同步标注「待实测」)。
|
|
3965
4286
|
const STALL_THRESHOLD_MS = 30000
|
|
3966
4287
|
|
|
3967
4288
|
// 折叠态状态 chip:拒绝(红)/执行中(琥珀脉冲)/异常(琥珀)/成功(绿)。
|