oh-my-knowledge 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/assets/agent-skills/omk/SKILL.md +3 -3
- package/dist/assets/agent-skills/omk/references/commands.md +13 -6
- package/dist/cli/commands/doctor.d.ts +3 -2
- package/dist/cli/commands/doctor.js +66 -63
- package/dist/cli/lib/cmd-flags.d.ts +3 -2
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +0 -4
- package/dist/doctor/endpoint-rule.js +1 -1
- package/dist/doctor/health/composer.js +162 -50
- package/dist/doctor/health/consensus.d.ts +43 -0
- package/dist/doctor/health/consensus.js +224 -0
- package/dist/doctor/health/dimension-spec.d.ts +6 -0
- package/dist/doctor/health/register.d.ts +0 -1
- package/dist/doctor/health/register.js +0 -1
- package/dist/doctor/index.js +3 -0
- package/dist/doctor/messages.d.ts +1 -1
- package/dist/doctor/messages.js +8 -0
- package/dist/doctor/rules.d.ts +4 -2
- package/dist/doctor/rules.js +4 -2
- package/dist/executors/claude-cli.js +1 -1
- package/dist/observability/skill-chain-advisories.js +2 -2
- package/dist/renderer/doctor-detail-renderer.js +30 -2
- package/dist/renderer/report-shell.d.ts +1 -1
- package/dist/renderer/report-shell.js +5 -0
- package/dist/renderer/skill-detail-renderer.js +844 -100
- package/dist/server/skill-index.js +37 -1
- package/dist/shared/llm-prompts/registry.js +2 -1
- package/dist/shared/llm-prompts/skill-health-merge.d.ts +22 -0
- package/dist/shared/llm-prompts/skill-health-merge.js +79 -0
- package/dist/shared/llm-prompts/skill-health.js +1 -1
- package/dist/types/doctor.d.ts +32 -8
- package/dist/types/skill-index.d.ts +10 -0
- package/package.json +4 -3
|
@@ -400,11 +400,21 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
400
400
|
/* Skill Map:以 SKILL.md 为根的轻量知识图,不暴露完整 graph JSON 拓扑 */
|
|
401
401
|
.sm-sect { border-left-color:#4f46e5 }
|
|
402
402
|
.sm-body { display:flex;flex-direction:column;gap:10px }
|
|
403
|
+
.sm-evidence-strip { display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px }
|
|
404
|
+
.sm-evidence-item { min-height:44px;padding:8px 10px;border:1px solid #e4e8f1;border-radius:8px;background:#fff;display:flex;flex-direction:column;justify-content:center;gap:2px }
|
|
405
|
+
.sm-evidence-item--risk { border-color:rgba(220,38,38,.34);background:rgba(220,38,38,.055) }
|
|
406
|
+
.sm-evidence-item--action { border-color:rgba(245,158,11,.42);background:rgba(245,158,11,.075) }
|
|
407
|
+
.sm-evidence-k { font-size:10px;color:#8792a4;font-weight:700;line-height:1;text-transform:uppercase;font-family:"SF Mono",Menlo,monospace }
|
|
408
|
+
.sm-evidence-v { font-size:12.5px;color:#182033;font-weight:700;line-height:1.35;word-break:break-word }
|
|
403
409
|
.sm-toolbar { display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:8px 10px;border:1px solid #e4e8f1;border-radius:8px;background:#fff }
|
|
404
410
|
.sm-tools,.sm-toggles { display:flex;align-items:center;gap:6px;flex-wrap:wrap }
|
|
405
411
|
.sm-tool { min-width:30px;height:28px;padding:0 9px;border:1px solid #dbe2ee;border-radius:6px;background:#fff;color:#384255;font-size:13px;font-weight:700;cursor:pointer }
|
|
406
412
|
.sm-tool:hover { background:#f7f8ff;border-color:#c7d2fe }
|
|
407
413
|
.sm-zoom-label { min-width:44px;text-align:center;font-size:11.5px;color:#637083;font-variant-numeric:tabular-nums }
|
|
414
|
+
.sm-view-switch { display:inline-flex;align-items:center;gap:3px;padding:3px;border:1px solid #dbe2ee;border-radius:8px;background:#f8fafc }
|
|
415
|
+
.sm-view { height:26px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:#637083;font-size:11.5px;font-weight:700;cursor:pointer }
|
|
416
|
+
.sm-view:hover { color:#384255;background:#fff }
|
|
417
|
+
.sm-view.is-active { color:#182033;background:#fff;box-shadow:0 1px 4px rgba(31,41,55,.08) }
|
|
408
418
|
.sm-toggle { display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 8px;border:1px solid #dbe2ee;border-radius:14px;background:#fff;color:#384255;font-size:11.5px;cursor:pointer;user-select:none }
|
|
409
419
|
.sm-toggle input { margin:0;accent-color:#4f46e5 }
|
|
410
420
|
.sm-canvas { position:relative;height:min(68vh,620px);min-height:520px;padding:0;background:
|
|
@@ -412,6 +422,13 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
412
422
|
linear-gradient(rgba(148,163,184,.075) 1px,transparent 1px),
|
|
413
423
|
linear-gradient(90deg,rgba(148,163,184,.06) 1px,transparent 1px),
|
|
414
424
|
linear-gradient(#fff,#fbfcff);background-size:auto,28px 28px,28px 28px,auto;background-position:0 0;border:1px solid #e4e8f1;border-radius:8px;overflow:auto;overscroll-behavior:contain }
|
|
425
|
+
.sm-body[data-sm-view="boundary"] .sm-canvas { background:
|
|
426
|
+
linear-gradient(90deg,rgba(31,157,99,.052) 0%,rgba(31,157,99,.025) 42%,transparent 42%,transparent 100%),
|
|
427
|
+
linear-gradient(rgba(148,163,184,.07) 1px,transparent 1px),
|
|
428
|
+
linear-gradient(90deg,rgba(148,163,184,.055) 1px,transparent 1px),
|
|
429
|
+
linear-gradient(#fff,#fbfcff);background-size:auto,28px 28px,28px 28px,auto;background-position:0 0 }
|
|
430
|
+
.sm-body[data-sm-view="boundary"] [data-sm-layer="measurement"],
|
|
431
|
+
.sm-body[data-sm-view="boundary"] [data-sm-evidence-edge="1"] { display:none !important }
|
|
415
432
|
.sm-graph-stage { position:relative;width:1100px;height:560px }
|
|
416
433
|
.sm-graph { position:absolute;left:0;top:0;width:1100px;height:560px;transform-origin:0 0;transition:transform .12s ease }
|
|
417
434
|
.sm-graph [hidden] { display:none !important }
|
|
@@ -419,6 +436,11 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
419
436
|
.sm-edge { fill:none;stroke:#d8e0ec;stroke-width:1.45;stroke-linecap:round;opacity:.86;vector-effect:non-scaling-stroke }
|
|
420
437
|
.sm-edge--definition { stroke:#a9cfc4 }
|
|
421
438
|
.sm-edge--measurement { stroke:#bbc7ff }
|
|
439
|
+
.sm-edge--coverage { stroke:#f59e0b;stroke-width:2;stroke-dasharray:5 5;opacity:.9 }
|
|
440
|
+
.sm-edge--coverage-failed { stroke:#dc2626;stroke-width:2.2;opacity:.92 }
|
|
441
|
+
.sm-zone-label { position:absolute;top:16px;z-index:1;font-size:10.5px;font-weight:800;letter-spacing:0;text-transform:uppercase;color:#8792a4;font-family:"SF Mono",Menlo,monospace;background:rgba(255,255,255,.82);border:1px solid #e4e8f1;border-radius:12px;padding:4px 9px;pointer-events:none }
|
|
442
|
+
.sm-zone-label--definition { left:36px;color:#2f7d68;border-color:rgba(47,125,104,.22);background:rgba(244,251,248,.86) }
|
|
443
|
+
.sm-zone-label--measurement { right:36px;color:#4f46e5;border-color:rgba(79,70,229,.22);background:rgba(247,248,255,.86) }
|
|
422
444
|
.sm-node { position:absolute;z-index:1;box-sizing:border-box;transform:translate(-50%,-50%);width:148px;min-height:54px;padding:8px 10px;border:1px solid #d8deea;border-radius:7px;background:rgba(255,255,255,.96);box-shadow:0 8px 20px rgba(31,41,55,6%);display:flex;flex-direction:column;justify-content:center;gap:4px;text-align:center;backdrop-filter:blur(4px) }
|
|
423
445
|
.sm-node[data-sm-draggable="1"] { cursor:grab;touch-action:none;user-select:none }
|
|
424
446
|
.sm-node[data-sm-draggable="1"].is-dragging { cursor:grabbing;z-index:3;box-shadow:0 16px 34px rgba(31,41,55,14%) }
|
|
@@ -426,7 +448,11 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
426
448
|
.sm-node[data-sm-draggable="1"][data-sm-more-toggle] { cursor:grab }
|
|
427
449
|
.sm-node[data-sm-draggable="1"][data-sm-more-toggle].is-dragging { cursor:grabbing }
|
|
428
450
|
.sm-node[data-sm-more-toggle]:hover,.sm-node[data-sm-more-toggle]:focus-visible { border-color:#9fbfb5;background:#f1fbf7;outline:none }
|
|
451
|
+
.sm-node:hover,.sm-node:focus-visible,.sm-node.is-selected { border-color:#4f46e5;outline:3px solid rgba(79,70,229,.12);outline-offset:2px }
|
|
452
|
+
.sm-node.is-selected { z-index:2;box-shadow:0 14px 30px rgba(79,70,229,13%) }
|
|
453
|
+
.sm-node.is-related { z-index:2;outline:3px solid rgba(245,158,11,.14);outline-offset:2px;box-shadow:0 12px 26px rgba(245,158,11,.12) }
|
|
429
454
|
.sm-node--skill { width:172px;min-height:86px;border-color:#4f46e5;background:linear-gradient(180deg,#fff 0%,#f7f8ff 100%);box-shadow:0 16px 34px rgba(79,70,229,14%);outline:4px solid rgba(79,70,229,.07) }
|
|
455
|
+
.sm-node--skill.is-selected { outline-color:rgba(79,70,229,.15) }
|
|
430
456
|
.sm-node--definition { border-color:#b7d8cf;background:#f4fbf8 }
|
|
431
457
|
.sm-node--measurement { border-color:#c7d2fe;background:#f7f8ff }
|
|
432
458
|
.sm-node--ok { border-color:rgba(31,157,99,.38);background:rgba(31,157,99,.06) }
|
|
@@ -440,6 +466,18 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
440
466
|
.sm-node-meta { font-size:10.5px;color:#637083;line-height:1.35;word-break:break-word }
|
|
441
467
|
.sm-node--skill .sm-node-meta { font-size:11.5px }
|
|
442
468
|
.sm-node-kind { font-size:9.5px;text-transform:uppercase;letter-spacing:0;color:#8792a4;font-family:"SF Mono",Menlo,monospace;line-height:1 }
|
|
469
|
+
.sm-node--group { width:164px;min-height:66px;border-color:rgba(47,125,104,.42);background:linear-gradient(180deg,#fff 0%,#f4fbf8 100%);box-shadow:0 12px 28px rgba(47,125,104,.1) }
|
|
470
|
+
.sm-node--group .sm-node-title { font-size:13px }
|
|
471
|
+
.sm-node-toggle-icon { position:absolute;right:7px;top:7px;width:17px;height:17px;border:1px solid rgba(47,125,104,.24);border-radius:50%;background:rgba(255,255,255,.72);box-shadow:0 4px 10px rgba(47,125,104,.06);color:#2f7d68;display:flex;align-items:center;justify-content:center;transition:background .14s ease,border-color .14s ease,box-shadow .14s ease }
|
|
472
|
+
.sm-node-toggle-icon::before { content:'';width:5px;height:5px;border-right:1.8px solid currentColor;border-bottom:1.8px solid currentColor;transform:translateY(-1px) rotate(45deg);transition:transform .14s ease }
|
|
473
|
+
.sm-node[data-sm-more-toggle]:hover .sm-node-toggle-icon,.sm-node[data-sm-more-toggle]:focus-visible .sm-node-toggle-icon { background:#fff;border-color:rgba(47,125,104,.42);box-shadow:0 5px 12px rgba(47,125,104,.11) }
|
|
474
|
+
.sm-node[data-sm-more-toggle][aria-expanded="true"] .sm-node-toggle-icon { background:rgba(47,125,104,.1);border-color:rgba(47,125,104,.34);box-shadow:0 4px 10px rgba(47,125,104,.08) }
|
|
475
|
+
.sm-node[data-sm-more-toggle][aria-expanded="true"] .sm-node-toggle-icon::before { transform:translateY(1px) rotate(225deg) }
|
|
476
|
+
.sm-node-signal { align-self:center;max-width:100%;padding:1px 6px;border-radius:9px;font-size:9.5px;font-weight:700;line-height:1.25;color:#637083;background:#eef2f7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis }
|
|
477
|
+
.sm-node-signal--failed { color:#dc2626;background:rgba(220,38,38,.12) }
|
|
478
|
+
.sm-node-signal--warning { color:#d97706;background:rgba(217,119,6,.13) }
|
|
479
|
+
.sm-node-signal--declared { color:#1f9d63;background:rgba(31,157,99,.13) }
|
|
480
|
+
.sm-node-signal--undeclared { color:#637083;background:#eef2f7;border:1px dashed #cbd5e1 }
|
|
443
481
|
.sm-stage-rail { display:grid;grid-template-columns:1fr auto 1fr auto 1fr;align-items:center;gap:8px;padding:10px 12px;border:1px solid #e4e8f1;border-radius:8px;background:#fff }
|
|
444
482
|
.sm-stage-card { min-height:50px;padding:9px 11px;border:1px solid #dbe2ee;border-radius:8px;background:#fbfcff;text-align:center;display:flex;flex-direction:column;justify-content:center;gap:3px }
|
|
445
483
|
.sm-stage-card--ok { border-color:rgba(31,157,99,.38);background:rgba(31,157,99,.06) }
|
|
@@ -449,6 +487,16 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
449
487
|
.sm-stage-k { font-size:10px;text-transform:uppercase;letter-spacing:0;color:#7b8798;font-family:"SF Mono",Menlo,monospace;line-height:1 }
|
|
450
488
|
.sm-stage-v { font-size:13px;font-weight:700;color:#182033;line-height:1.35 }
|
|
451
489
|
.sm-stage-arrow { color:#b8c5d8;font-size:18px;line-height:1;text-align:center }
|
|
490
|
+
.sm-detail { display:grid;grid-template-columns:minmax(180px,1.1fr) minmax(260px,2fr);gap:12px;align-items:start;padding:12px;border:1px solid #e4e8f1;border-radius:8px;background:linear-gradient(180deg,#fff 0%,#fbfcff 100%) }
|
|
491
|
+
.sm-detail-head { min-width:0 }
|
|
492
|
+
.sm-detail-kind { display:inline-flex;align-items:center;min-height:20px;padding:0 7px;border-radius:10px;background:#eef2ff;color:#4f46e5;font-size:10.5px;font-weight:700;line-height:1 }
|
|
493
|
+
.sm-detail-title { margin-top:8px;font-size:15px;font-weight:700;line-height:1.35;color:#182033;word-break:break-word }
|
|
494
|
+
.sm-detail-subtitle { margin-top:3px;font-size:12px;line-height:1.45;color:#637083;word-break:break-word }
|
|
495
|
+
.sm-detail-list { margin:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px }
|
|
496
|
+
.sm-detail-row { min-width:0;padding:8px 10px;border:1px solid #eef2f7;border-radius:7px;background:#fff }
|
|
497
|
+
.sm-detail-row[hidden] { display:none !important }
|
|
498
|
+
.sm-detail-row dt { margin:0 0 3px;font-size:10.5px;color:#8792a4;font-weight:600;line-height:1.2 }
|
|
499
|
+
.sm-detail-row dd { margin:0;font-size:12px;color:#384255;line-height:1.45;word-break:break-word }
|
|
452
500
|
.sm-card { border:1px solid #e4e8f1;border-radius:8px;background:#fff;overflow:hidden }
|
|
453
501
|
.sm-card > summary { cursor:pointer;list-style:none;padding:10px 12px;font-size:12.5px;font-weight:600;color:#4f46e5;user-select:none }
|
|
454
502
|
.sm-card > summary::-webkit-details-marker { display:none }
|
|
@@ -457,9 +505,12 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
457
505
|
.sm-card textarea { width:100%;min-height:260px;border:0;border-top:1px solid #e4e8f1;background:#fbfcff;padding:12px;font-family:"SF Mono",Menlo,monospace;font-size:11.5px;line-height:1.55;color:#182033;resize:vertical;box-sizing:border-box }
|
|
458
506
|
@media(max-width:900px){
|
|
459
507
|
.sm-toolbar { align-items:flex-start }
|
|
508
|
+
.sm-evidence-strip { grid-template-columns:1fr }
|
|
460
509
|
.sm-stage-rail { grid-template-columns:1fr;gap:6px }
|
|
461
510
|
.sm-stage-arrow { transform:rotate(90deg);font-size:16px }
|
|
462
511
|
.sm-canvas { height:620px;min-height:620px }
|
|
512
|
+
.sm-detail { grid-template-columns:1fr }
|
|
513
|
+
.sm-detail-list { grid-template-columns:1fr }
|
|
463
514
|
.sm-node { width:142px }
|
|
464
515
|
.sm-node--skill { width:168px }
|
|
465
516
|
}
|
|
@@ -846,6 +897,7 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
846
897
|
var stage = map.querySelector('[data-sm-graph-stage]');
|
|
847
898
|
var graph = map.querySelector('[data-sm-graph]');
|
|
848
899
|
var label = map.querySelector('[data-sm-zoom-label]');
|
|
900
|
+
var detail = map.querySelector('[data-sm-detail]');
|
|
849
901
|
if (!viewport || !stage || !graph) return;
|
|
850
902
|
var baseWidth = Number(map.getAttribute('data-sm-base-width')) || 1100;
|
|
851
903
|
var baseHeight = Number(map.getAttribute('data-sm-base-height')) || 560;
|
|
@@ -854,6 +906,8 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
854
906
|
var zoom = 1;
|
|
855
907
|
var dragState = null;
|
|
856
908
|
var suppressMoreClickUntil = 0;
|
|
909
|
+
var selectedNodeId = '';
|
|
910
|
+
var currentView = map.getAttribute('data-sm-view') || 'boundary';
|
|
857
911
|
|
|
858
912
|
function edgePoint(fromX, fromY, toX, toY, box){
|
|
859
913
|
var dx = toX - fromX;
|
|
@@ -902,6 +956,92 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
902
956
|
node.style.top = y + 'px';
|
|
903
957
|
}
|
|
904
958
|
|
|
959
|
+
function nodeViewPosition(node, view){
|
|
960
|
+
var prefix = view === 'evidence' ? 'data-sm-evidence' : 'data-sm-boundary';
|
|
961
|
+
var x = Number(node.getAttribute(prefix + '-x'));
|
|
962
|
+
var y = Number(node.getAttribute(prefix + '-y'));
|
|
963
|
+
if (Number.isFinite(x) && Number.isFinite(y)) return { x: x, y: y };
|
|
964
|
+
return {
|
|
965
|
+
x: Number(node.getAttribute('data-sm-origin-x')) || Number(node.getAttribute('data-sm-x')) || parseFloat(node.style.left) || 0,
|
|
966
|
+
y: Number(node.getAttribute('data-sm-origin-y')) || Number(node.getAttribute('data-sm-y')) || parseFloat(node.style.top) || 0
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function hideFilteredNode(node){
|
|
971
|
+
node.hidden = true;
|
|
972
|
+
node.toggleAttribute('hidden', true);
|
|
973
|
+
node.setAttribute('aria-hidden', 'true');
|
|
974
|
+
node.style.display = 'none';
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function revealNode(node, transient){
|
|
978
|
+
node.hidden = false;
|
|
979
|
+
node.toggleAttribute('hidden', false);
|
|
980
|
+
node.setAttribute('aria-hidden', 'false');
|
|
981
|
+
node.style.display = '';
|
|
982
|
+
node.setAttribute('data-sm-filter-hidden', '0');
|
|
983
|
+
if (transient) node.setAttribute('data-sm-evidence-revealed', '1');
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function detailText(node, attr, fallback){
|
|
987
|
+
var value = node ? node.getAttribute(attr) : '';
|
|
988
|
+
return value && value.trim() ? value : (fallback || '');
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function setDetailValue(key, value, required){
|
|
992
|
+
if (!detail) return;
|
|
993
|
+
var row = detail.querySelector('[data-sm-detail-row="' + key + '"]');
|
|
994
|
+
var valueEl = detail.querySelector('[data-sm-detail-value="' + key + '"]');
|
|
995
|
+
var hasValue = !!(value && value.trim());
|
|
996
|
+
if (valueEl) valueEl.textContent = hasValue ? value : '—';
|
|
997
|
+
if (row) {
|
|
998
|
+
var hidden = !hasValue && !required;
|
|
999
|
+
row.hidden = hidden;
|
|
1000
|
+
row.toggleAttribute('hidden', hidden);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function selectedMoreStatus(node){
|
|
1005
|
+
if (!node || !node.hasAttribute('data-sm-more-toggle')) return '';
|
|
1006
|
+
var attr = node.getAttribute('aria-expanded') === 'true'
|
|
1007
|
+
? 'data-sm-detail-expanded-status'
|
|
1008
|
+
: 'data-sm-detail-collapsed-status';
|
|
1009
|
+
return node.getAttribute(attr) || '';
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function setSelectedNode(node){
|
|
1013
|
+
if (!node || node.hidden) return;
|
|
1014
|
+
var nodeId = node.getAttribute('data-sm-node-id') || '';
|
|
1015
|
+
selectedNodeId = nodeId;
|
|
1016
|
+
graph.querySelectorAll('.sm-node.is-selected').forEach(function(item){
|
|
1017
|
+
item.classList.remove('is-selected');
|
|
1018
|
+
item.setAttribute('aria-pressed', 'false');
|
|
1019
|
+
});
|
|
1020
|
+
node.classList.add('is-selected');
|
|
1021
|
+
node.setAttribute('aria-pressed', 'true');
|
|
1022
|
+
if (!detail) return;
|
|
1023
|
+
var title = detailText(node, 'data-sm-detail-title', node.textContent || '');
|
|
1024
|
+
var subtitle = detailText(node, 'data-sm-detail-subtitle', '');
|
|
1025
|
+
var kind = detailText(node, 'data-sm-detail-kind', '');
|
|
1026
|
+
var kindEl = detail.querySelector('[data-sm-detail-kind-label]');
|
|
1027
|
+
var titleEl = detail.querySelector('[data-sm-detail-title]');
|
|
1028
|
+
var subtitleEl = detail.querySelector('[data-sm-detail-subtitle]');
|
|
1029
|
+
if (kindEl) kindEl.textContent = kind;
|
|
1030
|
+
if (titleEl) titleEl.textContent = title;
|
|
1031
|
+
if (subtitleEl) {
|
|
1032
|
+
subtitleEl.textContent = subtitle;
|
|
1033
|
+
subtitleEl.hidden = !subtitle;
|
|
1034
|
+
subtitleEl.toggleAttribute('hidden', !subtitle);
|
|
1035
|
+
}
|
|
1036
|
+
setDetailValue('scope', detailText(node, 'data-sm-detail-scope', ''), true);
|
|
1037
|
+
setDetailValue('source', detailText(node, 'data-sm-detail-source', ''), false);
|
|
1038
|
+
setDetailValue('coverage', detailText(node, 'data-sm-detail-coverage', ''), false);
|
|
1039
|
+
setDetailValue('status', selectedMoreStatus(node) || detailText(node, 'data-sm-detail-status', ''), false);
|
|
1040
|
+
setDetailValue('evidence', detailText(node, 'data-sm-detail-evidence', ''), false);
|
|
1041
|
+
setDetailValue('action', detailText(node, 'data-sm-detail-action', ''), false);
|
|
1042
|
+
updateEvidenceFocus();
|
|
1043
|
+
}
|
|
1044
|
+
|
|
905
1045
|
function updateEdgesForNode(nodeId, x, y){
|
|
906
1046
|
map.querySelectorAll('[data-sm-edge-to]').forEach(function(edge){
|
|
907
1047
|
if (edge.getAttribute('data-sm-edge-to') !== nodeId) return;
|
|
@@ -951,13 +1091,16 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
951
1091
|
}
|
|
952
1092
|
|
|
953
1093
|
function resetNodePositions(){
|
|
1094
|
+
applyViewPositions(currentView);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function applyViewPositions(view){
|
|
954
1098
|
graph.querySelectorAll('[data-sm-draggable="1"]').forEach(function(node){
|
|
955
1099
|
var nodeId = node.getAttribute('data-sm-node-id');
|
|
956
|
-
var
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (nodeId)
|
|
960
|
-
if (nodeId) updateEdgesFromNode(nodeId, originX, originY);
|
|
1100
|
+
var next = nodeViewPosition(node, view);
|
|
1101
|
+
setNodePosition(node, next.x, next.y);
|
|
1102
|
+
if (nodeId) updateEdgesForNode(nodeId, next.x, next.y);
|
|
1103
|
+
if (nodeId) updateEdgesFromNode(nodeId, next.x, next.y);
|
|
961
1104
|
});
|
|
962
1105
|
}
|
|
963
1106
|
|
|
@@ -989,24 +1132,60 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
989
1132
|
viewport.scrollTop = Math.max(0, scaledRootY - viewport.clientHeight / 2);
|
|
990
1133
|
}
|
|
991
1134
|
|
|
992
|
-
function layerEnabled(layer){
|
|
993
|
-
var input = map.querySelector('[data-sm-toggle-layer="' + layer + '"]');
|
|
994
|
-
return !input || input.checked;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
1135
|
function overflowGroupExpanded(group){
|
|
998
1136
|
if (!group) return true;
|
|
999
1137
|
var toggle = map.querySelector('[data-sm-more-toggle="' + group + '"]');
|
|
1000
1138
|
return !!toggle && toggle.getAttribute('aria-expanded') === 'true';
|
|
1001
1139
|
}
|
|
1002
1140
|
|
|
1141
|
+
function updateViewButtons(){
|
|
1142
|
+
map.setAttribute('data-sm-view', currentView);
|
|
1143
|
+
map.querySelectorAll('[data-sm-view-button]').forEach(function(button){
|
|
1144
|
+
var active = button.getAttribute('data-sm-view-button') === currentView;
|
|
1145
|
+
button.classList.toggle('is-active', active);
|
|
1146
|
+
button.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function updateEvidenceFocus(){
|
|
1151
|
+
if (!graph) return;
|
|
1152
|
+
graph.querySelectorAll('[data-sm-evidence-revealed="1"]').forEach(function(node){
|
|
1153
|
+
node.removeAttribute('data-sm-evidence-revealed');
|
|
1154
|
+
if (node.getAttribute('data-sm-layer') === 'definition' && currentView === 'evidence') hideFilteredNode(node);
|
|
1155
|
+
});
|
|
1156
|
+
graph.querySelectorAll('.sm-node.is-related').forEach(function(node){
|
|
1157
|
+
node.classList.remove('is-related');
|
|
1158
|
+
});
|
|
1159
|
+
map.querySelectorAll('[data-sm-evidence-edge="1"]').forEach(function(edge){
|
|
1160
|
+
var toId = edge.getAttribute('data-sm-edge-to');
|
|
1161
|
+
var fromId = edge.getAttribute('data-sm-edge-from-node');
|
|
1162
|
+
var toNode = toId ? graph.querySelector('[data-sm-node-id="' + toId + '"]') : null;
|
|
1163
|
+
var fromNode = fromId ? graph.querySelector('[data-sm-node-id="' + fromId + '"]') : null;
|
|
1164
|
+
var focused = currentView === 'evidence' && !!selectedNodeId && (toId === selectedNodeId || fromId === selectedNodeId);
|
|
1165
|
+
if (focused) {
|
|
1166
|
+
if (toNode) revealNode(toNode, true);
|
|
1167
|
+
if (fromNode) revealNode(fromNode, true);
|
|
1168
|
+
}
|
|
1169
|
+
var endpointHidden = (toNode && toNode.hidden) || (fromNode && fromNode.hidden);
|
|
1170
|
+
var shouldHide = !focused || endpointHidden;
|
|
1171
|
+
edge.hidden = shouldHide;
|
|
1172
|
+
edge.toggleAttribute('hidden', shouldHide);
|
|
1173
|
+
edge.setAttribute('aria-hidden', shouldHide ? 'true' : 'false');
|
|
1174
|
+
edge.style.display = shouldHide ? 'none' : '';
|
|
1175
|
+
if (!shouldHide) {
|
|
1176
|
+
if (toNode && toId !== selectedNodeId) toNode.classList.add('is-related');
|
|
1177
|
+
if (fromNode && fromId !== selectedNodeId) fromNode.classList.add('is-related');
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1003
1182
|
function updateVisibility(){
|
|
1004
1183
|
var showLeaves = true;
|
|
1005
1184
|
var leafToggle = map.querySelector('[data-sm-toggle-leaves]');
|
|
1006
1185
|
if (leafToggle) showLeaves = leafToggle.checked;
|
|
1007
1186
|
var layers = {
|
|
1008
|
-
definition:
|
|
1009
|
-
measurement:
|
|
1187
|
+
definition: currentView === 'boundary',
|
|
1188
|
+
measurement: currentView === 'evidence'
|
|
1010
1189
|
};
|
|
1011
1190
|
map.querySelectorAll('[data-sm-layer]').forEach(function(el){
|
|
1012
1191
|
var layer = el.getAttribute('data-sm-layer');
|
|
@@ -1020,6 +1199,7 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1020
1199
|
el.style.display = shouldHide ? 'none' : '';
|
|
1021
1200
|
});
|
|
1022
1201
|
map.querySelectorAll('[data-sm-edge-to]').forEach(function(edge){
|
|
1202
|
+
if (edge.getAttribute('data-sm-evidence-edge') === '1') return;
|
|
1023
1203
|
var toId = edge.getAttribute('data-sm-edge-to');
|
|
1024
1204
|
var fromId = edge.getAttribute('data-sm-edge-from-node');
|
|
1025
1205
|
var toNode = toId ? graph.querySelector('[data-sm-node-id="' + toId + '"]') : null;
|
|
@@ -1032,6 +1212,23 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1032
1212
|
edge.setAttribute('aria-hidden', shouldHideEdge ? 'true' : 'false');
|
|
1033
1213
|
edge.style.display = shouldHideEdge ? 'none' : '';
|
|
1034
1214
|
});
|
|
1215
|
+
if (selectedNodeId) {
|
|
1216
|
+
var selectedNode = graph.querySelector('[data-sm-node-id="' + selectedNodeId + '"]');
|
|
1217
|
+
if (selectedNode && selectedNode.hidden) {
|
|
1218
|
+
var rootNode = graph.querySelector('[data-sm-root]');
|
|
1219
|
+
if (rootNode) setSelectedNode(rootNode);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
updateEvidenceFocus();
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function setView(view){
|
|
1226
|
+
currentView = view === 'evidence' ? 'evidence' : 'boundary';
|
|
1227
|
+
applyViewPositions(currentView);
|
|
1228
|
+
updateViewButtons();
|
|
1229
|
+
updateVisibility();
|
|
1230
|
+
refreshEdges();
|
|
1231
|
+
centerRoot();
|
|
1035
1232
|
}
|
|
1036
1233
|
|
|
1037
1234
|
function setMoreExpanded(toggle, expanded){
|
|
@@ -1039,11 +1236,11 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1039
1236
|
var nextLabel = expanded
|
|
1040
1237
|
? toggle.getAttribute('data-sm-expanded-label')
|
|
1041
1238
|
: toggle.getAttribute('data-sm-collapsed-label');
|
|
1042
|
-
var title = toggle.querySelector('.sm-node-title');
|
|
1043
|
-
if (title && nextLabel) title.textContent = nextLabel;
|
|
1044
1239
|
if (nextLabel) toggle.setAttribute('title', nextLabel);
|
|
1240
|
+
if (nextLabel) toggle.setAttribute('aria-label', nextLabel);
|
|
1045
1241
|
updateVisibility();
|
|
1046
1242
|
refreshEdges();
|
|
1243
|
+
if (selectedNodeId && toggle.getAttribute('data-sm-node-id') === selectedNodeId) setSelectedNode(toggle);
|
|
1047
1244
|
}
|
|
1048
1245
|
|
|
1049
1246
|
function collapseMoreGroups(){
|
|
@@ -1067,7 +1264,13 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1067
1264
|
});
|
|
1068
1265
|
});
|
|
1069
1266
|
|
|
1070
|
-
map.querySelectorAll('[data-sm-
|
|
1267
|
+
map.querySelectorAll('[data-sm-view-button]').forEach(function(button){
|
|
1268
|
+
button.addEventListener('click', function(){
|
|
1269
|
+
setView(button.getAttribute('data-sm-view-button') || 'boundary');
|
|
1270
|
+
});
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
map.querySelectorAll('[data-sm-toggle-leaves]').forEach(function(input){
|
|
1071
1274
|
input.addEventListener('change', updateVisibility);
|
|
1072
1275
|
});
|
|
1073
1276
|
|
|
@@ -1078,11 +1281,13 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1078
1281
|
return;
|
|
1079
1282
|
}
|
|
1080
1283
|
ev.preventDefault();
|
|
1284
|
+
setSelectedNode(toggle);
|
|
1081
1285
|
setMoreExpanded(toggle, toggle.getAttribute('aria-expanded') !== 'true');
|
|
1082
1286
|
});
|
|
1083
1287
|
toggle.addEventListener('keydown', function(ev){
|
|
1084
1288
|
if (ev.key !== 'Enter' && ev.key !== ' ') return;
|
|
1085
1289
|
ev.preventDefault();
|
|
1290
|
+
setSelectedNode(toggle);
|
|
1086
1291
|
setMoreExpanded(toggle, toggle.getAttribute('aria-expanded') !== 'true');
|
|
1087
1292
|
});
|
|
1088
1293
|
});
|
|
@@ -1094,6 +1299,7 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1094
1299
|
var nodeId = node.getAttribute('data-sm-node-id');
|
|
1095
1300
|
if (!nodeId) return;
|
|
1096
1301
|
ev.preventDefault();
|
|
1302
|
+
setSelectedNode(node);
|
|
1097
1303
|
dragState = {
|
|
1098
1304
|
node: node,
|
|
1099
1305
|
nodeId: nodeId,
|
|
@@ -1142,6 +1348,19 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1142
1348
|
node.addEventListener('pointercancel', finishDrag);
|
|
1143
1349
|
});
|
|
1144
1350
|
|
|
1351
|
+
graph.querySelectorAll('[data-sm-node-id]').forEach(function(node){
|
|
1352
|
+
node.addEventListener('click', function(){
|
|
1353
|
+
if (node.hasAttribute('data-sm-more-toggle') && Date.now() < suppressMoreClickUntil) return;
|
|
1354
|
+
setSelectedNode(node);
|
|
1355
|
+
});
|
|
1356
|
+
node.addEventListener('keydown', function(ev){
|
|
1357
|
+
if (ev.key !== 'Enter' && ev.key !== ' ') return;
|
|
1358
|
+
if (node.hasAttribute('data-sm-more-toggle')) return;
|
|
1359
|
+
ev.preventDefault();
|
|
1360
|
+
setSelectedNode(node);
|
|
1361
|
+
});
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1145
1364
|
viewport.addEventListener('wheel', function(ev){
|
|
1146
1365
|
if (!ev.ctrlKey && !ev.metaKey) return;
|
|
1147
1366
|
ev.preventDefault();
|
|
@@ -1150,8 +1369,11 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1150
1369
|
setZoom(zoom + delta, ev.clientX - rect.left, ev.clientY - rect.top);
|
|
1151
1370
|
}, { passive: false });
|
|
1152
1371
|
|
|
1372
|
+
updateViewButtons();
|
|
1153
1373
|
applyZoom();
|
|
1154
1374
|
updateVisibility();
|
|
1375
|
+
var initialRoot = graph.querySelector('[data-sm-root]');
|
|
1376
|
+
if (initialRoot) setSelectedNode(initialRoot);
|
|
1155
1377
|
requestAnimationFrame(function(){
|
|
1156
1378
|
refreshEdges();
|
|
1157
1379
|
centerRoot();
|
|
@@ -1601,6 +1823,7 @@ function nodeKindLabel(kind, lang) {
|
|
|
1601
1823
|
eval_result: ['eval', 'eval'],
|
|
1602
1824
|
trace_session: ['observe', 'observe'],
|
|
1603
1825
|
more: ['more', 'more'],
|
|
1826
|
+
knowledge_group: ['group', 'group'],
|
|
1604
1827
|
};
|
|
1605
1828
|
const pair = labels[kind] ?? [kind, kind];
|
|
1606
1829
|
return zh ? pair[0] : pair[1];
|
|
@@ -1644,25 +1867,86 @@ function previewNodes(nodes, allowedKinds, limit) {
|
|
|
1644
1867
|
const SKILL_MAP_WIDTH = 1100;
|
|
1645
1868
|
const SKILL_MAP_HEIGHT = 560;
|
|
1646
1869
|
const SKILL_MAP_ROOT_ID = 'skill-root';
|
|
1647
|
-
const
|
|
1870
|
+
const SKILL_MAP_BOUNDARY_ROOT = { x: 560, y: 285 };
|
|
1871
|
+
const SKILL_MAP_EVIDENCE_ROOT = { x: 470, y: 260 };
|
|
1648
1872
|
const SKILL_MAP_ROOT_SIZE = { width: 172, height: 86 };
|
|
1649
1873
|
const SKILL_MAP_NODE_SIZE = { width: 148, height: 54 };
|
|
1650
|
-
const
|
|
1651
|
-
{
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1874
|
+
const SKILL_MAP_DEFINITION_GROUPS = [
|
|
1875
|
+
{
|
|
1876
|
+
key: 'references',
|
|
1877
|
+
labelZh: '引用材料',
|
|
1878
|
+
labelEn: 'References',
|
|
1879
|
+
descriptionZh: 'Skill 显式依赖的参考文档和知识来源。',
|
|
1880
|
+
descriptionEn: 'Reference docs and knowledge sources the Skill depends on.',
|
|
1881
|
+
nodeKinds: ['reference'],
|
|
1882
|
+
boundaryPosition: { x: 270, y: 225 },
|
|
1883
|
+
evidencePosition: { x: 140, y: 145 },
|
|
1884
|
+
childBoundaryPositions: [
|
|
1885
|
+
{ x: 135, y: 125 },
|
|
1886
|
+
{ x: 130, y: 325 },
|
|
1887
|
+
{ x: 270, y: 435 },
|
|
1888
|
+
{ x: 410, y: 125 },
|
|
1889
|
+
{ x: 405, y: 325 },
|
|
1890
|
+
],
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
key: 'workflow',
|
|
1894
|
+
labelZh: '执行流程',
|
|
1895
|
+
labelEn: 'Workflow',
|
|
1896
|
+
descriptionZh: 'Skill 期望代理遵循的流程与步骤。',
|
|
1897
|
+
descriptionEn: 'Workflows and steps the agent is expected to follow.',
|
|
1898
|
+
nodeKinds: ['workflow', 'workflow_node'],
|
|
1899
|
+
boundaryPosition: { x: 850, y: 225 },
|
|
1900
|
+
evidencePosition: { x: 960, y: 145 },
|
|
1901
|
+
childBoundaryPositions: [
|
|
1902
|
+
{ x: 990, y: 125 },
|
|
1903
|
+
{ x: 990, y: 325 },
|
|
1904
|
+
{ x: 850, y: 435 },
|
|
1905
|
+
{ x: 710, y: 125 },
|
|
1906
|
+
{ x: 715, y: 325 },
|
|
1907
|
+
],
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
key: 'rules',
|
|
1911
|
+
labelZh: '约束规则',
|
|
1912
|
+
labelEn: 'Rules',
|
|
1913
|
+
descriptionZh: 'Skill 中不可违反的硬规则和输出约束。',
|
|
1914
|
+
descriptionEn: 'Hard rules and output constraints that must not be violated.',
|
|
1915
|
+
nodeKinds: ['hard_rule'],
|
|
1916
|
+
boundaryPosition: { x: 560, y: 105 },
|
|
1917
|
+
evidencePosition: { x: 140, y: 285 },
|
|
1918
|
+
childBoundaryPositions: [
|
|
1919
|
+
{ x: 360, y: 80 },
|
|
1920
|
+
{ x: 760, y: 80 },
|
|
1921
|
+
{ x: 560, y: 30 },
|
|
1922
|
+
{ x: 360, y: 160 },
|
|
1923
|
+
{ x: 760, y: 160 },
|
|
1924
|
+
],
|
|
1925
|
+
},
|
|
1926
|
+
];
|
|
1927
|
+
const SKILL_MAP_BOUNDARY_ASSET_COORDS = [
|
|
1928
|
+
{ x: 380, y: 455 },
|
|
1929
|
+
{ x: 740, y: 455 },
|
|
1930
|
+
{ x: 205, y: 455 },
|
|
1931
|
+
{ x: 915, y: 455 },
|
|
1932
|
+
{ x: 560, y: 510 },
|
|
1933
|
+
];
|
|
1934
|
+
const SKILL_MAP_EVIDENCE_DEFINITION_COORDS = [
|
|
1935
|
+
{ x: 145, y: 145 },
|
|
1936
|
+
{ x: 145, y: 285 },
|
|
1937
|
+
{ x: 145, y: 425 },
|
|
1938
|
+
{ x: 955, y: 145 },
|
|
1939
|
+
{ x: 955, y: 285 },
|
|
1940
|
+
{ x: 955, y: 425 },
|
|
1941
|
+
{ x: 560, y: 485 },
|
|
1658
1942
|
];
|
|
1659
|
-
const
|
|
1660
|
-
{ x:
|
|
1661
|
-
{ x:
|
|
1662
|
-
{ x:
|
|
1663
|
-
{ x:
|
|
1664
|
-
{ x:
|
|
1665
|
-
{ x:
|
|
1943
|
+
const SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS = [
|
|
1944
|
+
{ x: 320, y: 140 },
|
|
1945
|
+
{ x: 560, y: 115 },
|
|
1946
|
+
{ x: 800, y: 150 },
|
|
1947
|
+
{ x: 345, y: 360 },
|
|
1948
|
+
{ x: 775, y: 365 },
|
|
1949
|
+
{ x: 560, y: 455 },
|
|
1666
1950
|
];
|
|
1667
1951
|
const SKILL_MAP_NODE_MARGIN = { x: 92, y: 48 };
|
|
1668
1952
|
const SKILL_MAP_STATUS_CLASSES = new Set(['ok', 'warning', 'failed', 'skipped', 'unknown', 'not_measured']);
|
|
@@ -1677,29 +1961,298 @@ function graphNodeCoverageClass(node) {
|
|
|
1677
1961
|
return '';
|
|
1678
1962
|
}
|
|
1679
1963
|
function graphNodeCoverageLabel(node, lang) {
|
|
1964
|
+
const groupSummary = skillMapGroupSummary(node);
|
|
1965
|
+
if (groupSummary) {
|
|
1966
|
+
return lang === 'zh'
|
|
1967
|
+
? `${groupSummary.declared}/${groupSummary.total} 个节点已被评测用例声明`
|
|
1968
|
+
: `${groupSummary.declared}/${groupSummary.total} nodes declared by eval samples`;
|
|
1969
|
+
}
|
|
1680
1970
|
if (node.coverage === 'declared')
|
|
1681
1971
|
return lang === 'zh' ? '已由评测用例声明' : 'declared by eval samples';
|
|
1682
1972
|
if (node.coverage === 'undeclared')
|
|
1683
1973
|
return lang === 'zh' ? '尚未由评测用例声明' : 'not declared by eval samples';
|
|
1684
1974
|
return '';
|
|
1685
1975
|
}
|
|
1976
|
+
function graphNodeEvidenceLabel(node, lang) {
|
|
1977
|
+
const zh = lang === 'zh';
|
|
1978
|
+
const groupSummary = skillMapGroupSummary(node);
|
|
1979
|
+
if (groupSummary) {
|
|
1980
|
+
return zh
|
|
1981
|
+
? `包含 ${groupSummary.total} 个结构节点,其中 ${groupSummary.declared} 个已有评测用例声明。`
|
|
1982
|
+
: `Contains ${groupSummary.total} structure nodes; ${groupSummary.declared} are declared by eval samples.`;
|
|
1983
|
+
}
|
|
1984
|
+
if (node.coveredBySamples?.length) {
|
|
1985
|
+
const samples = node.coveredBySamples.slice(0, 3).join(zh ? '、' : ', ');
|
|
1986
|
+
const more = node.coveredBySamples.length > 3 ? (zh ? ` 等 ${node.coveredBySamples.length} 条用例` : ` and ${node.coveredBySamples.length} samples`) : '';
|
|
1987
|
+
return zh ? `由 ${samples}${more} 声明覆盖` : `Declared by ${samples}${more}`;
|
|
1988
|
+
}
|
|
1989
|
+
if (node.coverage === 'undeclared')
|
|
1990
|
+
return zh ? '暂无评测用例显式声明覆盖' : 'No eval sample explicitly declares this node yet';
|
|
1991
|
+
if (node.status === 'failed')
|
|
1992
|
+
return zh ? '当前测量证据失败' : 'Current measurement evidence failed';
|
|
1993
|
+
if (node.status === 'warning')
|
|
1994
|
+
return zh ? '当前测量证据有告警' : 'Current measurement evidence has warnings';
|
|
1995
|
+
if (node.nodeKind === 'sample')
|
|
1996
|
+
return zh ? '这条用例用于测量当前 Skill' : 'This sample measures the current Skill';
|
|
1997
|
+
if (node.nodeKind === 'assertion')
|
|
1998
|
+
return zh ? '断言用于把用例输出转成可比较证据' : 'Assertion turns sample output into comparable evidence';
|
|
1999
|
+
return '';
|
|
2000
|
+
}
|
|
2001
|
+
function graphNodeActionLabel(node, lang) {
|
|
2002
|
+
const zh = lang === 'zh';
|
|
2003
|
+
if (node.nodeKind === 'knowledge_group') {
|
|
2004
|
+
return zh
|
|
2005
|
+
? '点击分组展开叶子节点;优先检查未声明或失败证据集中的节点。'
|
|
2006
|
+
: 'Open the group to inspect leaves; prioritize undeclared nodes or nodes linked to failures.';
|
|
2007
|
+
}
|
|
2008
|
+
if (node.status === 'failed' && node.nodeKind === 'sample') {
|
|
2009
|
+
return zh ? '优先查看这条失败用例的输出,确认是 Skill 缺知识、流程缺口还是断言过严。' : 'Inspect this failed sample first and decide whether the Skill, workflow, or assertion needs work.';
|
|
2010
|
+
}
|
|
2011
|
+
if (node.status === 'failed' && node.nodeKind === 'assertion') {
|
|
2012
|
+
return zh ? '回到对应用例,检查失败断言期望是否与 Skill 边界一致。' : 'Go back to the sample and verify whether the failed assertion matches the Skill boundary.';
|
|
2013
|
+
}
|
|
2014
|
+
if (node.coverage === 'undeclared') {
|
|
2015
|
+
return zh ? '如果这是关键知识边界,给代表性评测用例补 `sample.covers`;否则可保持未声明。' : 'If this is a key boundary, add sample.covers to a representative eval sample; otherwise leave it undeclared.';
|
|
2016
|
+
}
|
|
2017
|
+
if (node.coveredBySamples?.length) {
|
|
2018
|
+
return zh ? '优先看覆盖它的用例是否失败;失败集中时,回到这条知识边界修 Skill。' : 'Check whether samples covering this node fail; clustered failures point back to this Skill boundary.';
|
|
2019
|
+
}
|
|
2020
|
+
return '';
|
|
2021
|
+
}
|
|
2022
|
+
function graphNodeDetailKindLabel(nodeKind, lang) {
|
|
2023
|
+
const zh = lang === 'zh';
|
|
2024
|
+
const labels = {
|
|
2025
|
+
skill: ['Skill 根节点', 'Skill root'],
|
|
2026
|
+
skill_file: ['Skill 文件', 'Skill file'],
|
|
2027
|
+
frontmatter: ['Frontmatter', 'Frontmatter'],
|
|
2028
|
+
reference: ['引用文档', 'Reference'],
|
|
2029
|
+
script: ['脚本', 'Script'],
|
|
2030
|
+
tool: ['工具', 'Tool'],
|
|
2031
|
+
hard_rule: ['硬规则', 'Hard rule'],
|
|
2032
|
+
workflow: ['流程', 'Workflow'],
|
|
2033
|
+
workflow_node: ['流程节点', 'Workflow node'],
|
|
2034
|
+
sample: ['评测用例', 'Eval sample'],
|
|
2035
|
+
assertion: ['断言', 'Assertion'],
|
|
2036
|
+
eval_result: ['评测结果', 'Eval result'],
|
|
2037
|
+
diagnostic: ['诊断', 'Diagnostic'],
|
|
2038
|
+
more: ['折叠分组', 'Collapsed group'],
|
|
2039
|
+
knowledge_group: ['知识分组', 'Knowledge group'],
|
|
2040
|
+
};
|
|
2041
|
+
const pair = labels[nodeKind] ?? [nodeKind, nodeKind];
|
|
2042
|
+
return zh ? pair[0] : pair[1];
|
|
2043
|
+
}
|
|
2044
|
+
function graphNodeStatusLabel(status, lang) {
|
|
2045
|
+
if (!status)
|
|
2046
|
+
return '';
|
|
2047
|
+
const zh = lang === 'zh';
|
|
2048
|
+
const labels = {
|
|
2049
|
+
ok: ['正常', 'ok'],
|
|
2050
|
+
warning: ['告警', 'warning'],
|
|
2051
|
+
failed: ['失败', 'failed'],
|
|
2052
|
+
skipped: ['已跳过', 'skipped'],
|
|
2053
|
+
unknown: ['未知', 'unknown'],
|
|
2054
|
+
not_measured: ['未测量', 'not measured'],
|
|
2055
|
+
};
|
|
2056
|
+
const pair = labels[status];
|
|
2057
|
+
if (!pair)
|
|
2058
|
+
return '';
|
|
2059
|
+
return zh ? pair[0] : pair[1];
|
|
2060
|
+
}
|
|
2061
|
+
function skillMapLayerLabel(layer, lang) {
|
|
2062
|
+
if (layer === 'definition')
|
|
2063
|
+
return lang === 'zh' ? '结构定义' : 'Definition';
|
|
2064
|
+
return lang === 'zh' ? '评测测量' : 'Measurement';
|
|
2065
|
+
}
|
|
2066
|
+
function skillMapGroupSummary(node) {
|
|
2067
|
+
return node.groupSummary;
|
|
2068
|
+
}
|
|
2069
|
+
function skillMapGroupDescription(node) {
|
|
2070
|
+
return node.groupDescription ?? '';
|
|
2071
|
+
}
|
|
2072
|
+
function graphNodeDetailSource(node, lang) {
|
|
2073
|
+
const zh = lang === 'zh';
|
|
2074
|
+
switch (node.nodeKind) {
|
|
2075
|
+
case 'knowledge_group':
|
|
2076
|
+
return skillMapGroupDescription(node);
|
|
2077
|
+
case 'reference':
|
|
2078
|
+
case 'script':
|
|
2079
|
+
return node.label;
|
|
2080
|
+
case 'workflow':
|
|
2081
|
+
return `${zh ? 'workflow id' : 'workflow id'}: ${node.label}`;
|
|
2082
|
+
case 'workflow_node':
|
|
2083
|
+
return `${zh ? 'workflow node' : 'workflow node'}: ${node.label}`;
|
|
2084
|
+
case 'hard_rule':
|
|
2085
|
+
return `${zh ? 'rule id' : 'rule id'}: ${node.label}`;
|
|
2086
|
+
case 'sample':
|
|
2087
|
+
return `${zh ? 'sample_id' : 'sample_id'}: ${node.label}`;
|
|
2088
|
+
case 'assertion':
|
|
2089
|
+
case 'diagnostic':
|
|
2090
|
+
case 'eval_result':
|
|
2091
|
+
case 'tool':
|
|
2092
|
+
case 'frontmatter':
|
|
2093
|
+
return node.label;
|
|
2094
|
+
default:
|
|
2095
|
+
return '';
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
function attr(name, value) {
|
|
2099
|
+
return value ? ` ${name}="${e(value)}"` : '';
|
|
2100
|
+
}
|
|
2101
|
+
function renderSkillMapDetailAttrs(item, lang) {
|
|
2102
|
+
const coverageLabel = graphNodeCoverageLabel(item.node, lang);
|
|
2103
|
+
const isMore = item.node.nodeKind === 'more';
|
|
2104
|
+
const isGroup = item.node.nodeKind === 'knowledge_group';
|
|
2105
|
+
const groupSummary = skillMapGroupSummary(item.node);
|
|
2106
|
+
const scope = skillMapLayerLabel(item.layer, lang);
|
|
2107
|
+
const detailTitle = item.presentation?.detailTitle ?? item.presentation?.title;
|
|
2108
|
+
const status = isMore
|
|
2109
|
+
? (lang === 'zh' ? '已收起' : 'collapsed')
|
|
2110
|
+
: isGroup && groupSummary
|
|
2111
|
+
? (lang === 'zh' ? `${groupSummary.total} 个节点` : `${groupSummary.total} nodes`)
|
|
2112
|
+
: graphNodeStatusLabel(item.node.status, lang);
|
|
2113
|
+
return [
|
|
2114
|
+
attr('data-sm-detail-kind', graphNodeDetailKindLabel(item.node.nodeKind, lang)),
|
|
2115
|
+
attr('data-sm-detail-title', isMore ? item.node.label : detailTitle ?? graphNodeDisplayLabel(item.node, lang)),
|
|
2116
|
+
attr('data-sm-detail-subtitle', isMore ? scope : graphNodeDetailSource(item.node, lang)),
|
|
2117
|
+
attr('data-sm-detail-scope', scope),
|
|
2118
|
+
attr('data-sm-detail-source', graphNodeDetailSource(item.node, lang)),
|
|
2119
|
+
attr('data-sm-detail-coverage', coverageLabel),
|
|
2120
|
+
attr('data-sm-detail-status', status),
|
|
2121
|
+
attr('data-sm-detail-evidence', graphNodeEvidenceLabel(item.node, lang)),
|
|
2122
|
+
attr('data-sm-detail-action', graphNodeActionLabel(item.node, lang)),
|
|
2123
|
+
...(item.moreToggle ? [
|
|
2124
|
+
attr('data-sm-detail-collapsed-status', lang === 'zh' ? '已收起' : 'collapsed'),
|
|
2125
|
+
attr('data-sm-detail-expanded-status', lang === 'zh' ? '已展开' : 'expanded'),
|
|
2126
|
+
] : []),
|
|
2127
|
+
].join('');
|
|
2128
|
+
}
|
|
2129
|
+
function graphNodeSignalLabel(node, lang) {
|
|
2130
|
+
const zh = lang === 'zh';
|
|
2131
|
+
const groupSummary = skillMapGroupSummary(node);
|
|
2132
|
+
if (groupSummary)
|
|
2133
|
+
return zh ? `${groupSummary.declared}/${groupSummary.total} 已声明` : `${groupSummary.declared}/${groupSummary.total} declared`;
|
|
2134
|
+
if (node.status === 'failed')
|
|
2135
|
+
return zh ? '失败证据' : 'failed';
|
|
2136
|
+
if (node.status === 'warning')
|
|
2137
|
+
return zh ? '告警' : 'warning';
|
|
2138
|
+
if (node.coverage === 'declared')
|
|
2139
|
+
return zh ? '已声明覆盖' : 'declared';
|
|
2140
|
+
if (node.coverage === 'undeclared')
|
|
2141
|
+
return zh ? '未声明' : 'undeclared';
|
|
2142
|
+
return '';
|
|
2143
|
+
}
|
|
2144
|
+
function graphNodeSignalClass(node) {
|
|
2145
|
+
const groupSummary = skillMapGroupSummary(node);
|
|
2146
|
+
if (groupSummary)
|
|
2147
|
+
return groupSummary.declared === groupSummary.total ? ' sm-node-signal--declared' : ' sm-node-signal--undeclared';
|
|
2148
|
+
if (node.status === 'failed')
|
|
2149
|
+
return ' sm-node-signal--failed';
|
|
2150
|
+
if (node.status === 'warning')
|
|
2151
|
+
return ' sm-node-signal--warning';
|
|
2152
|
+
if (node.coverage === 'declared')
|
|
2153
|
+
return ' sm-node-signal--declared';
|
|
2154
|
+
if (node.coverage === 'undeclared')
|
|
2155
|
+
return ' sm-node-signal--undeclared';
|
|
2156
|
+
return '';
|
|
2157
|
+
}
|
|
2158
|
+
function skillMapRootDetail(entry, coverageSummary, lang) {
|
|
2159
|
+
const zh = lang === 'zh';
|
|
2160
|
+
const failedSamples = entry.eval?.failCount ?? 0;
|
|
2161
|
+
return {
|
|
2162
|
+
kindLabel: graphNodeDetailKindLabel('skill', lang),
|
|
2163
|
+
title: 'SKILL.md',
|
|
2164
|
+
subtitle: entry.skillName,
|
|
2165
|
+
scope: zh ? '中心节点' : 'Root',
|
|
2166
|
+
source: 'SKILL.md',
|
|
2167
|
+
coverage: coverageSummary ?? '',
|
|
2168
|
+
status: [
|
|
2169
|
+
`doctor ${stageText(entry, 'doctor', lang)}`,
|
|
2170
|
+
`eval ${stageText(entry, 'eval', lang)}`,
|
|
2171
|
+
`observe ${stageText(entry, 'observe', lang)}`,
|
|
2172
|
+
].join(' · '),
|
|
2173
|
+
evidence: coverageSummary ?? (zh ? '尚未形成声明锚点' : 'No declared anchors yet'),
|
|
2174
|
+
action: failedSamples > 0
|
|
2175
|
+
? (zh ? `先处理 ${failedSamples} 条失败评测用例,再回看其覆盖的知识边界。` : `Start with ${failedSamples} failed eval samples, then inspect the covered Skill boundaries.`)
|
|
2176
|
+
: (zh ? '继续补充关键场景的 sample.covers,让图谱从结构图变成证据图。' : 'Keep adding sample.covers for key scenarios so the map becomes evidence-oriented.'),
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
function renderSkillMapRootDetailAttrs(entry, coverageSummary, lang) {
|
|
2180
|
+
const detail = skillMapRootDetail(entry, coverageSummary, lang);
|
|
2181
|
+
return [
|
|
2182
|
+
attr('data-sm-detail-kind', detail.kindLabel),
|
|
2183
|
+
attr('data-sm-detail-title', detail.title),
|
|
2184
|
+
attr('data-sm-detail-subtitle', detail.subtitle),
|
|
2185
|
+
attr('data-sm-detail-scope', detail.scope),
|
|
2186
|
+
attr('data-sm-detail-source', detail.source),
|
|
2187
|
+
attr('data-sm-detail-coverage', detail.coverage),
|
|
2188
|
+
attr('data-sm-detail-status', detail.status),
|
|
2189
|
+
attr('data-sm-detail-evidence', detail.evidence),
|
|
2190
|
+
attr('data-sm-detail-action', detail.action),
|
|
2191
|
+
].join('');
|
|
2192
|
+
}
|
|
2193
|
+
function renderSkillMapInspector(entry, coverageSummary, lang) {
|
|
2194
|
+
const zh = lang === 'zh';
|
|
2195
|
+
const detail = skillMapRootDetail(entry, coverageSummary, lang);
|
|
2196
|
+
const row = (key, label, value, required = false) => {
|
|
2197
|
+
const hidden = !value && !required ? ' hidden' : '';
|
|
2198
|
+
return `<div class="sm-detail-row" data-sm-detail-row="${e(key)}"${hidden}>
|
|
2199
|
+
<dt>${e(label)}</dt>
|
|
2200
|
+
<dd data-sm-detail-value="${e(key)}">${e(value || '—')}</dd>
|
|
2201
|
+
</div>`;
|
|
2202
|
+
};
|
|
2203
|
+
return `<div class="sm-detail" data-sm-detail>
|
|
2204
|
+
<div class="sm-detail-head">
|
|
2205
|
+
<div class="sm-detail-kind" data-sm-detail-kind-label>${e(detail.kindLabel)}</div>
|
|
2206
|
+
<div class="sm-detail-title" data-sm-detail-title>${e(detail.title)}</div>
|
|
2207
|
+
<div class="sm-detail-subtitle" data-sm-detail-subtitle>${e(detail.subtitle)}</div>
|
|
2208
|
+
</div>
|
|
2209
|
+
<dl class="sm-detail-list">
|
|
2210
|
+
${row('scope', zh ? '范围' : 'Scope', detail.scope, true)}
|
|
2211
|
+
${row('source', zh ? '来源' : 'Source', detail.source)}
|
|
2212
|
+
${row('coverage', zh ? '声明' : 'Declaration', detail.coverage)}
|
|
2213
|
+
${row('status', zh ? '状态' : 'Status', detail.status)}
|
|
2214
|
+
${row('evidence', zh ? '证据' : 'Evidence', detail.evidence)}
|
|
2215
|
+
${row('action', zh ? '下一步' : 'Next', detail.action)}
|
|
2216
|
+
</dl>
|
|
2217
|
+
</div>`;
|
|
2218
|
+
}
|
|
1686
2219
|
function positionedNodeStyle(node) {
|
|
1687
2220
|
return `left:${node.x}px;top:${node.y}px`;
|
|
1688
2221
|
}
|
|
2222
|
+
function renderSkillMapViewPositionAttrs(item) {
|
|
2223
|
+
const attrs = [];
|
|
2224
|
+
if (item.boundaryPosition) {
|
|
2225
|
+
attrs.push(` data-sm-boundary-x="${item.boundaryPosition.x}" data-sm-boundary-y="${item.boundaryPosition.y}"`);
|
|
2226
|
+
}
|
|
2227
|
+
if (item.evidencePosition) {
|
|
2228
|
+
attrs.push(` data-sm-evidence-x="${item.evidencePosition.x}" data-sm-evidence-y="${item.evidencePosition.y}"`);
|
|
2229
|
+
}
|
|
2230
|
+
return attrs.join('');
|
|
2231
|
+
}
|
|
1689
2232
|
function renderSkillMapNode(item, lang) {
|
|
1690
|
-
const title = graphNodeMapLabel(item.node);
|
|
2233
|
+
const title = item.presentation?.title ?? graphNodeMapLabel(item.node);
|
|
1691
2234
|
const coverageLabel = graphNodeCoverageLabel(item.node, lang);
|
|
1692
|
-
const fullTitle = [
|
|
1693
|
-
|
|
1694
|
-
|
|
2235
|
+
const fullTitle = [
|
|
2236
|
+
item.presentation?.title,
|
|
2237
|
+
graphNodeDisplayLabel(item.node, lang),
|
|
2238
|
+
coverageLabel,
|
|
2239
|
+
].filter((value, index, values) => Boolean(value) && values.indexOf(value) === index).join(' · ');
|
|
2240
|
+
const signal = graphNodeSignalLabel(item.node, lang);
|
|
2241
|
+
const kindLabel = item.presentation?.hideKind || item.node.nodeKind === 'knowledge_group' ? '' : nodeKindLabel(item.node.nodeKind, lang);
|
|
2242
|
+
const kindClass = item.node.nodeKind === 'more' ? ' sm-node--more' : item.node.nodeKind === 'knowledge_group' ? ' sm-node--group' : '';
|
|
2243
|
+
const isLeaf = item.isLeaf !== false;
|
|
2244
|
+
const leaf = isLeaf ? ' data-sm-leaf="1"' : '';
|
|
1695
2245
|
const draggable = item.layer === 'definition' || item.layer === 'measurement' ? ' data-sm-draggable="1"' : '';
|
|
1696
2246
|
const overflow = item.overflowGroup ? ` data-sm-overflow-group="${item.overflowGroup}" hidden aria-hidden="true"` : '';
|
|
2247
|
+
const toggleIcon = item.moreToggle ? '<span class="sm-node-toggle-icon" aria-hidden="true"></span>' : '';
|
|
1697
2248
|
const moreToggle = item.moreToggle
|
|
1698
2249
|
? ` data-sm-more-toggle="${item.moreToggle.group}" data-sm-collapsed-label="${e(item.moreToggle.collapsedLabel)}" data-sm-expanded-label="${e(item.moreToggle.expandedLabel)}" role="button" tabindex="0" aria-expanded="false"`
|
|
1699
|
-
: '';
|
|
1700
|
-
return `<div class="sm-node sm-node--${item.layer}${kindClass}${graphNodeStatusClass(item.node)}${graphNodeCoverageClass(item.node)}" data-sm-node-id="${e(item.id)}" data-sm-layer="${item.layer}" data-sm-x="${item.x}" data-sm-y="${item.y}" data-sm-origin-x="${item.x}" data-sm-origin-y="${item.y}"${leaf}${draggable}${overflow}${moreToggle} style="${positionedNodeStyle(item)}" title="${e(fullTitle)}">
|
|
1701
|
-
|
|
2250
|
+
: ' role="button" tabindex="0"';
|
|
2251
|
+
return `<div class="sm-node sm-node--${item.layer}${kindClass}${graphNodeStatusClass(item.node)}${graphNodeCoverageClass(item.node)}" data-sm-node-id="${e(item.id)}" data-sm-layer="${item.layer}" data-sm-x="${item.x}" data-sm-y="${item.y}" data-sm-origin-x="${item.x}" data-sm-origin-y="${item.y}"${renderSkillMapViewPositionAttrs(item)}${leaf}${draggable}${overflow}${moreToggle}${renderSkillMapDetailAttrs(item, lang)} style="${positionedNodeStyle(item)}" title="${e(fullTitle)}" aria-label="${e(fullTitle)}">
|
|
2252
|
+
${toggleIcon}
|
|
2253
|
+
${kindLabel ? `<div class="sm-node-kind">${e(kindLabel)}</div>` : ''}
|
|
1702
2254
|
<div class="sm-node-title">${e(title)}</div>
|
|
2255
|
+
${signal ? `<div class="sm-node-signal${graphNodeSignalClass(item.node)}">${e(signal)}</div>` : ''}
|
|
1703
2256
|
</div>`;
|
|
1704
2257
|
}
|
|
1705
2258
|
function edgePoint(from, to, box) {
|
|
@@ -1718,33 +2271,212 @@ function skillMapPath(from, to, fromBox, toBox) {
|
|
|
1718
2271
|
const c2x = end.x - (end.x >= start.x ? dx * 0.45 : -dx * 0.45);
|
|
1719
2272
|
return `M ${start.x} ${start.y} C ${c1x} ${start.y}, ${c2x} ${end.y}, ${end.x} ${end.y}`;
|
|
1720
2273
|
}
|
|
1721
|
-
function renderSkillMapEdges(nodes, dimensions) {
|
|
2274
|
+
function renderSkillMapEdges(nodes, dimensions, evidenceLinks) {
|
|
1722
2275
|
const rootEdges = nodes
|
|
1723
2276
|
.map((node) => {
|
|
1724
|
-
const from = node.edgeFrom ??
|
|
2277
|
+
const from = node.edgeFrom ?? SKILL_MAP_BOUNDARY_ROOT;
|
|
1725
2278
|
const fromBox = node.edgeFrom ? SKILL_MAP_NODE_SIZE : SKILL_MAP_ROOT_SIZE;
|
|
1726
2279
|
const overflow = node.overflowGroup ? ` data-sm-overflow-group="${node.overflowGroup}" hidden aria-hidden="true"` : '';
|
|
1727
|
-
const fromNodeId = node.overflowGroup ? `${node.overflowGroup}-more` : SKILL_MAP_ROOT_ID;
|
|
1728
|
-
|
|
2280
|
+
const fromNodeId = node.edgeFromId ?? (node.overflowGroup ? `${node.overflowGroup}-more` : SKILL_MAP_ROOT_ID);
|
|
2281
|
+
const leaf = node.isLeaf === false ? '' : ' data-sm-leaf="1"';
|
|
2282
|
+
return `<path class="sm-edge sm-edge--${node.layer}" data-sm-layer="${node.layer}"${leaf} data-sm-edge-to="${e(node.id)}" data-sm-edge-from-node="${e(fromNodeId)}" data-sm-edge-from-x="${from.x}" data-sm-edge-from-y="${from.y}"${overflow} d="${skillMapPath(from, node, fromBox, SKILL_MAP_NODE_SIZE)}"></path>`;
|
|
1729
2283
|
})
|
|
1730
2284
|
.join('');
|
|
1731
|
-
|
|
2285
|
+
const evidenceEdges = evidenceLinks
|
|
2286
|
+
.map((link, index) => `<path class="sm-edge sm-edge--coverage${link.failed ? ' sm-edge--coverage-failed' : ''}" data-sm-layer="measurement" data-sm-leaf="1" data-sm-evidence-edge="1" data-sm-edge-to="${e(link.toId)}" data-sm-edge-from-node="${e(link.fromId)}" data-sm-edge-from-x="${link.from.x}" data-sm-edge-from-y="${link.from.y}" d="${skillMapPath(link.from, link.to, SKILL_MAP_NODE_SIZE, SKILL_MAP_NODE_SIZE)}" data-sm-edge-index="${index}" hidden aria-hidden="true"></path>`)
|
|
2287
|
+
.join('');
|
|
2288
|
+
return `<svg class="sm-svg" viewBox="0 0 ${dimensions.width} ${dimensions.height}" aria-hidden="true">${rootEdges}${evidenceEdges}</svg>`;
|
|
2289
|
+
}
|
|
2290
|
+
function coverageSamplesByStableKey(evalGraph, lang) {
|
|
2291
|
+
const zh = lang === 'zh';
|
|
2292
|
+
const out = new Map();
|
|
2293
|
+
for (const edge of evalGraph?.declaredCoverageEdges ?? []) {
|
|
2294
|
+
const samples = out.get(edge.targetStableKey) ?? [];
|
|
2295
|
+
const sampleLabel = edge.sampleStatus === 'failed'
|
|
2296
|
+
? (zh ? `${edge.sampleLabel}(失败)` : `${edge.sampleLabel} (failed)`)
|
|
2297
|
+
: edge.sampleLabel;
|
|
2298
|
+
if (!samples.includes(sampleLabel))
|
|
2299
|
+
samples.push(sampleLabel);
|
|
2300
|
+
out.set(edge.targetStableKey, samples);
|
|
2301
|
+
}
|
|
2302
|
+
return out;
|
|
2303
|
+
}
|
|
2304
|
+
function skillMapEvidenceLinks(nodes, evalGraph) {
|
|
2305
|
+
if (!evalGraph?.declaredCoverageEdges.length)
|
|
2306
|
+
return [];
|
|
2307
|
+
const targetByStableKey = new Map();
|
|
2308
|
+
const sampleByLabel = new Map();
|
|
2309
|
+
for (const node of nodes) {
|
|
2310
|
+
if (node.node.stableKey)
|
|
2311
|
+
targetByStableKey.set(node.node.stableKey, node);
|
|
2312
|
+
if (node.node.nodeKind === 'sample')
|
|
2313
|
+
sampleByLabel.set(node.node.label, node);
|
|
2314
|
+
}
|
|
2315
|
+
return evalGraph.declaredCoverageEdges
|
|
2316
|
+
.map((edge) => {
|
|
2317
|
+
const from = sampleByLabel.get(edge.sampleLabel);
|
|
2318
|
+
const to = targetByStableKey.get(edge.targetStableKey);
|
|
2319
|
+
if (!from || !to)
|
|
2320
|
+
return null;
|
|
2321
|
+
return {
|
|
2322
|
+
fromId: from.id,
|
|
2323
|
+
toId: to.id,
|
|
2324
|
+
from: { x: from.x, y: from.y },
|
|
2325
|
+
to: { x: to.x, y: to.y },
|
|
2326
|
+
...(edge.sampleStatus === 'failed' ? { failed: true } : {}),
|
|
2327
|
+
};
|
|
2328
|
+
})
|
|
2329
|
+
.filter((link) => Boolean(link));
|
|
1732
2330
|
}
|
|
1733
2331
|
function moreNode(label) {
|
|
1734
2332
|
return { nodeKind: 'more', label };
|
|
1735
2333
|
}
|
|
1736
|
-
function overflowNodePosition(layer, index) {
|
|
1737
|
-
const columns = layer === 'definition'
|
|
1738
|
-
|
|
2334
|
+
function overflowNodePosition(layer, index, view) {
|
|
2335
|
+
const columns = layer === 'definition'
|
|
2336
|
+
? view === 'boundary' ? [205, 382, 560, 738, 915] : [120, 290, 810, 980]
|
|
2337
|
+
: [250, 430, 610, 790, 970];
|
|
2338
|
+
const startY = layer === 'definition'
|
|
2339
|
+
? view === 'boundary' ? 570 : 535
|
|
2340
|
+
: 555;
|
|
1739
2341
|
return {
|
|
1740
2342
|
x: columns[index % columns.length],
|
|
1741
2343
|
y: startY + Math.floor(index / columns.length) * 98,
|
|
1742
2344
|
};
|
|
1743
2345
|
}
|
|
2346
|
+
function definitionGroupSpecForNode(node) {
|
|
2347
|
+
return SKILL_MAP_DEFINITION_GROUPS.find((group) => group.nodeKinds.includes(node.nodeKind));
|
|
2348
|
+
}
|
|
2349
|
+
function definitionGroupLabel(group, lang) {
|
|
2350
|
+
return lang === 'zh' ? group.labelZh : group.labelEn;
|
|
2351
|
+
}
|
|
2352
|
+
function definitionGroupDescription(group, lang) {
|
|
2353
|
+
return lang === 'zh' ? group.descriptionZh : group.descriptionEn;
|
|
2354
|
+
}
|
|
2355
|
+
function knowledgeGroupNode(group, children, lang) {
|
|
2356
|
+
const declared = children.filter((node) => node.coverage === 'declared').length;
|
|
2357
|
+
return {
|
|
2358
|
+
nodeKind: 'knowledge_group',
|
|
2359
|
+
label: definitionGroupLabel(group, lang),
|
|
2360
|
+
groupDescription: definitionGroupDescription(group, lang),
|
|
2361
|
+
groupSummary: {
|
|
2362
|
+
total: children.length,
|
|
2363
|
+
declared,
|
|
2364
|
+
},
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
function definitionGroupChildPosition(group, index) {
|
|
2368
|
+
const position = group.childBoundaryPositions[index];
|
|
2369
|
+
if (position)
|
|
2370
|
+
return position;
|
|
2371
|
+
const columns = group.key === 'references'
|
|
2372
|
+
? [120, 290, 460]
|
|
2373
|
+
: group.key === 'workflow'
|
|
2374
|
+
? [660, 830, 1000]
|
|
2375
|
+
: [250, 405, 560, 715, 870];
|
|
2376
|
+
const startY = group.key === 'rules' ? 190 : 500;
|
|
2377
|
+
return {
|
|
2378
|
+
x: columns[index % columns.length],
|
|
2379
|
+
y: startY + Math.floor(index / columns.length) * 92,
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
function standaloneAssetPosition(index) {
|
|
2383
|
+
const position = SKILL_MAP_BOUNDARY_ASSET_COORDS[index];
|
|
2384
|
+
if (position)
|
|
2385
|
+
return position;
|
|
2386
|
+
const columns = [205, 380, 560, 740, 915];
|
|
2387
|
+
return {
|
|
2388
|
+
x: columns[index % columns.length],
|
|
2389
|
+
y: 610 + Math.floor(index / columns.length) * 92,
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
function standaloneAssetPresentation(node, lang) {
|
|
2393
|
+
const zh = lang === 'zh';
|
|
2394
|
+
if (node.nodeKind === 'script') {
|
|
2395
|
+
return {
|
|
2396
|
+
title: zh ? '执行脚本' : 'Script',
|
|
2397
|
+
detailTitle: zh ? '执行脚本' : 'Script',
|
|
2398
|
+
hideKind: true,
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
if (node.nodeKind === 'frontmatter') {
|
|
2402
|
+
return {
|
|
2403
|
+
title: zh ? '元信息' : 'Metadata',
|
|
2404
|
+
detailTitle: zh ? '元信息' : 'Metadata',
|
|
2405
|
+
hideKind: true,
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
return undefined;
|
|
2409
|
+
}
|
|
2410
|
+
function buildDefinitionMapNodes(candidates, lang) {
|
|
2411
|
+
let evidenceIndex = 0;
|
|
2412
|
+
const groupNodes = SKILL_MAP_DEFINITION_GROUPS.flatMap((group) => {
|
|
2413
|
+
const children = candidates.filter((node) => definitionGroupSpecForNode(node)?.key === group.key);
|
|
2414
|
+
if (children.length === 0)
|
|
2415
|
+
return [];
|
|
2416
|
+
const groupId = `definition-group-${group.key}`;
|
|
2417
|
+
const label = definitionGroupLabel(group, lang);
|
|
2418
|
+
const groupNode = {
|
|
2419
|
+
id: groupId,
|
|
2420
|
+
node: knowledgeGroupNode(group, children, lang),
|
|
2421
|
+
layer: 'definition',
|
|
2422
|
+
x: group.boundaryPosition.x,
|
|
2423
|
+
y: group.boundaryPosition.y,
|
|
2424
|
+
boundaryPosition: group.boundaryPosition,
|
|
2425
|
+
evidencePosition: group.evidencePosition,
|
|
2426
|
+
isLeaf: false,
|
|
2427
|
+
moreToggle: {
|
|
2428
|
+
group: groupId,
|
|
2429
|
+
collapsedLabel: lang === 'zh' ? `展开${label}` : `Expand ${label}`,
|
|
2430
|
+
expandedLabel: lang === 'zh' ? `收起${label}` : `Collapse ${label}`,
|
|
2431
|
+
},
|
|
2432
|
+
};
|
|
2433
|
+
const childNodes = children.map((node, index) => {
|
|
2434
|
+
const boundaryPosition = definitionGroupChildPosition(group, index);
|
|
2435
|
+
const evidencePosition = SKILL_MAP_EVIDENCE_DEFINITION_COORDS[evidenceIndex] ?? overflowNodePosition('definition', evidenceIndex - SKILL_MAP_EVIDENCE_DEFINITION_COORDS.length, 'evidence');
|
|
2436
|
+
evidenceIndex += 1;
|
|
2437
|
+
return {
|
|
2438
|
+
id: `${groupId}-node-${index}`,
|
|
2439
|
+
node,
|
|
2440
|
+
layer: 'definition',
|
|
2441
|
+
edgeFrom: group.boundaryPosition,
|
|
2442
|
+
edgeFromId: groupId,
|
|
2443
|
+
overflowGroup: groupId,
|
|
2444
|
+
x: boundaryPosition.x,
|
|
2445
|
+
y: boundaryPosition.y,
|
|
2446
|
+
boundaryPosition,
|
|
2447
|
+
evidencePosition,
|
|
2448
|
+
};
|
|
2449
|
+
});
|
|
2450
|
+
return [groupNode, ...childNodes];
|
|
2451
|
+
});
|
|
2452
|
+
const standaloneAssets = candidates
|
|
2453
|
+
.filter((node) => node.nodeKind === 'script' || node.nodeKind === 'frontmatter')
|
|
2454
|
+
.map((node, index) => {
|
|
2455
|
+
const boundaryPosition = standaloneAssetPosition(index);
|
|
2456
|
+
const evidencePosition = SKILL_MAP_EVIDENCE_DEFINITION_COORDS[evidenceIndex] ?? overflowNodePosition('definition', evidenceIndex - SKILL_MAP_EVIDENCE_DEFINITION_COORDS.length, 'evidence');
|
|
2457
|
+
evidenceIndex += 1;
|
|
2458
|
+
return {
|
|
2459
|
+
id: `definition-asset-${index}`,
|
|
2460
|
+
node,
|
|
2461
|
+
layer: 'definition',
|
|
2462
|
+
x: boundaryPosition.x,
|
|
2463
|
+
y: boundaryPosition.y,
|
|
2464
|
+
boundaryPosition,
|
|
2465
|
+
evidencePosition,
|
|
2466
|
+
presentation: standaloneAssetPresentation(node, lang),
|
|
2467
|
+
};
|
|
2468
|
+
});
|
|
2469
|
+
return [...groupNodes, ...standaloneAssets];
|
|
2470
|
+
}
|
|
1744
2471
|
function skillMapDimensions(nodes) {
|
|
2472
|
+
const allPositions = nodes.flatMap((node) => [
|
|
2473
|
+
{ x: node.x, y: node.y },
|
|
2474
|
+
...(node.boundaryPosition ? [node.boundaryPosition] : []),
|
|
2475
|
+
...(node.evidencePosition ? [node.evidencePosition] : []),
|
|
2476
|
+
]);
|
|
1745
2477
|
return {
|
|
1746
|
-
width: Math.max(SKILL_MAP_WIDTH, ...
|
|
1747
|
-
height: Math.max(SKILL_MAP_HEIGHT, ...
|
|
2478
|
+
width: Math.max(SKILL_MAP_WIDTH, ...allPositions.map((node) => node.x + SKILL_MAP_NODE_MARGIN.x)),
|
|
2479
|
+
height: Math.max(SKILL_MAP_HEIGHT, ...allPositions.map((node) => node.y + SKILL_MAP_NODE_MARGIN.y)),
|
|
1748
2480
|
};
|
|
1749
2481
|
}
|
|
1750
2482
|
function renderSkillStageRail(entry, lang) {
|
|
@@ -1763,6 +2495,24 @@ function renderSkillStageRail(entry, lang) {
|
|
|
1763
2495
|
${card('observe', stageText(entry, 'observe', lang), observeStatus)}
|
|
1764
2496
|
</div>`;
|
|
1765
2497
|
}
|
|
2498
|
+
function renderSkillMapEvidenceStrip(entry, doctor, evalGraph, lang) {
|
|
2499
|
+
const zh = lang === 'zh';
|
|
2500
|
+
const failedSamples = entry.eval?.failCount ?? 0;
|
|
2501
|
+
const totalSamples = entry.eval?.totalSamples ?? evalGraph?.samples ?? 0;
|
|
2502
|
+
const declaredKeys = new Set(evalGraph?.declaredCoverageStableKeys ?? []);
|
|
2503
|
+
const coverable = doctor?.definitionNodes.filter((node) => node.stableKey && DEFINITION_PREVIEW_KINDS.has(node.nodeKind)) ?? [];
|
|
2504
|
+
const declaredCount = coverable.filter((node) => node.stableKey && declaredKeys.has(node.stableKey)).length;
|
|
2505
|
+
const undeclaredCount = coverable.filter((node) => node.stableKey && !declaredKeys.has(node.stableKey)).length;
|
|
2506
|
+
const item = (key, value, tone = '') => `<div class="sm-evidence-item${tone ? ` sm-evidence-item--${tone}` : ''}">
|
|
2507
|
+
<div class="sm-evidence-k">${e(key)}</div>
|
|
2508
|
+
<div class="sm-evidence-v">${e(value)}</div>
|
|
2509
|
+
</div>`;
|
|
2510
|
+
return `<div class="sm-evidence-strip" aria-label="${zh ? '图谱诊断摘要' : 'Map diagnosis summary'}">
|
|
2511
|
+
${item(zh ? '边界覆盖' : 'Boundary coverage', coverable.length > 0 ? `${declaredCount}/${coverable.length} ${zh ? '个结构节点' : 'structure nodes'}` : (zh ? '暂无结构节点' : 'no structure nodes'))}
|
|
2512
|
+
${item(zh ? '未声明' : 'Undeclared', coverable.length > 0 ? `${undeclaredCount} ${zh ? '个结构节点' : 'structure nodes'}` : (zh ? '暂无' : 'none'), undeclaredCount > 0 ? 'action' : '')}
|
|
2513
|
+
${item(zh ? '失败证据' : 'Failed evidence', totalSamples > 0 ? `${failedSamples}/${totalSamples} ${zh ? '条评测用例' : 'eval samples'}` : (zh ? '暂无评测用例' : 'no eval samples'), failedSamples > 0 ? 'risk' : '')}
|
|
2514
|
+
</div>`;
|
|
2515
|
+
}
|
|
1766
2516
|
function orderedPreviewNodes(nodes, allowedKinds, priority) {
|
|
1767
2517
|
return (nodes ?? [])
|
|
1768
2518
|
.map((node, index) => ({ node, index }))
|
|
@@ -1770,16 +2520,30 @@ function orderedPreviewNodes(nodes, allowedKinds, priority) {
|
|
|
1770
2520
|
.sort((a, b) => (priority[a.node.nodeKind] ?? 99) - (priority[b.node.nodeKind] ?? 99) || a.index - b.index)
|
|
1771
2521
|
.map(({ node }) => node);
|
|
1772
2522
|
}
|
|
1773
|
-
function
|
|
2523
|
+
function measurementStatusPriority(node) {
|
|
2524
|
+
if (node.status === 'failed')
|
|
2525
|
+
return 0;
|
|
2526
|
+
if (node.status === 'warning')
|
|
2527
|
+
return 1;
|
|
2528
|
+
if (node.nodeKind === 'sample')
|
|
2529
|
+
return 2;
|
|
2530
|
+
return 3;
|
|
2531
|
+
}
|
|
2532
|
+
function applyDefinitionCoverageDeclarations(nodes, declaredStableKeys, coveredByStableKey) {
|
|
1774
2533
|
if (!nodes)
|
|
1775
2534
|
return undefined;
|
|
1776
|
-
if (!declaredStableKeys || declaredStableKeys.length === 0)
|
|
2535
|
+
if ((!declaredStableKeys || declaredStableKeys.length === 0) && (!coveredByStableKey || coveredByStableKey.size === 0))
|
|
1777
2536
|
return nodes;
|
|
1778
2537
|
const declared = new Set(declaredStableKeys);
|
|
1779
2538
|
return nodes.map((node) => {
|
|
1780
2539
|
if (!node.stableKey || !DEFINITION_PREVIEW_KINDS.has(node.nodeKind))
|
|
1781
2540
|
return node;
|
|
1782
|
-
|
|
2541
|
+
const coveredBySamples = coveredByStableKey?.get(node.stableKey) ?? [];
|
|
2542
|
+
return {
|
|
2543
|
+
...node,
|
|
2544
|
+
coverage: declared.has(node.stableKey) ? 'declared' : 'undeclared',
|
|
2545
|
+
...(coveredBySamples.length > 0 ? { coveredBySamples } : {}),
|
|
2546
|
+
};
|
|
1783
2547
|
});
|
|
1784
2548
|
}
|
|
1785
2549
|
function skillMapCoverageDeclarationSummary(doctor, evalGraph, lang) {
|
|
@@ -1820,7 +2584,7 @@ function renderSkillEvidenceMarkdown(entry, lang) {
|
|
|
1820
2584
|
!entry.observe ? `- ${zh ? '接入观察' : 'Add observation'}${stepSeparator} \`omk observe ingest <trace-dir>\`` : '',
|
|
1821
2585
|
].filter(Boolean);
|
|
1822
2586
|
const coverageSummary = skillMapCoverageDeclarationSummary(doctor, evalGraph, lang);
|
|
1823
|
-
const definitionPreview = previewNodes(applyDefinitionCoverageDeclarations(doctor?.definitionNodes, evalGraph?.declaredCoverageStableKeys), DEFINITION_PREVIEW_KINDS, 8);
|
|
2587
|
+
const definitionPreview = previewNodes(applyDefinitionCoverageDeclarations(doctor?.definitionNodes, evalGraph?.declaredCoverageStableKeys, coverageSamplesByStableKey(evalGraph, lang)), DEFINITION_PREVIEW_KINDS, 8);
|
|
1824
2588
|
const measurementPreview = previewNodes(evalGraph?.measurementNodes, MEASUREMENT_PREVIEW_KINDS, 8);
|
|
1825
2589
|
const previewMermaidLines = [
|
|
1826
2590
|
...definitionPreview.visible.flatMap((node, index) => [
|
|
@@ -1883,7 +2647,8 @@ function renderSkillMapSection(entry, lang) {
|
|
|
1883
2647
|
const evalGraph = graph.eval;
|
|
1884
2648
|
const band = skillMapBand(entry);
|
|
1885
2649
|
const markdown = renderSkillEvidenceMarkdown(entry, lang);
|
|
1886
|
-
const
|
|
2650
|
+
const coverageSamples = coverageSamplesByStableKey(evalGraph, lang);
|
|
2651
|
+
const definitionNodesWithCoverageDeclarations = applyDefinitionCoverageDeclarations(doctor?.definitionNodes, evalGraph?.declaredCoverageStableKeys, coverageSamples);
|
|
1887
2652
|
const coverageSummary = skillMapCoverageDeclarationSummary(doctor, evalGraph, lang);
|
|
1888
2653
|
const definitionCandidates = orderedPreviewNodes(definitionNodesWithCoverageDeclarations, DEFINITION_PREVIEW_KINDS, {
|
|
1889
2654
|
reference: 1,
|
|
@@ -1897,53 +2662,27 @@ function renderSkillMapSection(entry, lang) {
|
|
|
1897
2662
|
sample: 1,
|
|
1898
2663
|
diagnostic: 2,
|
|
1899
2664
|
assertion: 3,
|
|
1900
|
-
});
|
|
1901
|
-
const
|
|
1902
|
-
const measurementLimit = SKILL_MAP_MEASUREMENT_COORDS.length - 1;
|
|
1903
|
-
const definitionVisible = definitionCandidates.slice(0, definitionLimit);
|
|
1904
|
-
const definitionOverflow = definitionCandidates.slice(definitionLimit);
|
|
2665
|
+
}).sort((a, b) => measurementStatusPriority(a) - measurementStatusPriority(b));
|
|
2666
|
+
const measurementLimit = SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS.length - 1;
|
|
1905
2667
|
const measurementVisible = measurementCandidates.slice(0, measurementLimit);
|
|
1906
2668
|
const measurementOverflow = measurementCandidates.slice(measurementLimit);
|
|
1907
|
-
const
|
|
1908
|
-
const
|
|
1909
|
-
const definitionNodes = [
|
|
1910
|
-
...definitionVisible.map((node, index) => ({
|
|
1911
|
-
id: `definition-${index}`,
|
|
1912
|
-
node,
|
|
1913
|
-
layer: 'definition',
|
|
1914
|
-
...SKILL_MAP_DEFINITION_COORDS[index],
|
|
1915
|
-
})),
|
|
1916
|
-
...(definitionOverflow.length > 0 ? [{
|
|
1917
|
-
id: 'definition-more',
|
|
1918
|
-
node: moreNode(`+ ${definitionOverflow.length} ${zh ? '更多结构节点' : 'more structure nodes'}`),
|
|
1919
|
-
layer: 'definition',
|
|
1920
|
-
moreToggle: {
|
|
1921
|
-
group: 'definition',
|
|
1922
|
-
collapsedLabel: `+ ${definitionOverflow.length} ${zh ? '更多结构节点' : 'more structure nodes'}`,
|
|
1923
|
-
expandedLabel: zh ? '收起结构节点' : 'collapse structure nodes',
|
|
1924
|
-
},
|
|
1925
|
-
...definitionMorePosition,
|
|
1926
|
-
}] : []),
|
|
1927
|
-
...definitionOverflow.map((node, index) => ({
|
|
1928
|
-
id: `definition-overflow-${index}`,
|
|
1929
|
-
node,
|
|
1930
|
-
layer: 'definition',
|
|
1931
|
-
edgeFrom: definitionMorePosition,
|
|
1932
|
-
overflowGroup: 'definition',
|
|
1933
|
-
...overflowNodePosition('definition', index),
|
|
1934
|
-
})),
|
|
1935
|
-
];
|
|
2669
|
+
const measurementMorePosition = SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS.length - 1];
|
|
2670
|
+
const definitionNodes = buildDefinitionMapNodes(definitionCandidates, lang);
|
|
1936
2671
|
const measurementNodes = [
|
|
1937
2672
|
...measurementVisible.map((node, index) => ({
|
|
1938
2673
|
id: `measurement-${index}`,
|
|
1939
2674
|
node,
|
|
1940
2675
|
layer: 'measurement',
|
|
1941
|
-
...
|
|
2676
|
+
...SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
|
|
2677
|
+
boundaryPosition: SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
|
|
2678
|
+
evidencePosition: SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
|
|
1942
2679
|
})),
|
|
1943
2680
|
...(measurementOverflow.length > 0 ? [{
|
|
1944
2681
|
id: 'measurement-more',
|
|
1945
2682
|
node: moreNode(`+ ${measurementOverflow.length} ${zh ? '更多测量节点' : 'more measurement nodes'}`),
|
|
1946
2683
|
layer: 'measurement',
|
|
2684
|
+
boundaryPosition: measurementMorePosition,
|
|
2685
|
+
evidencePosition: measurementMorePosition,
|
|
1947
2686
|
moreToggle: {
|
|
1948
2687
|
group: 'measurement',
|
|
1949
2688
|
collapsedLabel: `+ ${measurementOverflow.length} ${zh ? '更多测量节点' : 'more measurement nodes'}`,
|
|
@@ -1957,23 +2696,23 @@ function renderSkillMapSection(entry, lang) {
|
|
|
1957
2696
|
layer: 'measurement',
|
|
1958
2697
|
edgeFrom: measurementMorePosition,
|
|
1959
2698
|
overflowGroup: 'measurement',
|
|
1960
|
-
...overflowNodePosition('measurement', index),
|
|
2699
|
+
...overflowNodePosition('measurement', index, 'evidence'),
|
|
2700
|
+
boundaryPosition: overflowNodePosition('measurement', index, 'evidence'),
|
|
2701
|
+
evidencePosition: overflowNodePosition('measurement', index, 'evidence'),
|
|
1961
2702
|
})),
|
|
1962
2703
|
];
|
|
1963
2704
|
const mapNodes = [...definitionNodes, ...measurementNodes];
|
|
2705
|
+
const evidenceLinks = skillMapEvidenceLinks(mapNodes, evalGraph);
|
|
1964
2706
|
const mapDimensions = skillMapDimensions(mapNodes);
|
|
1965
|
-
const topMeta =
|
|
1966
|
-
coverageSummary,
|
|
1967
|
-
!coverageSummary && doctor ? `${doctor.nodeCount} ${zh ? '结构节点' : 'definition nodes'}` : '',
|
|
1968
|
-
!coverageSummary && evalGraph ? `${evalGraph.samples} ${zh ? '用例' : 'samples'}` : '',
|
|
1969
|
-
].filter(Boolean).join(' · ');
|
|
2707
|
+
const topMeta = '';
|
|
1970
2708
|
return `<section id="section-skill-map" class="si-sect si-sect--${band} sm-sect">
|
|
1971
2709
|
<div class="si-sect-h">
|
|
1972
2710
|
<span class="si-sect-title">🗺 ${zh ? 'Skill Map' : 'Skill Map'}</span>
|
|
1973
|
-
|
|
2711
|
+
${topMeta ? `<span class="si-sect-meta">${e(topMeta)}</span>` : ''}
|
|
1974
2712
|
</div>
|
|
1975
|
-
<div class="sm-body" data-sm-map data-sm-base-width="${mapDimensions.width}" data-sm-base-height="${mapDimensions.height}" data-sm-root-x="${
|
|
2713
|
+
<div class="sm-body" data-sm-map data-sm-view="boundary" data-sm-base-width="${mapDimensions.width}" data-sm-base-height="${mapDimensions.height}" data-sm-root-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-root-y="${SKILL_MAP_BOUNDARY_ROOT.y}">
|
|
1976
2714
|
${renderSkillStageRail(entry, lang)}
|
|
2715
|
+
${renderSkillMapEvidenceStrip(entry, doctor, evalGraph, lang)}
|
|
1977
2716
|
<div class="sm-toolbar" aria-label="${zh ? 'Skill Map 控制' : 'Skill Map controls'}">
|
|
1978
2717
|
<div class="sm-tools">
|
|
1979
2718
|
<button class="sm-tool" type="button" data-sm-action="zoom-out" title="${zh ? '缩小' : 'Zoom out'}">−</button>
|
|
@@ -1982,16 +2721,20 @@ function renderSkillMapSection(entry, lang) {
|
|
|
1982
2721
|
<button class="sm-tool" type="button" data-sm-action="reset" title="${zh ? '重置视图' : 'Reset view'}">${zh ? '重置' : 'Reset'}</button>
|
|
1983
2722
|
</div>
|
|
1984
2723
|
<div class="sm-toggles">
|
|
1985
|
-
<
|
|
1986
|
-
|
|
2724
|
+
<div class="sm-view-switch" role="group" aria-label="${zh ? '图谱视图' : 'Map view'}">
|
|
2725
|
+
<button class="sm-view is-active" type="button" data-sm-view-button="boundary" aria-pressed="true">${zh ? '边界图' : 'Boundary'}</button>
|
|
2726
|
+
<button class="sm-view" type="button" data-sm-view-button="evidence" aria-pressed="false">${zh ? '证据视图' : 'Evidence'}</button>
|
|
2727
|
+
</div>
|
|
1987
2728
|
<label class="sm-toggle"><input type="checkbox" data-sm-toggle-leaves checked> ${zh ? '叶子节点' : 'Leaf nodes'}</label>
|
|
1988
2729
|
</div>
|
|
1989
2730
|
</div>
|
|
1990
2731
|
<div class="sm-canvas" data-sm-viewport aria-label="Skill Map">
|
|
1991
2732
|
<div class="sm-graph-stage" data-sm-graph-stage style="width:${mapDimensions.width}px;height:${mapDimensions.height}px">
|
|
1992
2733
|
<div class="sm-graph" data-sm-graph style="width:${mapDimensions.width}px;height:${mapDimensions.height}px">
|
|
1993
|
-
|
|
1994
|
-
<div class="sm-
|
|
2734
|
+
<div class="sm-zone-label sm-zone-label--definition" data-sm-layer="definition">${zh ? '知识边界' : 'Knowledge'}</div>
|
|
2735
|
+
<div class="sm-zone-label sm-zone-label--measurement" data-sm-layer="measurement">${zh ? '评测证据' : 'Evidence'}</div>
|
|
2736
|
+
${renderSkillMapEdges(mapNodes, mapDimensions, evidenceLinks)}
|
|
2737
|
+
<div class="sm-node sm-node--skill" data-sm-root data-sm-node-id="${SKILL_MAP_ROOT_ID}" data-sm-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-y="${SKILL_MAP_BOUNDARY_ROOT.y}" data-sm-origin-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-origin-y="${SKILL_MAP_BOUNDARY_ROOT.y}" data-sm-boundary-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-boundary-y="${SKILL_MAP_BOUNDARY_ROOT.y}" data-sm-evidence-x="${SKILL_MAP_EVIDENCE_ROOT.x}" data-sm-evidence-y="${SKILL_MAP_EVIDENCE_ROOT.y}" data-sm-draggable="1"${renderSkillMapRootDetailAttrs(entry, coverageSummary, lang)} role="button" tabindex="0" aria-label="SKILL.md ${e(entry.skillName)}" style="${positionedNodeStyle(SKILL_MAP_BOUNDARY_ROOT)}">
|
|
1995
2738
|
<div class="sm-node-title">SKILL.md</div>
|
|
1996
2739
|
<div class="sm-node-meta">${e(entry.skillName)}</div>
|
|
1997
2740
|
</div>
|
|
@@ -1999,6 +2742,7 @@ function renderSkillMapSection(entry, lang) {
|
|
|
1999
2742
|
</div>
|
|
2000
2743
|
</div>
|
|
2001
2744
|
</div>
|
|
2745
|
+
${renderSkillMapInspector(entry, coverageSummary, lang)}
|
|
2002
2746
|
<details class="sm-card">
|
|
2003
2747
|
<summary>${zh ? '复制图谱摘要' : 'Copy Skill Map Summary'}</summary>
|
|
2004
2748
|
<textarea readonly>${e(markdown)}</textarea>
|