@winspan/claude-forge 8.18.0 → 8.25.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/dist/claudemd/resume-manager.d.ts.map +1 -1
- package/dist/claudemd/resume-manager.js +8 -7
- package/dist/claudemd/resume-manager.js.map +1 -1
- package/dist/cli/commands/agents.d.ts +3 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +62 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/storage/schema.sql +3 -0
- package/dist/core/storage/sqlite.d.ts +4 -0
- package/dist/core/storage/sqlite.d.ts.map +1 -1
- package/dist/core/storage/sqlite.js +24 -3
- package/dist/core/storage/sqlite.js.map +1 -1
- package/dist/daemon/handlers/stop.d.ts +3 -1
- package/dist/daemon/handlers/stop.d.ts.map +1 -1
- package/dist/daemon/handlers/stop.js +13 -2
- package/dist/daemon/handlers/stop.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +1 -1
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/routing-observer.d.ts +1 -0
- package/dist/daemon/routing-observer.d.ts.map +1 -1
- package/dist/daemon/routing-observer.js +29 -23
- package/dist/daemon/routing-observer.js.map +1 -1
- package/dist/intelligence/task-segmenter.d.ts +1 -0
- package/dist/intelligence/task-segmenter.d.ts.map +1 -1
- package/dist/intelligence/task-segmenter.js +10 -0
- package/dist/intelligence/task-segmenter.js.map +1 -1
- package/dist/web/server.d.ts +1 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +832 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/static/index.html +786 -2
- package/package.json +1 -1
|
@@ -353,6 +353,10 @@
|
|
|
353
353
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
354
354
|
注入
|
|
355
355
|
</a>
|
|
356
|
+
<a onclick="nav('execution-trace')" id="nav-execution-trace">
|
|
357
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
|
358
|
+
执行追踪
|
|
359
|
+
</a>
|
|
356
360
|
<div class="nav-section-title">配置</div>
|
|
357
361
|
<a onclick="nav('agents')" id="nav-agents">
|
|
358
362
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
|
@@ -523,6 +527,33 @@
|
|
|
523
527
|
</div>
|
|
524
528
|
</div>
|
|
525
529
|
|
|
530
|
+
<!-- Execution Trace -->
|
|
531
|
+
<div id="page-execution-trace" class="page">
|
|
532
|
+
<div class="toolbar">
|
|
533
|
+
<input class="search-box" id="trace-search" placeholder="搜索 prompt..." oninput="filterTraces()">
|
|
534
|
+
<select class="btn" id="trace-agent-filter" onchange="loadExecutionTraces()">
|
|
535
|
+
<option value="">全部 Agent/Skill</option>
|
|
536
|
+
</select>
|
|
537
|
+
<select class="btn" id="trace-obeyed-filter" onchange="loadExecutionTraces()">
|
|
538
|
+
<option value="">全部状态</option>
|
|
539
|
+
<option value="true">✓ 已遵守</option>
|
|
540
|
+
<option value="false">✗ 违抗</option>
|
|
541
|
+
</select>
|
|
542
|
+
<button class="btn" onclick="loadExecutionTraces()">↻ 刷新</button>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="panel" style="margin-bottom:1rem">
|
|
545
|
+
<div class="panel-header"><span class="panel-title">实时状态</span></div>
|
|
546
|
+
<div class="panel-body">
|
|
547
|
+
<div id="trace-live-status" style="max-height:200px;overflow-y:auto;font-size:0.875rem"></div>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
<div class="panel">
|
|
551
|
+
<div class="panel-body">
|
|
552
|
+
<div id="trace-list"></div>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
|
|
526
557
|
<!-- Agent Management -->
|
|
527
558
|
<div id="page-agents" class="page">
|
|
528
559
|
<div class="panel">
|
|
@@ -579,6 +610,8 @@
|
|
|
579
610
|
<button class="btn" id="routing-tab-overview" onclick="routingTab('overview')" style="border-radius:0;border:none">总览</button>
|
|
580
611
|
<button class="btn" id="routing-tab-events" onclick="routingTab('events')" style="border-radius:0;border:none">决策明细</button>
|
|
581
612
|
<button class="btn" id="routing-tab-refusals" onclick="routingTab('refusals')" style="border-radius:0;border:none">违抗聚合</button>
|
|
613
|
+
<button class="btn" id="routing-tab-performance" onclick="routingTab('performance')" style="border-radius:0;border:none">性能分析</button>
|
|
614
|
+
<button class="btn" id="routing-tab-ai-optimization" onclick="routingTab('ai-optimization')" style="border-radius:0;border:none">AI 优化</button>
|
|
582
615
|
<button class="btn" id="routing-tab-editor" onclick="routingTab('editor')" style="border-radius:0;border:none">路由调优</button>
|
|
583
616
|
<button class="btn" id="routing-tab-experiments" onclick="routingTab('experiments')" style="border-radius:0;border:none">A/B 测试</button>
|
|
584
617
|
<button class="btn" id="routing-tab-recommendations" onclick="routingTab('recommendations')" style="border-radius:0;border:none">规则推荐</button>
|
|
@@ -634,6 +667,57 @@
|
|
|
634
667
|
</div>
|
|
635
668
|
</div>
|
|
636
669
|
|
|
670
|
+
<!-- Performance subpanel -->
|
|
671
|
+
<div id="routing-sub-performance" style="display:none">
|
|
672
|
+
<div class="cards" id="routing-performance-cards"></div>
|
|
673
|
+
<div class="grid-2" style="margin-top:1.25rem">
|
|
674
|
+
<div class="panel">
|
|
675
|
+
<div class="panel-header"><span class="panel-title">每日路由趋势</span></div>
|
|
676
|
+
<div class="panel-body"><div class="chart-wrap"><canvas id="chart-routing-trend"></canvas></div></div>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="panel">
|
|
679
|
+
<div class="panel-header"><span class="panel-title">平均真实执行耗时趋势</span></div>
|
|
680
|
+
<div class="panel-body"><div class="chart-wrap"><canvas id="chart-routing-latency"></canvas></div></div>
|
|
681
|
+
</div>
|
|
682
|
+
</div>
|
|
683
|
+
<div class="grid-2" style="margin-top:1.25rem">
|
|
684
|
+
<div class="panel">
|
|
685
|
+
<div class="panel-header"><span class="panel-title">Per-agent 指标</span></div>
|
|
686
|
+
<div class="panel-body">
|
|
687
|
+
<table>
|
|
688
|
+
<thead><tr><th>Agent</th><th>总次数</th><th>已判定</th><th>遵守率</th><th>违抗率</th><th>分类耗时</th><th>真实耗时</th></tr></thead>
|
|
689
|
+
<tbody id="routing-performance-by-agent"></tbody>
|
|
690
|
+
</table>
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
<div class="panel">
|
|
694
|
+
<div class="panel-header"><span class="panel-title">高违抗率榜单</span></div>
|
|
695
|
+
<div class="panel-body" id="routing-high-refusal"></div>
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
698
|
+
</div>
|
|
699
|
+
|
|
700
|
+
<!-- AI optimization subpanel -->
|
|
701
|
+
<div id="routing-sub-ai-optimization" style="display:none">
|
|
702
|
+
<div class="panel">
|
|
703
|
+
<div class="panel-header">
|
|
704
|
+
<span class="panel-title">AI 优化建议</span>
|
|
705
|
+
<div style="display:flex;gap:0.5rem;align-items:center">
|
|
706
|
+
<label style="font-size:0.875rem;color:var(--text-dim)">最小样本
|
|
707
|
+
<input id="ai-opt-min-attempts" type="number" value="10" min="1" max="100" style="width:72px;margin-left:0.25rem" class="btn">
|
|
708
|
+
</label>
|
|
709
|
+
<button class="btn" onclick="loadRoutingAIOptimization()">↻ 生成建议</button>
|
|
710
|
+
</div>
|
|
711
|
+
</div>
|
|
712
|
+
<div class="panel-body">
|
|
713
|
+
<div style="margin-bottom:0.75rem;font-size:0.875rem;color:var(--text-dim)">
|
|
714
|
+
基于高违抗率 agent、规则推荐、执行追踪证据,由 AI 生成针对 agent / skill / routing rule 的优化建议。
|
|
715
|
+
</div>
|
|
716
|
+
<div id="routing-ai-optimization"></div>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
|
|
637
721
|
<!-- Editor subpanel (Phase 3 Feature 2, upgraded to Monaco in Phase 4 Feature 1) -->
|
|
638
722
|
<div id="routing-sub-editor" style="display:none">
|
|
639
723
|
<div class="panel">
|
|
@@ -742,6 +826,10 @@
|
|
|
742
826
|
const API = '';
|
|
743
827
|
let allEvents = [], allSessions = [], allInjections = [];
|
|
744
828
|
let charts = {};
|
|
829
|
+
let traceStream = null;
|
|
830
|
+
let traceRefreshTimer = null;
|
|
831
|
+
let currentPatchPreview = null;
|
|
832
|
+
let currentAIOptimizationData = null;
|
|
745
833
|
|
|
746
834
|
// === Navigation ===
|
|
747
835
|
function nav(page) {
|
|
@@ -751,13 +839,14 @@ function nav(page) {
|
|
|
751
839
|
const pageEl = document.getElementById('page-' + page);
|
|
752
840
|
if (navEl) navEl.classList.add('active');
|
|
753
841
|
if (pageEl) pageEl.classList.add('active');
|
|
754
|
-
const titles = { dashboard:'仪表盘', sessions:'会话', events:'事件', injections:'注入', agents:'Agent 管理', skills:'Skill 管理', 'ai-config':'AI 配置', routing:'Agent 路由' };
|
|
842
|
+
const titles = { dashboard:'仪表盘', sessions:'会话', events:'事件', injections:'注入', 'execution-trace':'执行追踪', agents:'Agent 管理', skills:'Skill 管理', 'ai-config':'AI 配置', routing:'Agent 路由' };
|
|
755
843
|
document.getElementById('topbar-title').textContent = titles[page] || page;
|
|
756
844
|
closeDrawer();
|
|
757
845
|
if (page === 'dashboard') loadDashboard();
|
|
758
846
|
else if (page === 'sessions') loadSessions();
|
|
759
847
|
else if (page === 'events') loadEvents();
|
|
760
848
|
else if (page === 'injections') loadInjections();
|
|
849
|
+
else if (page === 'execution-trace') loadExecutionTraces();
|
|
761
850
|
else if (page === 'agents') loadAgents();
|
|
762
851
|
else if (page === 'skills') loadSkills();
|
|
763
852
|
else if (page === 'ai-config') loadAIConfig();
|
|
@@ -1150,7 +1239,7 @@ let routingCurrentTab = 'overview';
|
|
|
1150
1239
|
|
|
1151
1240
|
function routingTab(tab) {
|
|
1152
1241
|
routingCurrentTab = tab;
|
|
1153
|
-
['overview', 'events', 'refusals', 'editor', 'experiments', 'recommendations'].forEach(t => {
|
|
1242
|
+
['overview', 'events', 'refusals', 'performance', 'ai-optimization', 'editor', 'experiments', 'recommendations'].forEach(t => {
|
|
1154
1243
|
const btn = document.getElementById('routing-tab-' + t);
|
|
1155
1244
|
const sub = document.getElementById('routing-sub-' + t);
|
|
1156
1245
|
if (btn) btn.classList.toggle('active', t === tab);
|
|
@@ -1171,6 +1260,8 @@ async function loadRouting() {
|
|
|
1171
1260
|
if (routingCurrentTab === 'overview') return loadRoutingOverview();
|
|
1172
1261
|
if (routingCurrentTab === 'events') return loadRoutingEvents();
|
|
1173
1262
|
if (routingCurrentTab === 'refusals') return loadRoutingRefusals();
|
|
1263
|
+
if (routingCurrentTab === 'performance') return loadRoutingPerformance();
|
|
1264
|
+
if (routingCurrentTab === 'ai-optimization') return loadRoutingAIOptimization();
|
|
1174
1265
|
if (routingCurrentTab === 'editor') return loadRoutingConfig();
|
|
1175
1266
|
if (routingCurrentTab === 'experiments') {
|
|
1176
1267
|
await loadExperimentsConfig();
|
|
@@ -1408,6 +1499,9 @@ async function viewAgent(name) {
|
|
|
1408
1499
|
${data.tools ? `<span class="badge badge-allow">${data.tools}</span>` : ''}
|
|
1409
1500
|
</div>
|
|
1410
1501
|
</div>
|
|
1502
|
+
<div style="margin-bottom:1rem;display:flex;gap:0.5rem">
|
|
1503
|
+
<button class="btn" onclick="viewAgentVersions('${name}')">📜 版本历史</button>
|
|
1504
|
+
</div>
|
|
1411
1505
|
<div style="background:var(--bg-secondary);padding:1rem;border-radius:var(--radius-sm);max-height:60vh;overflow-y:auto">
|
|
1412
1506
|
<pre style="margin:0;white-space:pre-wrap;font-size:0.875rem">${escapeHtml(data.content)}</pre>
|
|
1413
1507
|
</div>
|
|
@@ -1491,6 +1585,87 @@ async function loadSkills() {
|
|
|
1491
1585
|
}
|
|
1492
1586
|
}
|
|
1493
1587
|
|
|
1588
|
+
async function viewAgentVersions(name) {
|
|
1589
|
+
try {
|
|
1590
|
+
const r = await fetch(API + `/api/agents/${name}/versions`);
|
|
1591
|
+
if (!r.ok) throw new Error('Failed to load versions');
|
|
1592
|
+
const data = await r.json();
|
|
1593
|
+
|
|
1594
|
+
if (data.versions.length === 0) {
|
|
1595
|
+
alert('暂无版本历史');
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const html = `
|
|
1600
|
+
<div style="margin-bottom:1rem">
|
|
1601
|
+
<h3>版本历史: ${name}</h3>
|
|
1602
|
+
<p style="color:var(--text-dim)">共 ${data.versions.length} 个备份版本</p>
|
|
1603
|
+
</div>
|
|
1604
|
+
<div style="max-height:60vh;overflow-y:auto">
|
|
1605
|
+
${data.versions.map(v => `
|
|
1606
|
+
<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:0.75rem;margin-bottom:0.5rem">
|
|
1607
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem">
|
|
1608
|
+
<div>
|
|
1609
|
+
<div style="font-weight:500">${new Date(v.mtime).toLocaleString('zh-CN')}</div>
|
|
1610
|
+
<div style="font-size:0.75rem;color:var(--text-dim)">${(v.size / 1024).toFixed(1)} KB</div>
|
|
1611
|
+
</div>
|
|
1612
|
+
<div style="display:flex;gap:0.25rem">
|
|
1613
|
+
<button class="btn" onclick="viewAgentVersion('${name}', '${v.timestamp}')">查看</button>
|
|
1614
|
+
<button class="btn btn-primary" onclick="rollbackAgent('${name}', '${v.timestamp}')">回滚</button>
|
|
1615
|
+
</div>
|
|
1616
|
+
</div>
|
|
1617
|
+
</div>
|
|
1618
|
+
`).join('')}
|
|
1619
|
+
</div>
|
|
1620
|
+
`;
|
|
1621
|
+
openDrawer(`版本历史: ${name}`, html);
|
|
1622
|
+
} catch (err) {
|
|
1623
|
+
alert('加载失败: ' + err.message);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
async function viewAgentVersion(name, timestamp) {
|
|
1628
|
+
try {
|
|
1629
|
+
const r = await fetch(API + `/api/agents/${name}/versions/${encodeURIComponent(timestamp)}`);
|
|
1630
|
+
if (!r.ok) throw new Error('Failed to load version');
|
|
1631
|
+
const data = await r.json();
|
|
1632
|
+
|
|
1633
|
+
const html = `
|
|
1634
|
+
<div style="margin-bottom:1rem">
|
|
1635
|
+
<h3>历史版本: ${name}</h3>
|
|
1636
|
+
<p style="color:var(--text-dim)">${new Date(timestamp).toLocaleString('zh-CN')}</p>
|
|
1637
|
+
</div>
|
|
1638
|
+
<div style="background:var(--bg-secondary);padding:1rem;border-radius:var(--radius-sm);max-height:60vh;overflow-y:auto">
|
|
1639
|
+
<pre style="margin:0;white-space:pre-wrap;font-size:0.875rem">${escapeHtml(data.content)}</pre>
|
|
1640
|
+
</div>
|
|
1641
|
+
`;
|
|
1642
|
+
openDrawer(`历史版本: ${name}`, html);
|
|
1643
|
+
} catch (err) {
|
|
1644
|
+
alert('加载失败: ' + err.message);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
async function rollbackAgent(name, timestamp) {
|
|
1649
|
+
if (!confirm(`确定要回滚到 ${new Date(timestamp).toLocaleString('zh-CN')} 的版本吗?\n\n当前版本会自动备份。`)) {
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
try {
|
|
1654
|
+
const r = await fetch(API + `/api/agents/${name}/rollback`, {
|
|
1655
|
+
method: 'POST',
|
|
1656
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1657
|
+
body: JSON.stringify({ timestamp }),
|
|
1658
|
+
});
|
|
1659
|
+
if (!r.ok) throw new Error('Failed to rollback');
|
|
1660
|
+
const data = await r.json();
|
|
1661
|
+
alert(`✓ 回滚成功!\n\n当前版本已备份到:\n${data.backup}`);
|
|
1662
|
+
closeDrawer();
|
|
1663
|
+
loadAgents();
|
|
1664
|
+
} catch (err) {
|
|
1665
|
+
alert('回滚失败: ' + err.message);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1494
1669
|
async function viewSkill(name) {
|
|
1495
1670
|
try {
|
|
1496
1671
|
const r = await fetch(API + `/api/skills/${name}`);
|
|
@@ -1506,6 +1681,9 @@ async function viewSkill(name) {
|
|
|
1506
1681
|
<span class="badge ${data.source === 'official' ? 'badge-allow' : 'badge-warn'}">${data.source === 'official' ? '官方' : '用户'}</span>
|
|
1507
1682
|
</div>
|
|
1508
1683
|
</div>
|
|
1684
|
+
<div style="margin-bottom:1rem;display:flex;gap:0.5rem">
|
|
1685
|
+
<button class="btn" onclick="viewSkillVersions('${name}')">📜 版本历史</button>
|
|
1686
|
+
</div>
|
|
1509
1687
|
<div style="background:var(--bg-secondary);padding:1rem;border-radius:var(--radius-sm);max-height:60vh;overflow-y:auto">
|
|
1510
1688
|
<pre style="margin:0;white-space:pre-wrap;font-size:0.875rem">${escapeHtml(data.content)}</pre>
|
|
1511
1689
|
</div>
|
|
@@ -1516,6 +1694,87 @@ async function viewSkill(name) {
|
|
|
1516
1694
|
}
|
|
1517
1695
|
}
|
|
1518
1696
|
|
|
1697
|
+
async function viewSkillVersions(name) {
|
|
1698
|
+
try {
|
|
1699
|
+
const r = await fetch(API + `/api/skills/${name}/versions`);
|
|
1700
|
+
if (!r.ok) throw new Error('Failed to load versions');
|
|
1701
|
+
const data = await r.json();
|
|
1702
|
+
|
|
1703
|
+
if (data.versions.length === 0) {
|
|
1704
|
+
alert('暂无版本历史');
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
const html = `
|
|
1709
|
+
<div style="margin-bottom:1rem">
|
|
1710
|
+
<h3>版本历史: ${name}</h3>
|
|
1711
|
+
<p style="color:var(--text-dim)">共 ${data.versions.length} 个备份版本</p>
|
|
1712
|
+
</div>
|
|
1713
|
+
<div style="max-height:60vh;overflow-y:auto">
|
|
1714
|
+
${data.versions.map(v => `
|
|
1715
|
+
<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:0.75rem;margin-bottom:0.5rem">
|
|
1716
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem">
|
|
1717
|
+
<div>
|
|
1718
|
+
<div style="font-weight:500">${new Date(v.mtime).toLocaleString('zh-CN')}</div>
|
|
1719
|
+
<div style="font-size:0.75rem;color:var(--text-dim)">${(v.size / 1024).toFixed(1)} KB</div>
|
|
1720
|
+
</div>
|
|
1721
|
+
<div style="display:flex;gap:0.25rem">
|
|
1722
|
+
<button class="btn" onclick="viewSkillVersion('${name}', '${v.timestamp}')">查看</button>
|
|
1723
|
+
<button class="btn btn-primary" onclick="rollbackSkill('${name}', '${v.timestamp}')">回滚</button>
|
|
1724
|
+
</div>
|
|
1725
|
+
</div>
|
|
1726
|
+
</div>
|
|
1727
|
+
`).join('')}
|
|
1728
|
+
</div>
|
|
1729
|
+
`;
|
|
1730
|
+
openDrawer(`版本历史: ${name}`, html);
|
|
1731
|
+
} catch (err) {
|
|
1732
|
+
alert('加载失败: ' + err.message);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
async function viewSkillVersion(name, timestamp) {
|
|
1737
|
+
try {
|
|
1738
|
+
const r = await fetch(API + `/api/skills/${name}/versions/${encodeURIComponent(timestamp)}`);
|
|
1739
|
+
if (!r.ok) throw new Error('Failed to load version');
|
|
1740
|
+
const data = await r.json();
|
|
1741
|
+
|
|
1742
|
+
const html = `
|
|
1743
|
+
<div style="margin-bottom:1rem">
|
|
1744
|
+
<h3>历史版本: ${name}</h3>
|
|
1745
|
+
<p style="color:var(--text-dim)">${new Date(timestamp).toLocaleString('zh-CN')}</p>
|
|
1746
|
+
</div>
|
|
1747
|
+
<div style="background:var(--bg-secondary);padding:1rem;border-radius:var(--radius-sm);max-height:60vh;overflow-y:auto">
|
|
1748
|
+
<pre style="margin:0;white-space:pre-wrap;font-size:0.875rem">${escapeHtml(data.content)}</pre>
|
|
1749
|
+
</div>
|
|
1750
|
+
`;
|
|
1751
|
+
openDrawer(`历史版本: ${name}`, html);
|
|
1752
|
+
} catch (err) {
|
|
1753
|
+
alert('加载失败: ' + err.message);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
async function rollbackSkill(name, timestamp) {
|
|
1758
|
+
if (!confirm(`确定要回滚到 ${new Date(timestamp).toLocaleString('zh-CN')} 的版本吗?\n\n当前版本会自动备份。`)) {
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
try {
|
|
1763
|
+
const r = await fetch(API + `/api/skills/${name}/rollback`, {
|
|
1764
|
+
method: 'POST',
|
|
1765
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1766
|
+
body: JSON.stringify({ timestamp }),
|
|
1767
|
+
});
|
|
1768
|
+
if (!r.ok) throw new Error('Failed to rollback');
|
|
1769
|
+
const data = await r.json();
|
|
1770
|
+
alert(`✓ 回滚成功!\n\n当前版本已备份到:\n${data.backup}`);
|
|
1771
|
+
closeDrawer();
|
|
1772
|
+
loadSkills();
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
alert('回滚失败: ' + err.message);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1519
1778
|
async function editSkill(name) {
|
|
1520
1779
|
try {
|
|
1521
1780
|
const r = await fetch(API + `/api/skills/${name}`);
|
|
@@ -1570,6 +1829,258 @@ function escapeHtml(text) {
|
|
|
1570
1829
|
return div.innerHTML;
|
|
1571
1830
|
}
|
|
1572
1831
|
|
|
1832
|
+
// === Execution Trace ===
|
|
1833
|
+
let allTraces = [];
|
|
1834
|
+
|
|
1835
|
+
async function loadExecutionTraces() {
|
|
1836
|
+
try {
|
|
1837
|
+
const agentFilter = document.getElementById('trace-agent-filter').value;
|
|
1838
|
+
const obeyedFilter = document.getElementById('trace-obeyed-filter').value;
|
|
1839
|
+
const params = new URLSearchParams({ limit: '100' });
|
|
1840
|
+
if (agentFilter) params.set('agent', agentFilter);
|
|
1841
|
+
if (obeyedFilter) params.set('obeyed', obeyedFilter);
|
|
1842
|
+
|
|
1843
|
+
const r = await fetch(API + '/api/execution-trace?' + params.toString());
|
|
1844
|
+
if (!r.ok) throw new Error('Failed to load traces');
|
|
1845
|
+
const data = await r.json();
|
|
1846
|
+
allTraces = data.traces;
|
|
1847
|
+
|
|
1848
|
+
// Populate agent filter dropdown
|
|
1849
|
+
const agentSet = new Set();
|
|
1850
|
+
allTraces.forEach(t => {
|
|
1851
|
+
if (t.routedToName) agentSet.add(t.routedToName);
|
|
1852
|
+
});
|
|
1853
|
+
const agentSelect = document.getElementById('trace-agent-filter');
|
|
1854
|
+
const currentAgent = agentSelect.value;
|
|
1855
|
+
agentSelect.innerHTML = '<option value="">全部 Agent/Skill</option>' +
|
|
1856
|
+
Array.from(agentSet).sort().map(a =>
|
|
1857
|
+
`<option value="${a}" ${a === currentAgent ? 'selected' : ''}>${a}</option>`
|
|
1858
|
+
).join('');
|
|
1859
|
+
|
|
1860
|
+
renderTraces();
|
|
1861
|
+
ensureTraceStream();
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
console.error('Failed to load traces:', err);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function ensureTraceStream() {
|
|
1868
|
+
if (traceStream) return;
|
|
1869
|
+
traceStream = new EventSource(API + '/api/execution-trace/stream');
|
|
1870
|
+
traceStream.onmessage = (e) => {
|
|
1871
|
+
try {
|
|
1872
|
+
const data = JSON.parse(e.data);
|
|
1873
|
+
if (data.type === 'execution-status') {
|
|
1874
|
+
appendLiveStatus(data);
|
|
1875
|
+
if (traceRefreshTimer) clearTimeout(traceRefreshTimer);
|
|
1876
|
+
traceRefreshTimer = setTimeout(() => loadExecutionTraces(), 500);
|
|
1877
|
+
}
|
|
1878
|
+
} catch (err) {
|
|
1879
|
+
console.error('Failed to parse SSE:', err);
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
traceStream.onerror = () => {
|
|
1883
|
+
traceStream.close();
|
|
1884
|
+
traceStream = null;
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
function appendLiveStatus(data) {
|
|
1889
|
+
const statusBox = document.getElementById('trace-live-status');
|
|
1890
|
+
if (!statusBox) return;
|
|
1891
|
+
const time = new Date(data.timestamp).toLocaleTimeString('zh-CN');
|
|
1892
|
+
const statusBadge = data.status === 'routing' ? '<span class="badge badge-info">正在路由</span>'
|
|
1893
|
+
: data.status === 'executing' ? '<span class="badge badge-warn">正在执行</span>'
|
|
1894
|
+
: data.status === 'completed' ? '<span class="badge badge-allow">已完成</span>'
|
|
1895
|
+
: '<span class="badge badge-block">失败</span>';
|
|
1896
|
+
const item = `<div style="padding:0.5rem;border-bottom:1px solid var(--border)">${time} · Session ${data.sessionId.slice(0,8)} · ${statusBadge}${data.tool ? ` · ${data.tool}` : ''}${data.error ? ` · ${data.error}` : ''}</div>`;
|
|
1897
|
+
statusBox.innerHTML = item + statusBox.innerHTML;
|
|
1898
|
+
const children = statusBox.children;
|
|
1899
|
+
while (children.length > 20) statusBox.removeChild(children[children.length - 1]);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
function renderTraces() {
|
|
1903
|
+
const searchTerm = document.getElementById('trace-search').value.toLowerCase();
|
|
1904
|
+
const filtered = allTraces.filter(t =>
|
|
1905
|
+
!searchTerm || t.prompt.toLowerCase().includes(searchTerm)
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
const list = document.getElementById('trace-list');
|
|
1909
|
+
if (filtered.length === 0) {
|
|
1910
|
+
list.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-dim)">暂无执行追踪记录</div>';
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
const items = filtered.map(t => {
|
|
1915
|
+
const time = new Date(t.timestamp).toLocaleString('zh-CN');
|
|
1916
|
+
const obeyedBadge = t.obeyed === 1
|
|
1917
|
+
? '<span class="badge badge-allow">✓ 遵守</span>'
|
|
1918
|
+
: t.obeyed === 0
|
|
1919
|
+
? '<span class="badge badge-block">✗ 违抗</span>'
|
|
1920
|
+
: '<span class="badge badge-info">— 未路由</span>';
|
|
1921
|
+
|
|
1922
|
+
const routeBadge = t.routedToName
|
|
1923
|
+
? `<span class="badge badge-info">${t.routedToType === 'agent' ? '🤖' : '⚡'} ${t.routedToName}</span>`
|
|
1924
|
+
: '<span class="badge badge-warn">无路由</span>';
|
|
1925
|
+
|
|
1926
|
+
const intentBadge = t.intent.taskType
|
|
1927
|
+
? `<span class="badge" style="background:var(--bg-secondary)">${t.intent.taskType}</span>`
|
|
1928
|
+
: '';
|
|
1929
|
+
|
|
1930
|
+
const firstToolBadge = t.firstTool
|
|
1931
|
+
? `<span class="badge" style="background:var(--bg-secondary)">首工具: ${t.firstTool}</span>`
|
|
1932
|
+
: '';
|
|
1933
|
+
|
|
1934
|
+
const classificationBadge = t.fallbackUsed
|
|
1935
|
+
? '<span class="badge badge-warn">正则兜底</span>'
|
|
1936
|
+
: t.classificationMs
|
|
1937
|
+
? `<span class="badge badge-allow">AI 分类 (${t.classificationMs}ms)</span>`
|
|
1938
|
+
: '';
|
|
1939
|
+
|
|
1940
|
+
const executionBadge = t.totalExecutionMs
|
|
1941
|
+
? `<span class="badge" style="background:var(--purple-soft);color:var(--purple)">真实耗时: ${t.totalExecutionMs}ms</span>`
|
|
1942
|
+
: '';
|
|
1943
|
+
|
|
1944
|
+
const completionBadge = t.completionReason
|
|
1945
|
+
? `<span class="badge" style="background:var(--bg-secondary)">完成来源: ${t.completionReason}</span>`
|
|
1946
|
+
: '';
|
|
1947
|
+
|
|
1948
|
+
return `
|
|
1949
|
+
<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:1rem;margin-bottom:0.75rem;cursor:pointer;transition:background 0.2s"
|
|
1950
|
+
onclick="viewTraceDetails('${t.sessionId}')"
|
|
1951
|
+
onmouseover="this.style.background='var(--bg-secondary)'"
|
|
1952
|
+
onmouseout="this.style.background='transparent'">
|
|
1953
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:0.5rem">
|
|
1954
|
+
<div style="flex:1">
|
|
1955
|
+
<div style="font-weight:500;margin-bottom:0.25rem">${escapeHtml(t.prompt.slice(0, 100))}${t.prompt.length > 100 ? '...' : ''}</div>
|
|
1956
|
+
<div style="font-size:0.75rem;color:var(--text-dim)">${time} · Session: ${t.sessionId.slice(0, 8)}</div>
|
|
1957
|
+
</div>
|
|
1958
|
+
<div style="display:flex;gap:0.25rem;flex-wrap:wrap;justify-content:flex-end">
|
|
1959
|
+
${obeyedBadge}
|
|
1960
|
+
${routeBadge}
|
|
1961
|
+
</div>
|
|
1962
|
+
</div>
|
|
1963
|
+
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;font-size:0.75rem">
|
|
1964
|
+
${intentBadge}
|
|
1965
|
+
${firstToolBadge}
|
|
1966
|
+
${classificationBadge}
|
|
1967
|
+
${executionBadge}
|
|
1968
|
+
${completionBadge}
|
|
1969
|
+
${t.taskChain.length > 0 ? `<span class="badge badge-info">子 Agent: ${t.taskChain.join(' → ')}</span>` : ''}
|
|
1970
|
+
</div>
|
|
1971
|
+
</div>
|
|
1972
|
+
`;
|
|
1973
|
+
}).join('');
|
|
1974
|
+
|
|
1975
|
+
list.innerHTML = items;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
function filterTraces() {
|
|
1979
|
+
renderTraces();
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
async function viewTraceDetails(sessionId) {
|
|
1983
|
+
try {
|
|
1984
|
+
const r = await fetch(API + `/api/execution-trace/${sessionId}`);
|
|
1985
|
+
if (!r.ok) throw new Error('Failed to load trace details');
|
|
1986
|
+
const data = await r.json();
|
|
1987
|
+
|
|
1988
|
+
const routing = data.routing;
|
|
1989
|
+
const obeyedText = routing.obeyed === 1
|
|
1990
|
+
? '<span class="badge badge-allow">✓ 遵守</span>'
|
|
1991
|
+
: routing.obeyed === 0
|
|
1992
|
+
? '<span class="badge badge-block">✗ 违抗</span>'
|
|
1993
|
+
: '<span class="badge badge-info">— 未路由</span>';
|
|
1994
|
+
|
|
1995
|
+
const html = `
|
|
1996
|
+
<div style="margin-bottom:1.5rem">
|
|
1997
|
+
<h3>执行追踪详情</h3>
|
|
1998
|
+
<div style="font-size:0.875rem;color:var(--text-dim);margin-top:0.5rem">Session: ${sessionId}</div>
|
|
1999
|
+
</div>
|
|
2000
|
+
|
|
2001
|
+
<div style="margin-bottom:1.5rem">
|
|
2002
|
+
<h4 style="margin-bottom:0.5rem">用户 Prompt</h4>
|
|
2003
|
+
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);font-size:0.875rem">
|
|
2004
|
+
${escapeHtml(routing.prompt)}
|
|
2005
|
+
</div>
|
|
2006
|
+
</div>
|
|
2007
|
+
|
|
2008
|
+
<div style="margin-bottom:1.5rem">
|
|
2009
|
+
<h4 style="margin-bottom:0.5rem">AI 意图分类</h4>
|
|
2010
|
+
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);font-size:0.875rem">
|
|
2011
|
+
<div><strong>任务类型:</strong> ${routing.intent.taskType || '—'}</div>
|
|
2012
|
+
<div><strong>复杂度:</strong> ${routing.intent.complexity || '—'}</div>
|
|
2013
|
+
<div><strong>分类耗时:</strong> ${routing.classificationMs ? routing.classificationMs + 'ms' : '—'}</div>
|
|
2014
|
+
<div><strong>分类方式:</strong> ${routing.fallbackUsed ? '<span class="badge badge-warn">正则兜底</span>' : '<span class="badge badge-allow">AI 分类</span>'}</div>
|
|
2015
|
+
${routing.intent.reasoning ? `<div style="margin-top:0.5rem"><strong>推理:</strong> ${escapeHtml(routing.intent.reasoning)}</div>` : ''}
|
|
2016
|
+
</div>
|
|
2017
|
+
</div>
|
|
2018
|
+
|
|
2019
|
+
<div style="margin-bottom:1.5rem">
|
|
2020
|
+
<h4 style="margin-bottom:0.5rem">路由决策</h4>
|
|
2021
|
+
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);font-size:0.875rem">
|
|
2022
|
+
<div><strong>路由类型:</strong> ${routing.routedToType || '无'}</div>
|
|
2023
|
+
<div><strong>路由目标:</strong> ${routing.routedToName || '—'}</div>
|
|
2024
|
+
<div><strong>执行状态:</strong> ${obeyedText}</div>
|
|
2025
|
+
<div><strong>完成来源:</strong> ${routing.completionReason || '—'}</div>
|
|
2026
|
+
${routing.refusalReason ? `<div style="margin-top:0.5rem;color:var(--red)"><strong>违抗原因:</strong> ${escapeHtml(routing.refusalReason)}</div>` : ''}
|
|
2027
|
+
</div>
|
|
2028
|
+
</div>
|
|
2029
|
+
|
|
2030
|
+
<div style="margin-bottom:1.5rem">
|
|
2031
|
+
<h4 style="margin-bottom:0.5rem">时间分解</h4>
|
|
2032
|
+
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);font-size:0.875rem">
|
|
2033
|
+
<div><strong>路由开始:</strong> ${routing.timestamp ? new Date(routing.timestamp).toLocaleString('zh-CN') : '—'}</div>
|
|
2034
|
+
<div><strong>首工具时间:</strong> ${routing.firstToolTs ? new Date(routing.firstToolTs).toLocaleString('zh-CN') : '—'}</div>
|
|
2035
|
+
<div><strong>执行完成:</strong> ${routing.completedTs ? new Date(routing.completedTs).toLocaleString('zh-CN') : '—'}</div>
|
|
2036
|
+
<div><strong>分类耗时:</strong> ${routing.classificationMs ? routing.classificationMs + 'ms' : '—'}</div>
|
|
2037
|
+
<div><strong>首工具延迟:</strong> ${routing.firstToolTs ? (routing.firstToolTs - routing.timestamp) + 'ms' : '—'}</div>
|
|
2038
|
+
<div><strong>真实执行耗时:</strong> ${routing.totalExecutionMs ? routing.totalExecutionMs + 'ms' : '—'}</div>
|
|
2039
|
+
</div>
|
|
2040
|
+
</div>
|
|
2041
|
+
|
|
2042
|
+
<div style="margin-bottom:1.5rem">
|
|
2043
|
+
<h4 style="margin-bottom:0.5rem">实际执行</h4>
|
|
2044
|
+
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);font-size:0.875rem">
|
|
2045
|
+
<div><strong>首个工具:</strong> ${routing.firstTool || '—'}</div>
|
|
2046
|
+
${routing.taskChain.length > 0 ? `<div><strong>子 Agent 链:</strong> ${routing.taskChain.join(' → ')}</div>` : ''}
|
|
2047
|
+
</div>
|
|
2048
|
+
</div>
|
|
2049
|
+
|
|
2050
|
+
<div style="margin-bottom:1.5rem">
|
|
2051
|
+
<h4 style="margin-bottom:0.5rem">事件时间线 (${data.events.length} 个事件)</h4>
|
|
2052
|
+
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);max-height:300px;overflow-y:auto;font-size:0.875rem">
|
|
2053
|
+
${data.events.map(e => `
|
|
2054
|
+
<div style="padding:0.25rem 0;border-bottom:1px solid var(--border)">
|
|
2055
|
+
<span style="color:var(--text-dim)">${new Date(e.timestamp).toLocaleTimeString('zh-CN')}</span>
|
|
2056
|
+
<span class="badge badge-info" style="margin:0 0.5rem">${e.hook_type}</span>
|
|
2057
|
+
${e.tool_name ? `<span class="badge">${e.tool_name}</span>` : ''}
|
|
2058
|
+
</div>
|
|
2059
|
+
`).join('')}
|
|
2060
|
+
</div>
|
|
2061
|
+
</div>
|
|
2062
|
+
|
|
2063
|
+
${data.injections.length > 0 ? `
|
|
2064
|
+
<div style="margin-bottom:1.5rem">
|
|
2065
|
+
<h4 style="margin-bottom:0.5rem">注入内容 (${data.injections.length} 条)</h4>
|
|
2066
|
+
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);max-height:300px;overflow-y:auto;font-size:0.875rem">
|
|
2067
|
+
${data.injections.map(inj => `
|
|
2068
|
+
<div style="padding:0.5rem 0;border-bottom:1px solid var(--border)">
|
|
2069
|
+
<div><span class="badge badge-info">${inj.injection_type}</span></div>
|
|
2070
|
+
<div style="margin-top:0.25rem;white-space:pre-wrap;font-family:monospace;font-size:0.8rem">${escapeHtml(inj.content.slice(0, 200))}${inj.content.length > 200 ? '...' : ''}</div>
|
|
2071
|
+
</div>
|
|
2072
|
+
`).join('')}
|
|
2073
|
+
</div>
|
|
2074
|
+
</div>
|
|
2075
|
+
` : ''}
|
|
2076
|
+
`;
|
|
2077
|
+
|
|
2078
|
+
openDrawer('执行追踪详情', html);
|
|
2079
|
+
} catch (err) {
|
|
2080
|
+
alert('加载失败: ' + err.message);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
1573
2084
|
function routingWindow() {
|
|
1574
2085
|
const el = document.getElementById('routing-window');
|
|
1575
2086
|
return el ? el.value : '168';
|
|
@@ -1632,6 +2143,279 @@ function card(label, value, color) {
|
|
|
1632
2143
|
+ '</div>';
|
|
1633
2144
|
}
|
|
1634
2145
|
|
|
2146
|
+
async function loadRoutingPerformance() {
|
|
2147
|
+
try {
|
|
2148
|
+
const r = await fetch(API + '/api/routing/performance?window=' + routingWindow() + '&minAttempts=10');
|
|
2149
|
+
const data = await r.json();
|
|
2150
|
+
|
|
2151
|
+
document.getElementById('routing-performance-cards').innerHTML = [
|
|
2152
|
+
card('总路由数', data.summary.totalRouted),
|
|
2153
|
+
card('已判定', data.summary.totalJudged),
|
|
2154
|
+
card('遵守率', pct(data.summary.obedienceRate)),
|
|
2155
|
+
card('违抗率', pct(data.summary.refusalRate)),
|
|
2156
|
+
card('平均分类耗时', ms(data.summary.avgClassificationMs)),
|
|
2157
|
+
card('平均真实耗时', ms(data.summary.avgExecutionMs)),
|
|
2158
|
+
].join('');
|
|
2159
|
+
|
|
2160
|
+
const byAgentTbody = document.getElementById('routing-performance-by-agent');
|
|
2161
|
+
byAgentTbody.innerHTML = (data.byAgent || []).length === 0
|
|
2162
|
+
? '<tr><td colspan="7">' + empty('暂无性能数据') + '</td></tr>'
|
|
2163
|
+
: data.byAgent.map(a => '<tr>'
|
|
2164
|
+
+ '<td style="font-family:monospace">' + a.agent + '</td>'
|
|
2165
|
+
+ '<td>' + a.total + '</td>'
|
|
2166
|
+
+ '<td>' + a.judged + '</td>'
|
|
2167
|
+
+ '<td>' + pct(a.obedienceRate) + '</td>'
|
|
2168
|
+
+ '<td>' + pct(a.refusalRate) + '</td>'
|
|
2169
|
+
+ '<td>' + ms(a.avgClassificationMs) + '</td>'
|
|
2170
|
+
+ '<td>' + ms(a.avgExecutionMs) + '</td>'
|
|
2171
|
+
+ '</tr>').join('');
|
|
2172
|
+
|
|
2173
|
+
const highRefusal = document.getElementById('routing-high-refusal');
|
|
2174
|
+
highRefusal.innerHTML = (data.highRefusalAgents || []).length === 0
|
|
2175
|
+
? empty('暂无达到阈值的高违抗率 Agent')
|
|
2176
|
+
: '<table><thead><tr><th>Agent</th><th>样本</th><th>违抗率</th><th>分类耗时</th><th>真实耗时</th></tr></thead><tbody>'
|
|
2177
|
+
+ data.highRefusalAgents.map(a => '<tr>'
|
|
2178
|
+
+ '<td style="font-family:monospace">' + a.agent + '</td>'
|
|
2179
|
+
+ '<td>' + a.totalAttempts + '</td>'
|
|
2180
|
+
+ '<td>' + pct(a.refusalRate) + '</td>'
|
|
2181
|
+
+ '<td>' + ms(a.avgClassificationMs) + '</td>'
|
|
2182
|
+
+ '<td>' + ms(a.avgExecutionMs) + '</td>'
|
|
2183
|
+
+ '</tr>').join('')
|
|
2184
|
+
+ '</tbody></table>';
|
|
2185
|
+
|
|
2186
|
+
renderRoutingPerformanceCharts(data.dailyTrend || []);
|
|
2187
|
+
} catch (err) {
|
|
2188
|
+
document.getElementById('routing-performance-cards').innerHTML = empty('加载失败: ' + err.message);
|
|
2189
|
+
document.getElementById('routing-performance-by-agent').innerHTML = '<tr><td colspan="7">' + empty('加载失败: ' + err.message) + '</td></tr>';
|
|
2190
|
+
document.getElementById('routing-high-refusal').innerHTML = empty('加载失败: ' + err.message);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
function renderRoutingPerformanceCharts(trend) {
|
|
2195
|
+
const labels = trend.map(d => d.date.slice(5));
|
|
2196
|
+
const total = trend.map(d => d.total);
|
|
2197
|
+
const obeyed = trend.map(d => d.obeyed);
|
|
2198
|
+
const refused = trend.map(d => d.refused);
|
|
2199
|
+
const latency = trend.map(d => d.avgExecutionMs ?? 0);
|
|
2200
|
+
|
|
2201
|
+
if (charts.routingTrend) charts.routingTrend.destroy();
|
|
2202
|
+
if (charts.routingLatency) charts.routingLatency.destroy();
|
|
2203
|
+
|
|
2204
|
+
const trendCtx = document.getElementById('chart-routing-trend');
|
|
2205
|
+
if (trendCtx) {
|
|
2206
|
+
charts.routingTrend = new Chart(trendCtx, {
|
|
2207
|
+
type: 'line',
|
|
2208
|
+
data: {
|
|
2209
|
+
labels,
|
|
2210
|
+
datasets: [
|
|
2211
|
+
{ label: '总路由', data: total, borderColor: '#4f46e5', backgroundColor: 'rgba(79,70,229,0.12)', tension: 0.25 },
|
|
2212
|
+
{ label: '遵守', data: obeyed, borderColor: '#16a34a', backgroundColor: 'rgba(22,163,74,0.12)', tension: 0.25 },
|
|
2213
|
+
{ label: '违抗', data: refused, borderColor: '#dc2626', backgroundColor: 'rgba(220,38,38,0.12)', tension: 0.25 },
|
|
2214
|
+
],
|
|
2215
|
+
},
|
|
2216
|
+
options: { responsive: true, maintainAspectRatio: false },
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
const latencyCtx = document.getElementById('chart-routing-latency');
|
|
2221
|
+
if (latencyCtx) {
|
|
2222
|
+
charts.routingLatency = new Chart(latencyCtx, {
|
|
2223
|
+
type: 'bar',
|
|
2224
|
+
data: {
|
|
2225
|
+
labels,
|
|
2226
|
+
datasets: [{ label: '平均真实执行耗时(ms)', data: latency, backgroundColor: 'rgba(37,99,235,0.65)' }],
|
|
2227
|
+
},
|
|
2228
|
+
options: { responsive: true, maintainAspectRatio: false },
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
async function loadRoutingAIOptimization() {
|
|
2234
|
+
const container = document.getElementById('routing-ai-optimization');
|
|
2235
|
+
container.innerHTML = loading();
|
|
2236
|
+
try {
|
|
2237
|
+
const minAttempts = parseInt(document.getElementById('ai-opt-min-attempts').value) || 10;
|
|
2238
|
+
const r = await fetch(API + '/api/routing/ai-optimization?window=' + routingWindow() + '&minAttempts=' + minAttempts);
|
|
2239
|
+
if (!r.ok) {
|
|
2240
|
+
const err = await r.json().catch(() => ({ error: r.statusText }));
|
|
2241
|
+
throw new Error(err.error || r.statusText);
|
|
2242
|
+
}
|
|
2243
|
+
const data = await r.json();
|
|
2244
|
+
currentAIOptimizationData = data;
|
|
2245
|
+
|
|
2246
|
+
const priorities = (data.priorities || []).length === 0
|
|
2247
|
+
? empty('暂无优先级建议')
|
|
2248
|
+
: (data.priorities || []).map((p, idx) => `
|
|
2249
|
+
<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:0.75rem;margin-bottom:0.5rem">
|
|
2250
|
+
<div style="display:flex;justify-content:space-between;gap:0.5rem;align-items:center;margin-bottom:0.35rem">
|
|
2251
|
+
<strong>#${idx + 1} ${escapeHtml(p.area || '优化项')}</strong>
|
|
2252
|
+
<span class="badge badge-info">${Math.round((p.confidence || 0) * 100)}%</span>
|
|
2253
|
+
</div>
|
|
2254
|
+
<div style="font-size:0.875rem;margin-bottom:0.25rem"><strong>发现:</strong>${escapeHtml(p.finding || '')}</div>
|
|
2255
|
+
<div style="font-size:0.875rem"><strong>影响:</strong>${escapeHtml(p.impact || '')}</div>
|
|
2256
|
+
</div>
|
|
2257
|
+
`).join('');
|
|
2258
|
+
|
|
2259
|
+
const changes = (data.suggestedChanges || []).length === 0
|
|
2260
|
+
? empty('暂无可执行改动建议')
|
|
2261
|
+
: (data.suggestedChanges || []).map((c, idx) => `
|
|
2262
|
+
<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:0.75rem;margin-bottom:0.5rem">
|
|
2263
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.35rem">
|
|
2264
|
+
<strong>#${idx + 1} ${escapeHtml(c.targetType || '')}: ${escapeHtml(c.targetName || '—')}</strong>
|
|
2265
|
+
<div style="display:flex;gap:0.5rem">
|
|
2266
|
+
<span class="badge ${c.targetType === 'routing_rule' ? 'badge-warn' : 'badge-allow'}">${escapeHtml(c.targetType || '—')}</span>
|
|
2267
|
+
<button class="btn" style="font-size:0.75rem;padding:0.25rem 0.5rem" onclick="previewPatch(${idx})">预览 Patch</button>
|
|
2268
|
+
</div>
|
|
2269
|
+
</div>
|
|
2270
|
+
<div style="font-size:0.875rem;margin-bottom:0.25rem"><strong>建议:</strong>${escapeHtml(c.recommendation || '')}</div>
|
|
2271
|
+
<div style="font-size:0.875rem;margin-bottom:0.25rem"><strong>理由:</strong>${escapeHtml(c.rationale || '')}</div>
|
|
2272
|
+
<div style="font-size:0.875rem;color:var(--text-dim)"><strong>收益:</strong>${escapeHtml(c.expectedBenefit || '')}</div>
|
|
2273
|
+
</div>
|
|
2274
|
+
`).join('');
|
|
2275
|
+
|
|
2276
|
+
const evidence = data.evidence || {};
|
|
2277
|
+
container.innerHTML = `
|
|
2278
|
+
<div class="cards" style="margin-bottom:1rem">
|
|
2279
|
+
${card('高违抗率 Agent', (evidence.highRefusalAgents || []).length)}
|
|
2280
|
+
${card('违抗模式', (evidence.topViolations || []).length)}
|
|
2281
|
+
${card('规则建议', (evidence.ruleRecommendations || []).length)}
|
|
2282
|
+
</div>
|
|
2283
|
+
<div class="grid-2">
|
|
2284
|
+
<div class="panel">
|
|
2285
|
+
<div class="panel-header"><span class="panel-title">AI 摘要</span></div>
|
|
2286
|
+
<div class="panel-body" style="white-space:pre-wrap;line-height:1.6">${escapeHtml(data.summary || '—')}</div>
|
|
2287
|
+
</div>
|
|
2288
|
+
<div class="panel">
|
|
2289
|
+
<div class="panel-header"><span class="panel-title">优先级</span></div>
|
|
2290
|
+
<div class="panel-body">${priorities}</div>
|
|
2291
|
+
</div>
|
|
2292
|
+
</div>
|
|
2293
|
+
<div class="grid-2" style="margin-top:1rem">
|
|
2294
|
+
<div class="panel">
|
|
2295
|
+
<div class="panel-header"><span class="panel-title">可执行改动建议</span></div>
|
|
2296
|
+
<div class="panel-body">${changes}</div>
|
|
2297
|
+
</div>
|
|
2298
|
+
<div class="panel">
|
|
2299
|
+
<div class="panel-header"><span class="panel-title">证据</span></div>
|
|
2300
|
+
<div class="panel-body">
|
|
2301
|
+
<details open style="margin-bottom:0.5rem"><summary style="cursor:pointer;font-weight:600">高违抗率 Agent</summary><pre style="white-space:pre-wrap;font-size:0.8rem;background:var(--bg-secondary);padding:0.5rem;border-radius:4px">${escapeHtml(JSON.stringify(evidence.highRefusalAgents || [], null, 2))}</pre></details>
|
|
2302
|
+
<details style="margin-bottom:0.5rem"><summary style="cursor:pointer;font-weight:600">违抗模式</summary><pre style="white-space:pre-wrap;font-size:0.8rem;background:var(--bg-secondary);padding:0.5rem;border-radius:4px">${escapeHtml(JSON.stringify(evidence.topViolations || [], null, 2))}</pre></details>
|
|
2303
|
+
<details><summary style="cursor:pointer;font-weight:600">规则建议</summary><pre style="white-space:pre-wrap;font-size:0.8rem;background:var(--bg-secondary);padding:0.5rem;border-radius:4px">${escapeHtml(JSON.stringify(evidence.ruleRecommendations || [], null, 2))}</pre></details>
|
|
2304
|
+
</div>
|
|
2305
|
+
</div>
|
|
2306
|
+
</div>
|
|
2307
|
+
`;
|
|
2308
|
+
} catch (err) {
|
|
2309
|
+
container.innerHTML = empty('加载失败: ' + err.message);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// Patch preview and apply functions
|
|
2314
|
+
async function previewPatch(idx) {
|
|
2315
|
+
if (!currentAIOptimizationData || !currentAIOptimizationData.suggestedChanges) {
|
|
2316
|
+
alert('请先加载 AI 优化建议');
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
const change = currentAIOptimizationData.suggestedChanges[idx];
|
|
2320
|
+
if (!change) {
|
|
2321
|
+
alert('建议不存在');
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
const drawerTitle = document.getElementById('drawer-title');
|
|
2326
|
+
const drawerBody = document.getElementById('drawer-body');
|
|
2327
|
+
drawerTitle.textContent = 'Patch 预览';
|
|
2328
|
+
drawerBody.innerHTML = '<div style="text-align:center;padding:2rem">正在生成 patch...</div>';
|
|
2329
|
+
openDrawer();
|
|
2330
|
+
|
|
2331
|
+
try {
|
|
2332
|
+
const r = await fetch(API + '/api/patch/preview', {
|
|
2333
|
+
method: 'POST',
|
|
2334
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2335
|
+
body: JSON.stringify({
|
|
2336
|
+
targetType: change.targetType,
|
|
2337
|
+
targetName: change.targetName,
|
|
2338
|
+
recommendation: change.recommendation,
|
|
2339
|
+
rationale: change.rationale,
|
|
2340
|
+
}),
|
|
2341
|
+
});
|
|
2342
|
+
if (!r.ok) {
|
|
2343
|
+
const err = await r.json().catch(() => ({ error: r.statusText }));
|
|
2344
|
+
throw new Error(err.error || r.statusText);
|
|
2345
|
+
}
|
|
2346
|
+
const preview = await r.json();
|
|
2347
|
+
currentPatchPreview = preview;
|
|
2348
|
+
|
|
2349
|
+
const riskBadge = preview.risk === 'low' ? '<span class="badge badge-allow">低风险</span>'
|
|
2350
|
+
: preview.risk === 'high' ? '<span class="badge badge-block">高风险</span>'
|
|
2351
|
+
: '<span class="badge badge-warn">中风险</span>';
|
|
2352
|
+
|
|
2353
|
+
drawerBody.innerHTML = `
|
|
2354
|
+
<div style="margin-bottom:1rem">
|
|
2355
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem">
|
|
2356
|
+
<strong>${preview.targetType}: ${preview.targetName}</strong>
|
|
2357
|
+
${riskBadge}
|
|
2358
|
+
</div>
|
|
2359
|
+
<div style="font-size:0.875rem;color:var(--text-dim);margin-bottom:0.5rem">${escapeHtml(preview.summary)}</div>
|
|
2360
|
+
<div style="font-size:0.875rem;color:var(--text-dim)">文件: ${escapeHtml(preview.filePath)}</div>
|
|
2361
|
+
</div>
|
|
2362
|
+
<div style="margin-bottom:1rem">
|
|
2363
|
+
<strong>变更前:</strong>
|
|
2364
|
+
<pre style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);max-height:300px;overflow:auto;font-size:0.75rem">${escapeHtml(preview.before)}</pre>
|
|
2365
|
+
</div>
|
|
2366
|
+
<div style="margin-bottom:1rem">
|
|
2367
|
+
<strong>变更后:</strong>
|
|
2368
|
+
<pre style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);max-height:300px;overflow:auto;font-size:0.75rem">${escapeHtml(preview.after)}</pre>
|
|
2369
|
+
</div>
|
|
2370
|
+
<div style="display:flex;gap:0.5rem;justify-content:flex-end">
|
|
2371
|
+
<button class="btn" onclick="closeDrawer()">取消</button>
|
|
2372
|
+
<button class="btn btn-primary" onclick="applyPatch()">应用 Patch</button>
|
|
2373
|
+
</div>
|
|
2374
|
+
`;
|
|
2375
|
+
} catch (err) {
|
|
2376
|
+
drawerBody.innerHTML = `<div style="color:var(--red);padding:1rem">预览失败: ${escapeHtml(err.message)}</div>`;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
async function applyPatch() {
|
|
2381
|
+
if (!currentPatchPreview) {
|
|
2382
|
+
alert('请先预览 patch');
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
if (!confirm(`确定要应用此 patch 吗?\n\n目标: ${currentPatchPreview.targetType}/${currentPatchPreview.targetName}\n\n系统会自动备份当前版本。`)) {
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
try {
|
|
2391
|
+
const r = await fetch(API + '/api/patch/apply', {
|
|
2392
|
+
method: 'POST',
|
|
2393
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2394
|
+
body: JSON.stringify({
|
|
2395
|
+
targetType: currentPatchPreview.targetType,
|
|
2396
|
+
targetName: currentPatchPreview.targetName,
|
|
2397
|
+
afterContent: currentPatchPreview.after,
|
|
2398
|
+
}),
|
|
2399
|
+
});
|
|
2400
|
+
if (!r.ok) {
|
|
2401
|
+
const err = await r.json().catch(() => ({ error: r.statusText }));
|
|
2402
|
+
throw new Error(err.error || r.statusText);
|
|
2403
|
+
}
|
|
2404
|
+
const result = await r.json();
|
|
2405
|
+
alert(`Patch 应用成功!\n\n备份路径: ${result.backupPath}\n\n请刷新相关页面查看结果。`);
|
|
2406
|
+
closeDrawer();
|
|
2407
|
+
const targetType = currentPatchPreview.targetType;
|
|
2408
|
+
currentPatchPreview = null;
|
|
2409
|
+
|
|
2410
|
+
// Refresh relevant pages
|
|
2411
|
+
if (targetType === 'agent') loadAgents();
|
|
2412
|
+
else if (targetType === 'skill') loadSkills();
|
|
2413
|
+
else if (targetType === 'routing_rule') loadRoutingConfig();
|
|
2414
|
+
} catch (err) {
|
|
2415
|
+
alert('应用失败: ' + err.message);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
|
|
1635
2419
|
async function loadRoutingEvents() {
|
|
1636
2420
|
try {
|
|
1637
2421
|
const obeyed = document.getElementById('routing-filter-obeyed')?.value || '';
|