@winspan/claude-forge 8.19.0 → 8.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +35 -29
- 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 +711 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/static/index.html +566 -1
- package/package.json +1 -1
|
@@ -541,6 +541,12 @@
|
|
|
541
541
|
</select>
|
|
542
542
|
<button class="btn" onclick="loadExecutionTraces()">↻ 刷新</button>
|
|
543
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>
|
|
544
550
|
<div class="panel">
|
|
545
551
|
<div class="panel-body">
|
|
546
552
|
<div id="trace-list"></div>
|
|
@@ -604,6 +610,8 @@
|
|
|
604
610
|
<button class="btn" id="routing-tab-overview" onclick="routingTab('overview')" style="border-radius:0;border:none">总览</button>
|
|
605
611
|
<button class="btn" id="routing-tab-events" onclick="routingTab('events')" style="border-radius:0;border:none">决策明细</button>
|
|
606
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>
|
|
607
615
|
<button class="btn" id="routing-tab-editor" onclick="routingTab('editor')" style="border-radius:0;border:none">路由调优</button>
|
|
608
616
|
<button class="btn" id="routing-tab-experiments" onclick="routingTab('experiments')" style="border-radius:0;border:none">A/B 测试</button>
|
|
609
617
|
<button class="btn" id="routing-tab-recommendations" onclick="routingTab('recommendations')" style="border-radius:0;border:none">规则推荐</button>
|
|
@@ -659,6 +667,57 @@
|
|
|
659
667
|
</div>
|
|
660
668
|
</div>
|
|
661
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
|
+
|
|
662
721
|
<!-- Editor subpanel (Phase 3 Feature 2, upgraded to Monaco in Phase 4 Feature 1) -->
|
|
663
722
|
<div id="routing-sub-editor" style="display:none">
|
|
664
723
|
<div class="panel">
|
|
@@ -767,6 +826,10 @@
|
|
|
767
826
|
const API = '';
|
|
768
827
|
let allEvents = [], allSessions = [], allInjections = [];
|
|
769
828
|
let charts = {};
|
|
829
|
+
let traceStream = null;
|
|
830
|
+
let traceRefreshTimer = null;
|
|
831
|
+
let currentPatchPreview = null;
|
|
832
|
+
let currentAIOptimizationData = null;
|
|
770
833
|
|
|
771
834
|
// === Navigation ===
|
|
772
835
|
function nav(page) {
|
|
@@ -1176,7 +1239,7 @@ let routingCurrentTab = 'overview';
|
|
|
1176
1239
|
|
|
1177
1240
|
function routingTab(tab) {
|
|
1178
1241
|
routingCurrentTab = tab;
|
|
1179
|
-
['overview', 'events', 'refusals', 'editor', 'experiments', 'recommendations'].forEach(t => {
|
|
1242
|
+
['overview', 'events', 'refusals', 'performance', 'ai-optimization', 'editor', 'experiments', 'recommendations'].forEach(t => {
|
|
1180
1243
|
const btn = document.getElementById('routing-tab-' + t);
|
|
1181
1244
|
const sub = document.getElementById('routing-sub-' + t);
|
|
1182
1245
|
if (btn) btn.classList.toggle('active', t === tab);
|
|
@@ -1197,6 +1260,8 @@ async function loadRouting() {
|
|
|
1197
1260
|
if (routingCurrentTab === 'overview') return loadRoutingOverview();
|
|
1198
1261
|
if (routingCurrentTab === 'events') return loadRoutingEvents();
|
|
1199
1262
|
if (routingCurrentTab === 'refusals') return loadRoutingRefusals();
|
|
1263
|
+
if (routingCurrentTab === 'performance') return loadRoutingPerformance();
|
|
1264
|
+
if (routingCurrentTab === 'ai-optimization') return loadRoutingAIOptimization();
|
|
1200
1265
|
if (routingCurrentTab === 'editor') return loadRoutingConfig();
|
|
1201
1266
|
if (routingCurrentTab === 'experiments') {
|
|
1202
1267
|
await loadExperimentsConfig();
|
|
@@ -1434,6 +1499,9 @@ async function viewAgent(name) {
|
|
|
1434
1499
|
${data.tools ? `<span class="badge badge-allow">${data.tools}</span>` : ''}
|
|
1435
1500
|
</div>
|
|
1436
1501
|
</div>
|
|
1502
|
+
<div style="margin-bottom:1rem;display:flex;gap:0.5rem">
|
|
1503
|
+
<button class="btn" onclick="viewAgentVersions('${name}')">📜 版本历史</button>
|
|
1504
|
+
</div>
|
|
1437
1505
|
<div style="background:var(--bg-secondary);padding:1rem;border-radius:var(--radius-sm);max-height:60vh;overflow-y:auto">
|
|
1438
1506
|
<pre style="margin:0;white-space:pre-wrap;font-size:0.875rem">${escapeHtml(data.content)}</pre>
|
|
1439
1507
|
</div>
|
|
@@ -1517,6 +1585,87 @@ async function loadSkills() {
|
|
|
1517
1585
|
}
|
|
1518
1586
|
}
|
|
1519
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
|
+
|
|
1520
1669
|
async function viewSkill(name) {
|
|
1521
1670
|
try {
|
|
1522
1671
|
const r = await fetch(API + `/api/skills/${name}`);
|
|
@@ -1532,6 +1681,9 @@ async function viewSkill(name) {
|
|
|
1532
1681
|
<span class="badge ${data.source === 'official' ? 'badge-allow' : 'badge-warn'}">${data.source === 'official' ? '官方' : '用户'}</span>
|
|
1533
1682
|
</div>
|
|
1534
1683
|
</div>
|
|
1684
|
+
<div style="margin-bottom:1rem;display:flex;gap:0.5rem">
|
|
1685
|
+
<button class="btn" onclick="viewSkillVersions('${name}')">📜 版本历史</button>
|
|
1686
|
+
</div>
|
|
1535
1687
|
<div style="background:var(--bg-secondary);padding:1rem;border-radius:var(--radius-sm);max-height:60vh;overflow-y:auto">
|
|
1536
1688
|
<pre style="margin:0;white-space:pre-wrap;font-size:0.875rem">${escapeHtml(data.content)}</pre>
|
|
1537
1689
|
</div>
|
|
@@ -1542,6 +1694,87 @@ async function viewSkill(name) {
|
|
|
1542
1694
|
}
|
|
1543
1695
|
}
|
|
1544
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
|
+
|
|
1545
1778
|
async function editSkill(name) {
|
|
1546
1779
|
try {
|
|
1547
1780
|
const r = await fetch(API + `/api/skills/${name}`);
|
|
@@ -1625,11 +1858,47 @@ async function loadExecutionTraces() {
|
|
|
1625
1858
|
).join('');
|
|
1626
1859
|
|
|
1627
1860
|
renderTraces();
|
|
1861
|
+
ensureTraceStream();
|
|
1628
1862
|
} catch (err) {
|
|
1629
1863
|
console.error('Failed to load traces:', err);
|
|
1630
1864
|
}
|
|
1631
1865
|
}
|
|
1632
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
|
+
|
|
1633
1902
|
function renderTraces() {
|
|
1634
1903
|
const searchTerm = document.getElementById('trace-search').value.toLowerCase();
|
|
1635
1904
|
const filtered = allTraces.filter(t =>
|
|
@@ -1668,6 +1937,14 @@ function renderTraces() {
|
|
|
1668
1937
|
? `<span class="badge badge-allow">AI 分类 (${t.classificationMs}ms)</span>`
|
|
1669
1938
|
: '';
|
|
1670
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
|
+
|
|
1671
1948
|
return `
|
|
1672
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"
|
|
1673
1950
|
onclick="viewTraceDetails('${t.sessionId}')"
|
|
@@ -1687,6 +1964,8 @@ function renderTraces() {
|
|
|
1687
1964
|
${intentBadge}
|
|
1688
1965
|
${firstToolBadge}
|
|
1689
1966
|
${classificationBadge}
|
|
1967
|
+
${executionBadge}
|
|
1968
|
+
${completionBadge}
|
|
1690
1969
|
${t.taskChain.length > 0 ? `<span class="badge badge-info">子 Agent: ${t.taskChain.join(' → ')}</span>` : ''}
|
|
1691
1970
|
</div>
|
|
1692
1971
|
</div>
|
|
@@ -1743,10 +2022,23 @@ async function viewTraceDetails(sessionId) {
|
|
|
1743
2022
|
<div><strong>路由类型:</strong> ${routing.routedToType || '无'}</div>
|
|
1744
2023
|
<div><strong>路由目标:</strong> ${routing.routedToName || '—'}</div>
|
|
1745
2024
|
<div><strong>执行状态:</strong> ${obeyedText}</div>
|
|
2025
|
+
<div><strong>完成来源:</strong> ${routing.completionReason || '—'}</div>
|
|
1746
2026
|
${routing.refusalReason ? `<div style="margin-top:0.5rem;color:var(--red)"><strong>违抗原因:</strong> ${escapeHtml(routing.refusalReason)}</div>` : ''}
|
|
1747
2027
|
</div>
|
|
1748
2028
|
</div>
|
|
1749
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
|
+
|
|
1750
2042
|
<div style="margin-bottom:1.5rem">
|
|
1751
2043
|
<h4 style="margin-bottom:0.5rem">实际执行</h4>
|
|
1752
2044
|
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:var(--radius-sm);font-size:0.875rem">
|
|
@@ -1851,6 +2143,279 @@ function card(label, value, color) {
|
|
|
1851
2143
|
+ '</div>';
|
|
1852
2144
|
}
|
|
1853
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
|
+
|
|
1854
2419
|
async function loadRoutingEvents() {
|
|
1855
2420
|
try {
|
|
1856
2421
|
const obeyed = document.getElementById('routing-filter-obeyed')?.value || '';
|