oh-my-knowledge 0.48.0 → 0.49.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 +50 -18
- package/README.zh.md +55 -23
- package/dist/analysis/coverage-analyzer.d.ts +1 -0
- package/dist/analysis/coverage-analyzer.js +125 -62
- package/dist/analysis/failure-clusterer.js +2 -1
- package/dist/analysis/gap-analyzer.d.ts +2 -2
- package/dist/analysis/gap-analyzer.js +13 -3
- package/dist/analysis/hedging-classifier.d.ts +2 -2
- package/dist/analysis/hedging-classifier.js +3 -4
- package/dist/analysis/report-diagnostics.js +9 -7
- package/dist/analysis/sample-diagnostics.js +6 -6
- package/dist/artifact-graph/doctor.js +15 -7
- package/dist/assets/agent-skills/omk/SKILL.md +27 -7
- package/dist/assets/agent-skills/omk/references/commands.md +18 -17
- package/dist/authoring/evolver.d.ts +10 -6
- package/dist/authoring/evolver.js +496 -83
- package/dist/authoring/generator.d.ts +3 -3
- package/dist/authoring/generator.js +5 -10
- package/dist/authoring/sample-fixer.d.ts +8 -6
- package/dist/authoring/sample-fixer.js +76 -5
- package/dist/cli/commands/doctor.js +31 -14
- package/dist/cli/commands/eval/index.d.ts +3 -0
- package/dist/cli/commands/eval/index.js +163 -17
- package/dist/cli/commands/evolve.d.ts +4 -4
- package/dist/cli/commands/evolve.js +27 -13
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/observe/inbox.js +28 -21
- package/dist/cli/commands/observe/index.js +20 -11
- package/dist/cli/commands/observe/ingest.d.ts +3 -0
- package/dist/cli/commands/observe/ingest.js +30 -2
- package/dist/cli/commands/sample.d.ts +6 -3
- package/dist/cli/commands/sample.js +72 -68
- package/dist/cli/lib/codex-model-hint.d.ts +9 -0
- package/dist/cli/lib/codex-model-hint.js +45 -0
- package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
- package/dist/cli/lib/generation-failure-hint.js +61 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +4 -0
- package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/gen.js +38 -6
- package/dist/cli/lib/i18n-dict/help.js +6 -6
- package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/init.js +13 -9
- package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/run.js +34 -2
- package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
- package/dist/cli/lib/llm-failure-classifier.js +8 -0
- package/dist/cli/lib/parse-run-config.d.ts +6 -5
- package/dist/cli/lib/parse-run-config.js +16 -9
- package/dist/cli/lib/runtime-defaults.d.ts +21 -0
- package/dist/cli/lib/runtime-defaults.js +79 -0
- package/dist/diagnosis/observe-mapper.js +14 -15
- package/dist/diagnosis/observe-producer.js +3 -1
- package/dist/diagnosis/studio-projection.js +14 -7
- package/dist/diagnosis/types.d.ts +2 -0
- package/dist/diagnosis/types.js +12 -0
- package/dist/doctor/endpoint-rule.js +2 -1
- package/dist/eval-core/artifact-file-names.js +18 -1
- package/dist/eval-core/artifact-index.d.ts +7 -11
- package/dist/eval-core/artifact-index.js +139 -80
- package/dist/eval-core/cache.d.ts +12 -3
- package/dist/eval-core/cache.js +89 -29
- package/dist/eval-core/comparability.js +10 -6
- package/dist/eval-core/evaluation-execution.d.ts +2 -1
- package/dist/eval-core/evaluation-execution.js +122 -37
- package/dist/eval-core/evaluation-job.d.ts +4 -1
- package/dist/eval-core/evaluation-job.js +4 -1
- package/dist/eval-core/evaluation-reporting.d.ts +15 -13
- package/dist/eval-core/evaluation-reporting.js +54 -52
- package/dist/eval-core/execution-strategy.d.ts +2 -0
- package/dist/eval-core/execution-strategy.js +11 -9
- package/dist/eval-core/fact-checker.js +15 -7
- package/dist/eval-core/holdout.js +3 -2
- package/dist/eval-core/judge-independence.d.ts +2 -2
- package/dist/eval-core/mock-hook.cjs +23 -6
- package/dist/eval-core/mocks-runtime.js +30 -8
- package/dist/eval-core/report-document.d.ts +12 -0
- package/dist/eval-core/report-document.js +1151 -0
- package/dist/eval-core/report-extensions.d.ts +4 -0
- package/dist/eval-core/report-extensions.js +500 -0
- package/dist/eval-core/report-file-migration.js +7 -2
- package/dist/eval-core/resume-compatibility.d.ts +31 -0
- package/dist/eval-core/resume-compatibility.js +141 -0
- package/dist/eval-core/sample-fingerprint.d.ts +12 -0
- package/dist/eval-core/sample-fingerprint.js +193 -0
- package/dist/eval-core/schema.js +86 -31
- package/dist/eval-core/verdict.d.ts +8 -4
- package/dist/eval-core/verdict.js +24 -10
- package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
- package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
- package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
- package/dist/eval-workflows/evaluation-pipeline.js +12 -7
- package/dist/eval-workflows/run-evaluation.d.ts +9 -7
- package/dist/eval-workflows/run-evaluation.js +79 -51
- package/dist/executors/anthropic-api.js +65 -9
- package/dist/executors/claude-cli.js +16 -79
- package/dist/executors/claude-protocol.d.ts +28 -0
- package/dist/executors/claude-protocol.js +180 -0
- package/dist/executors/claude-sdk-trace.js +56 -28
- package/dist/executors/claude-sdk.d.ts +1 -0
- package/dist/executors/claude-sdk.js +39 -93
- package/dist/executors/codex-cli-trace.js +166 -31
- package/dist/executors/codex-cli.d.ts +6 -8
- package/dist/executors/codex-cli.js +49 -151
- package/dist/executors/codex-protocol.d.ts +24 -0
- package/dist/executors/codex-protocol.js +234 -0
- package/dist/executors/codex-sdk.js +68 -120
- package/dist/executors/gemini.js +88 -13
- package/dist/executors/index.d.ts +2 -3
- package/dist/executors/index.js +5 -3
- package/dist/executors/openai-api.js +70 -9
- package/dist/executors/runtime-fingerprint.js +88 -11
- package/dist/executors/script-command.d.ts +8 -0
- package/dist/executors/script-command.js +87 -0
- package/dist/executors/script.js +202 -29
- package/dist/executors/shared.d.ts +35 -3
- package/dist/executors/shared.js +113 -15
- package/dist/grading/assertions.d.ts +1 -1
- package/dist/grading/assertions.js +19 -9
- package/dist/grading/diagnostic.d.ts +9 -2
- package/dist/grading/diagnostic.js +25 -2
- package/dist/grading/index.js +10 -4
- package/dist/grading/judge.js +19 -6
- package/dist/grading/layered-scores.d.ts +2 -3
- package/dist/grading/layered-scores.js +2 -3
- package/dist/inputs/load-samples.d.ts +1 -2
- package/dist/inputs/load-samples.js +23 -1
- package/dist/inputs/mcp-resolver.js +6 -3
- package/dist/inputs/sample-document.d.ts +11 -0
- package/dist/inputs/sample-document.js +96 -0
- package/dist/managed/evidence.d.ts +1 -0
- package/dist/managed/evidence.js +1 -1
- package/dist/managed/store.js +200 -91
- package/dist/observability/codex-trace-adapter.d.ts +5 -0
- package/dist/observability/codex-trace-adapter.js +850 -0
- package/dist/observability/experience.d.ts +32 -6
- package/dist/observability/experience.js +2695 -459
- package/dist/observability/feedback-matchers.js +16 -1
- package/dist/observability/inbox-view-model.d.ts +1 -1
- package/dist/observability/inbox-view-model.js +19 -14
- package/dist/observability/inbox.d.ts +7 -1
- package/dist/observability/inbox.js +632 -124
- package/dist/observability/problem-patterns.js +2 -0
- package/dist/observability/review-state.d.ts +6 -0
- package/dist/observability/review-state.js +235 -63
- package/dist/observability/skill-chain-advisories.js +1 -1
- package/dist/observability/skill-chain.js +17 -4
- package/dist/observability/skill-health-analyzer.d.ts +32 -7
- package/dist/observability/skill-health-analyzer.js +194 -121
- package/dist/observability/skill-health-report.d.ts +10 -0
- package/dist/observability/skill-health-report.js +620 -0
- package/dist/observability/soft-standards/constants.d.ts +0 -1
- package/dist/observability/soft-standards/constants.js +0 -1
- package/dist/observability/soft-standards/index.d.ts +1 -1
- package/dist/observability/soft-standards/index.js +1 -1
- package/dist/observability/soft-standards/llm-extractor.js +8 -10
- package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
- package/dist/observability/soft-standards/skill-standards-store.js +59 -18
- package/dist/observability/soft-standards/types.d.ts +2 -2
- package/dist/observability/trace-adapter.d.ts +12 -7
- package/dist/observability/trace-adapter.js +11 -9
- package/dist/observability/trace-attribution.d.ts +13 -5
- package/dist/observability/trace-attribution.js +315 -21
- package/dist/observability/trace-ingestion.d.ts +9 -0
- package/dist/observability/trace-ingestion.js +80 -0
- package/dist/observability/trace-ir.d.ts +113 -0
- package/dist/observability/trace-ir.js +87 -0
- package/dist/observability/trace-segmenter.d.ts +19 -6
- package/dist/observability/trace-segmenter.js +377 -196
- package/dist/observability/trace-session-index.d.ts +19 -0
- package/dist/observability/trace-session-index.js +68 -0
- package/dist/observability/trace-source.d.ts +12 -4
- package/dist/observability/trace-source.js +939 -215
- package/dist/renderer/html-renderer.js +37 -6
- package/dist/renderer/icons.js +3 -0
- package/dist/renderer/observation-inbox-renderer.js +208 -90
- package/dist/renderer/skill-detail-renderer.js +452 -109
- package/dist/renderer/skill-health-renderer.js +69 -12
- package/dist/renderer/summary.js +28 -7
- package/dist/renderer/table.js +21 -4
- package/dist/renderer/test-view.d.ts +1 -0
- package/dist/renderer/test-view.js +44 -9
- package/dist/server/indexed-report-store.js +14 -18
- package/dist/server/job-store.js +64 -26
- package/dist/server/report-server.js +190 -78
- package/dist/server/report-store.js +57 -80
- package/dist/server/skill-index.js +143 -49
- package/dist/server/skill-insights.js +44 -5
- package/dist/shared/artifact-graph.d.ts +3 -0
- package/dist/shared/artifact-graph.js +224 -0
- package/dist/shared/assertion-types.d.ts +8 -0
- package/dist/shared/assertion-types.js +46 -0
- package/dist/shared/atomic-json.d.ts +8 -0
- package/dist/shared/atomic-json.js +33 -0
- package/dist/shared/diagnosis-schema.d.ts +9 -0
- package/dist/shared/diagnosis-schema.js +181 -0
- package/dist/shared/doctor-report.d.ts +3 -0
- package/dist/shared/doctor-report.js +103 -0
- package/dist/shared/evaluation-job.d.ts +6 -0
- package/dist/shared/evaluation-job.js +217 -0
- package/dist/shared/executor-result.d.ts +17 -0
- package/dist/shared/executor-result.js +221 -0
- package/dist/shared/file-lock.d.ts +12 -0
- package/dist/shared/file-lock.js +129 -0
- package/dist/shared/json-value.d.ts +5 -0
- package/dist/shared/json-value.js +36 -0
- package/dist/shared/keyed-mutex.d.ts +7 -0
- package/dist/shared/keyed-mutex.js +24 -0
- package/dist/shared/record-count.d.ts +8 -0
- package/dist/shared/record-count.js +43 -0
- package/dist/shared/sample-contract.d.ts +3 -0
- package/dist/shared/sample-contract.js +332 -0
- package/dist/shared/timestamp.d.ts +6 -0
- package/dist/shared/timestamp.js +64 -0
- package/dist/shared/token-usage.d.ts +19 -0
- package/dist/shared/token-usage.js +50 -0
- package/dist/shared/tool-call-status.d.ts +8 -0
- package/dist/shared/tool-call-status.js +28 -0
- package/dist/shared/tool-identity.d.ts +21 -0
- package/dist/shared/tool-identity.js +84 -0
- package/dist/shared/tool-search.js +73 -16
- package/dist/shared/trace-projection.d.ts +5 -0
- package/dist/shared/trace-projection.js +20 -0
- package/dist/shared/trace-source-kind.d.ts +3 -0
- package/dist/shared/trace-source-kind.js +12 -0
- package/dist/types/diagnosis.d.ts +2 -0
- package/dist/types/eval.d.ts +4 -0
- package/dist/types/executor.d.ts +32 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/judge.d.ts +2 -0
- package/dist/types/observability.d.ts +116 -9
- package/dist/types/report.d.ts +58 -6
- package/dist/types/skill-index.d.ts +7 -0
- package/dist/types/trace.d.ts +2 -0
- package/dist/types/trace.js +1 -0
- package/package.json +9 -5
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* 隐藏 modal:每条 insight 一个 + 3 个阶段全量明细各一个
|
|
13
13
|
*/
|
|
14
14
|
import { layout, e, DEFAULT_LANG } from './layout.js';
|
|
15
|
+
import { icon } from './icons.js';
|
|
15
16
|
// v6 之前的 insight × audience × 三视角 抽象在 v7 详情页砍掉了,只保留 sample 维度
|
|
16
17
|
// (renderEvalSection 直接读 evalReport.results,不再走 detectInsights / audience 分组)。
|
|
17
18
|
// 相关常量(SEVERITY_ICON / AUDIENCE_INFO_* / PATCH_TARGET_*)等以后做调试 UI
|
|
@@ -40,7 +41,14 @@ function relTime(ts, lang) {
|
|
|
40
41
|
function effectiveObserveBand(observe) {
|
|
41
42
|
if (!observe)
|
|
42
43
|
return 'gray';
|
|
43
|
-
|
|
44
|
+
if (observe.confidence === 'underpowered')
|
|
45
|
+
return 'gray';
|
|
46
|
+
if (observe.healthBand === 'green'
|
|
47
|
+
&& (observe.toolCallCount ?? 0) > 0
|
|
48
|
+
&& Math.max(0, (observe.toolResolvedCount ?? observe.toolCallCount ?? 0)
|
|
49
|
+
- (observe.toolCancelledCount ?? 0)) < 5)
|
|
50
|
+
return 'gray';
|
|
51
|
+
return observe.healthBand;
|
|
44
52
|
}
|
|
45
53
|
export function assessHealth(entry, insights, lang) {
|
|
46
54
|
// underpowered observe 的色带仅供参考,不能算作一个「可信维度」:否则只有 observe、且段数过少时,
|
|
@@ -408,8 +416,10 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
408
416
|
.sm-evidence-v { font-size:12.5px;color:#182033;font-weight:700;line-height:1.35;word-break:break-word }
|
|
409
417
|
.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 }
|
|
410
418
|
.sm-tools,.sm-toggles { display:flex;align-items:center;gap:6px;flex-wrap:wrap }
|
|
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 }
|
|
419
|
+
.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;display:inline-flex;align-items:center;justify-content:center;gap:5px }
|
|
412
420
|
.sm-tool:hover { background:#f7f8ff;border-color:#c7d2fe }
|
|
421
|
+
.sm-tool svg { flex:none }
|
|
422
|
+
.sm-tool--icon { width:30px;padding:0 }
|
|
413
423
|
.sm-zoom-label { min-width:44px;text-align:center;font-size:11.5px;color:#637083;font-variant-numeric:tabular-nums }
|
|
414
424
|
.sm-view-switch { display:inline-flex;align-items:center;gap:3px;padding:3px;border:1px solid #dbe2ee;border-radius:8px;background:#f8fafc }
|
|
415
425
|
.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 }
|
|
@@ -429,7 +439,7 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
429
439
|
linear-gradient(#fff,#fbfcff);background-size:auto,28px 28px,28px 28px,auto;background-position:0 0 }
|
|
430
440
|
.sm-body[data-sm-view="boundary"] [data-sm-layer="measurement"],
|
|
431
441
|
.sm-body[data-sm-view="boundary"] [data-sm-evidence-edge="1"] { display:none !important }
|
|
432
|
-
.sm-graph-stage { position:relative;width:1100px;height:560px }
|
|
442
|
+
.sm-graph-stage { position:relative;width:1100px;height:560px;margin:0 auto }
|
|
433
443
|
.sm-graph { position:absolute;left:0;top:0;width:1100px;height:560px;transform-origin:0 0;transition:transform .12s ease }
|
|
434
444
|
.sm-graph [hidden] { display:none !important }
|
|
435
445
|
.sm-svg { position:absolute;inset:0;width:100%;height:100%;pointer-events:none }
|
|
@@ -438,9 +448,18 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
|
|
|
438
448
|
.sm-edge--measurement { stroke:#bbc7ff }
|
|
439
449
|
.sm-edge--coverage { stroke:#f59e0b;stroke-width:2;stroke-dasharray:5 5;opacity:.9 }
|
|
440
450
|
.sm-edge--coverage-failed { stroke:#dc2626;stroke-width:2.2;opacity:.92 }
|
|
451
|
+
.sm-body[data-sm-view="evidence"] .sm-node.is-muted { opacity:.2;filter:saturate(.45) }
|
|
452
|
+
.sm-body[data-sm-view="evidence"] .sm-edge.is-muted { opacity:.12 }
|
|
453
|
+
.sm-body[data-sm-view="evidence"] .sm-edge.is-related { opacity:1;stroke-width:2.15 }
|
|
441
454
|
.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
455
|
.sm-zone-label--definition { left:36px;color:#2f7d68;border-color:rgba(47,125,104,.22);background:rgba(244,251,248,.86) }
|
|
443
456
|
.sm-zone-label--measurement { right:36px;color:#4f46e5;border-color:rgba(79,70,229,.22);background:rgba(247,248,255,.86) }
|
|
457
|
+
.sm-flow-label { position:absolute;top:18px;z-index:1;padding:3px 8px;border-radius:10px;background:rgba(255,255,255,.84);border:1px solid #e4e8f1;color:#7b8798;font-size:10px;font-weight:700;line-height:1;pointer-events:none }
|
|
458
|
+
.sm-flow-label--samples { left:326px }
|
|
459
|
+
.sm-flow-label--results { left:621px }
|
|
460
|
+
.sm-flow-label--targets { left:886px;color:#2f7d68;border-color:rgba(47,125,104,.22) }
|
|
461
|
+
.sm-body[data-sm-view="boundary"] [data-sm-evidence-only] { display:none !important }
|
|
462
|
+
.sm-body[data-sm-view="evidence"] .sm-zone-label { display:none !important }
|
|
444
463
|
.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) }
|
|
445
464
|
.sm-node[data-sm-draggable="1"] { cursor:grab;touch-action:none;user-select:none }
|
|
446
465
|
.sm-node[data-sm-draggable="1"].is-dragging { cursor:grabbing;z-index:3;box-shadow:0 16px 34px rgba(31,41,55,14%) }
|
|
@@ -904,6 +923,8 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
904
923
|
var rootX = Number(map.getAttribute('data-sm-root-x')) || 470;
|
|
905
924
|
var rootY = Number(map.getAttribute('data-sm-root-y')) || 260;
|
|
906
925
|
var zoom = 1;
|
|
926
|
+
var activeWidth = baseWidth;
|
|
927
|
+
var activeHeight = baseHeight;
|
|
907
928
|
var dragState = null;
|
|
908
929
|
var suppressMoreClickUntil = 0;
|
|
909
930
|
var selectedNodeId = '';
|
|
@@ -949,6 +970,24 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
949
970
|
return 'M ' + sx + ' ' + sy + ' C ' + c1x + ' ' + sy + ', ' + c2x + ' ' + ty + ', ' + tx + ' ' + ty;
|
|
950
971
|
}
|
|
951
972
|
|
|
973
|
+
function coveragePathData(fromX, fromY, toX, toY, fromBox, toBox){
|
|
974
|
+
var start = { x: fromX + fromBox.width / 2, y: fromY };
|
|
975
|
+
var end = { x: toX - toBox.width / 2, y: toY };
|
|
976
|
+
var routeY = fromY <= 285 ? Math.max(42, fromY - 92) : Math.min(baseHeight - 42, fromY + 92);
|
|
977
|
+
var leaveX = fromX + 112;
|
|
978
|
+
var approachX = Math.max(leaveX + 80, toX - 105);
|
|
979
|
+
return 'M ' + start.x + ' ' + start.y
|
|
980
|
+
+ ' C ' + leaveX + ' ' + start.y + ', ' + leaveX + ' ' + routeY + ', ' + (leaveX + 48) + ' ' + routeY
|
|
981
|
+
+ ' L ' + approachX + ' ' + routeY
|
|
982
|
+
+ ' C ' + (toX - 70) + ' ' + routeY + ', ' + (toX - 70) + ' ' + end.y + ', ' + end.x + ' ' + end.y;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function edgePathData(edge, fromX, fromY, toX, toY, fromBox, toBox){
|
|
986
|
+
return edge.getAttribute('data-sm-evidence-edge') === '1'
|
|
987
|
+
? coveragePathData(fromX, fromY, toX, toY, fromBox, toBox)
|
|
988
|
+
: pathData(fromX, fromY, toX, toY, fromBox, toBox);
|
|
989
|
+
}
|
|
990
|
+
|
|
952
991
|
function setNodePosition(node, x, y){
|
|
953
992
|
node.setAttribute('data-sm-x', String(Math.round(x * 10) / 10));
|
|
954
993
|
node.setAttribute('data-sm-y', String(Math.round(y * 10) / 10));
|
|
@@ -967,6 +1006,89 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
967
1006
|
};
|
|
968
1007
|
}
|
|
969
1008
|
|
|
1009
|
+
function isVisibleNode(node){
|
|
1010
|
+
return !!node && !node.hidden && node.style.display !== 'none';
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function nodeLayoutPriority(node){
|
|
1014
|
+
if (node.hasAttribute('data-sm-root')) return 0;
|
|
1015
|
+
if (node.classList.contains('is-selected')) return 1;
|
|
1016
|
+
if (node.classList.contains('sm-node--group')) return 2;
|
|
1017
|
+
if (node.hasAttribute('data-sm-more-toggle')) return 3;
|
|
1018
|
+
return 4;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function nodesOverlapAt(node, x, y, other){
|
|
1022
|
+
if (!isVisibleNode(other) || node === other) return false;
|
|
1023
|
+
var box = nodeBox(node, 154, 58);
|
|
1024
|
+
var otherBox = nodeBox(other, 154, 58);
|
|
1025
|
+
var otherCenter = nodeCenter(other, 0, 0);
|
|
1026
|
+
var horizontalGap = 18;
|
|
1027
|
+
var verticalGap = 14;
|
|
1028
|
+
return Math.abs(x - otherCenter.x) < (box.width + otherBox.width) / 2 + horizontalGap
|
|
1029
|
+
&& Math.abs(y - otherCenter.y) < (box.height + otherBox.height) / 2 + verticalGap;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function positionHasCollision(node, x, y, candidates){
|
|
1033
|
+
var others = candidates || Array.prototype.slice.call(graph.querySelectorAll('.sm-node'));
|
|
1034
|
+
return others.some(function(other){ return nodesOverlapAt(node, x, y, other); });
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function nearestOpenPosition(node, desiredX, desiredY, placed){
|
|
1038
|
+
var box = nodeBox(node, 154, 58);
|
|
1039
|
+
var minX = box.width / 2 + 24;
|
|
1040
|
+
var minY = box.height / 2 + 24;
|
|
1041
|
+
var startX = Math.max(minX, desiredX);
|
|
1042
|
+
var startY = Math.max(minY, desiredY);
|
|
1043
|
+
var rootNode = graph.querySelector('[data-sm-root]');
|
|
1044
|
+
var rootCenter = nodeCenter(rootNode, rootX, rootY);
|
|
1045
|
+
var outwardAngle = Math.atan2(startY - rootCenter.y, startX - rootCenter.x);
|
|
1046
|
+
if (!Number.isFinite(outwardAngle)) outwardAngle = 0;
|
|
1047
|
+
|
|
1048
|
+
for (var radius = 0; radius <= 1800; radius += 18) {
|
|
1049
|
+
var pointCount = radius === 0 ? 1 : Math.max(12, Math.ceil(Math.PI * 2 * radius / 28));
|
|
1050
|
+
for (var index = 0; index < pointCount; index += 1) {
|
|
1051
|
+
var offsetIndex = index === 0 ? 0 : Math.ceil(index / 2) * (index % 2 ? 1 : -1);
|
|
1052
|
+
var angle = outwardAngle + offsetIndex * (Math.PI * 2 / pointCount);
|
|
1053
|
+
var x = Math.max(minX, startX + Math.cos(angle) * radius);
|
|
1054
|
+
var y = Math.max(minY, startY + Math.sin(angle) * radius);
|
|
1055
|
+
if (!positionHasCollision(node, x, y, placed)) return { x: x, y: y };
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
var fallbackX = startX;
|
|
1060
|
+
var fallbackY = Math.max(
|
|
1061
|
+
minY,
|
|
1062
|
+
placed.reduce(function(maxY, other){
|
|
1063
|
+
var otherCenter = nodeCenter(other, 0, 0);
|
|
1064
|
+
var otherBox = nodeBox(other, 154, 58);
|
|
1065
|
+
return Math.max(maxY, otherCenter.y + otherBox.height / 2);
|
|
1066
|
+
}, baseHeight) + box.height / 2 + 28
|
|
1067
|
+
);
|
|
1068
|
+
while (positionHasCollision(node, fallbackX, fallbackY, placed)) {
|
|
1069
|
+
fallbackY += box.height + 28;
|
|
1070
|
+
}
|
|
1071
|
+
return { x: fallbackX, y: fallbackY };
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function layoutVisibleNodes(resetToPreferred){
|
|
1075
|
+
var nodes = Array.prototype.slice.call(graph.querySelectorAll('.sm-node'))
|
|
1076
|
+
.filter(isVisibleNode)
|
|
1077
|
+
.sort(function(a, b){
|
|
1078
|
+
return nodeLayoutPriority(a) - nodeLayoutPriority(b);
|
|
1079
|
+
});
|
|
1080
|
+
var placed = [];
|
|
1081
|
+
nodes.forEach(function(node){
|
|
1082
|
+
var desired = resetToPreferred
|
|
1083
|
+
? nodeViewPosition(node, currentView)
|
|
1084
|
+
: nodeCenter(node, rootX, rootY);
|
|
1085
|
+
var open = nearestOpenPosition(node, desired.x, desired.y, placed);
|
|
1086
|
+
setNodePosition(node, open.x, open.y);
|
|
1087
|
+
placed.push(node);
|
|
1088
|
+
});
|
|
1089
|
+
refreshEdges();
|
|
1090
|
+
}
|
|
1091
|
+
|
|
970
1092
|
function hideFilteredNode(node){
|
|
971
1093
|
node.hidden = true;
|
|
972
1094
|
node.toggleAttribute('hidden', true);
|
|
@@ -1009,7 +1131,7 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1009
1131
|
return node.getAttribute(attr) || '';
|
|
1010
1132
|
}
|
|
1011
1133
|
|
|
1012
|
-
function setSelectedNode(node){
|
|
1134
|
+
function setSelectedNode(node, refit){
|
|
1013
1135
|
if (!node || node.hidden) return;
|
|
1014
1136
|
var nodeId = node.getAttribute('data-sm-node-id') || '';
|
|
1015
1137
|
selectedNodeId = nodeId;
|
|
@@ -1019,27 +1141,35 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1019
1141
|
});
|
|
1020
1142
|
node.classList.add('is-selected');
|
|
1021
1143
|
node.setAttribute('aria-pressed', 'true');
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1144
|
+
if (detail) {
|
|
1145
|
+
var title = detailText(node, 'data-sm-detail-title', node.textContent || '');
|
|
1146
|
+
var subtitle = detailText(node, 'data-sm-detail-subtitle', '');
|
|
1147
|
+
var kind = detailText(node, 'data-sm-detail-kind', '');
|
|
1148
|
+
var kindEl = detail.querySelector('[data-sm-detail-kind-label]');
|
|
1149
|
+
var titleEl = detail.querySelector('[data-sm-detail-title]');
|
|
1150
|
+
var subtitleEl = detail.querySelector('[data-sm-detail-subtitle]');
|
|
1151
|
+
if (kindEl) kindEl.textContent = kind;
|
|
1152
|
+
if (titleEl) titleEl.textContent = title;
|
|
1153
|
+
if (subtitleEl) {
|
|
1154
|
+
subtitleEl.textContent = subtitle;
|
|
1155
|
+
subtitleEl.hidden = !subtitle;
|
|
1156
|
+
subtitleEl.toggleAttribute('hidden', !subtitle);
|
|
1157
|
+
}
|
|
1158
|
+
setDetailValue('scope', detailText(node, 'data-sm-detail-scope', ''), true);
|
|
1159
|
+
setDetailValue('source', detailText(node, 'data-sm-detail-source', ''), false);
|
|
1160
|
+
setDetailValue('coverage', detailText(node, 'data-sm-detail-coverage', ''), false);
|
|
1161
|
+
setDetailValue('status', selectedMoreStatus(node) || detailText(node, 'data-sm-detail-status', ''), false);
|
|
1162
|
+
setDetailValue('evidence', detailText(node, 'data-sm-detail-evidence', ''), false);
|
|
1163
|
+
setDetailValue('action', detailText(node, 'data-sm-detail-action', ''), false);
|
|
1035
1164
|
}
|
|
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
1165
|
updateEvidenceFocus();
|
|
1166
|
+
if (refit) {
|
|
1167
|
+
requestAnimationFrame(function(){
|
|
1168
|
+
layoutVisibleNodes(false);
|
|
1169
|
+
fitToViewport();
|
|
1170
|
+
centerRoot();
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1043
1173
|
}
|
|
1044
1174
|
|
|
1045
1175
|
function updateEdgesForNode(nodeId, x, y){
|
|
@@ -1049,7 +1179,8 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1049
1179
|
var fromNode = fromNodeId ? graph.querySelector('[data-sm-node-id="' + fromNodeId + '"]') : graph.querySelector('[data-sm-root]');
|
|
1050
1180
|
var toNode = graph.querySelector('[data-sm-node-id="' + nodeId + '"]');
|
|
1051
1181
|
var fromCenter = nodeCenter(fromNode, rootX, rootY);
|
|
1052
|
-
edge.setAttribute('d',
|
|
1182
|
+
edge.setAttribute('d', edgePathData(
|
|
1183
|
+
edge,
|
|
1053
1184
|
fromCenter.x,
|
|
1054
1185
|
fromCenter.y,
|
|
1055
1186
|
x,
|
|
@@ -1069,7 +1200,8 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1069
1200
|
var toCenter = nodeCenter(toNode, 0, 0);
|
|
1070
1201
|
edge.setAttribute('data-sm-edge-from-x', String(Math.round(x * 10) / 10));
|
|
1071
1202
|
edge.setAttribute('data-sm-edge-from-y', String(Math.round(y * 10) / 10));
|
|
1072
|
-
edge.setAttribute('d',
|
|
1203
|
+
edge.setAttribute('d', edgePathData(
|
|
1204
|
+
edge,
|
|
1073
1205
|
x,
|
|
1074
1206
|
y,
|
|
1075
1207
|
toCenter.x,
|
|
@@ -1092,6 +1224,7 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1092
1224
|
|
|
1093
1225
|
function resetNodePositions(){
|
|
1094
1226
|
applyViewPositions(currentView);
|
|
1227
|
+
layoutVisibleNodes(false);
|
|
1095
1228
|
}
|
|
1096
1229
|
|
|
1097
1230
|
function applyViewPositions(view){
|
|
@@ -1105,8 +1238,12 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1105
1238
|
}
|
|
1106
1239
|
|
|
1107
1240
|
function applyZoom(){
|
|
1108
|
-
|
|
1109
|
-
|
|
1241
|
+
var svg = graph.querySelector('.sm-svg');
|
|
1242
|
+
if (svg) svg.setAttribute('viewBox', '0 0 ' + activeWidth + ' ' + activeHeight);
|
|
1243
|
+
graph.style.width = activeWidth + 'px';
|
|
1244
|
+
graph.style.height = activeHeight + 'px';
|
|
1245
|
+
stage.style.width = Math.ceil(activeWidth * zoom) + 'px';
|
|
1246
|
+
stage.style.height = Math.ceil(activeHeight * zoom) + 'px';
|
|
1110
1247
|
graph.style.transform = 'scale(' + zoom + ')';
|
|
1111
1248
|
if (label) label.textContent = Math.round(zoom * 100) + '%';
|
|
1112
1249
|
}
|
|
@@ -1123,6 +1260,29 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1123
1260
|
viewport.scrollTop = Math.max(0, graphY * zoom - y);
|
|
1124
1261
|
}
|
|
1125
1262
|
|
|
1263
|
+
function fitToViewport(){
|
|
1264
|
+
var maxX = 0;
|
|
1265
|
+
var maxY = 0;
|
|
1266
|
+
graph.querySelectorAll('.sm-node').forEach(function(node){
|
|
1267
|
+
if (node.hidden || node.style.display === 'none') return;
|
|
1268
|
+
var center = nodeCenter(node, 0, 0);
|
|
1269
|
+
var box = nodeBox(node, 154, 58);
|
|
1270
|
+
maxX = Math.max(maxX, center.x + box.width / 2 + 24);
|
|
1271
|
+
maxY = Math.max(maxY, center.y + box.height / 2 + 24);
|
|
1272
|
+
});
|
|
1273
|
+
graph.querySelectorAll('[data-sm-evidence-only]').forEach(function(labelNode){
|
|
1274
|
+
if (labelNode.hidden || labelNode.style.display === 'none') return;
|
|
1275
|
+
maxX = Math.max(maxX, labelNode.offsetLeft + labelNode.offsetWidth + 24);
|
|
1276
|
+
maxY = Math.max(maxY, labelNode.offsetTop + labelNode.offsetHeight + 24);
|
|
1277
|
+
});
|
|
1278
|
+
activeWidth = Math.max(560, maxX);
|
|
1279
|
+
activeHeight = Math.max(420, maxY);
|
|
1280
|
+
var availableWidth = Math.max(1, viewport.clientWidth - 24);
|
|
1281
|
+
var availableHeight = Math.max(1, viewport.clientHeight - 24);
|
|
1282
|
+
zoom = clamp(Math.min(availableWidth / activeWidth, availableHeight / activeHeight, 1), 0.65, 1);
|
|
1283
|
+
applyZoom();
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1126
1286
|
function centerRoot(){
|
|
1127
1287
|
var rootNode = graph.querySelector('[data-sm-root]');
|
|
1128
1288
|
var rootCenter = nodeCenter(rootNode, rootX, rootY);
|
|
@@ -1153,15 +1313,52 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1153
1313
|
node.removeAttribute('data-sm-evidence-revealed');
|
|
1154
1314
|
if (node.getAttribute('data-sm-layer') === 'definition' && currentView === 'evidence') hideFilteredNode(node);
|
|
1155
1315
|
});
|
|
1156
|
-
graph.querySelectorAll('.sm-node.is-related').forEach(function(node){
|
|
1316
|
+
graph.querySelectorAll('.sm-node.is-related,.sm-node.is-muted').forEach(function(node){
|
|
1157
1317
|
node.classList.remove('is-related');
|
|
1318
|
+
node.classList.remove('is-muted');
|
|
1319
|
+
});
|
|
1320
|
+
map.querySelectorAll('.sm-edge.is-related,.sm-edge.is-muted').forEach(function(edge){
|
|
1321
|
+
edge.classList.remove('is-related');
|
|
1322
|
+
edge.classList.remove('is-muted');
|
|
1158
1323
|
});
|
|
1324
|
+
var selectedNode = selectedNodeId
|
|
1325
|
+
? graph.querySelector('[data-sm-node-id="' + selectedNodeId + '"]')
|
|
1326
|
+
: null;
|
|
1327
|
+
var focusedChainId = selectedNode
|
|
1328
|
+
? (selectedNode.getAttribute('data-sm-chain') || selectedNode.getAttribute('data-sm-parent-sample') || '')
|
|
1329
|
+
: '';
|
|
1330
|
+
if (!focusedChainId && selectedNodeId && currentView === 'evidence') {
|
|
1331
|
+
var selectedCoverageEdge = map.querySelector('[data-sm-evidence-edge="1"][data-sm-edge-to="' + selectedNodeId + '"]');
|
|
1332
|
+
if (selectedCoverageEdge) focusedChainId = selectedCoverageEdge.getAttribute('data-sm-edge-from-node') || '';
|
|
1333
|
+
}
|
|
1334
|
+
var relatedLabel = graph.querySelector('[data-sm-related-label]');
|
|
1335
|
+
if (relatedLabel) {
|
|
1336
|
+
var showRelatedLabel = currentView === 'evidence' && !!focusedChainId;
|
|
1337
|
+
relatedLabel.hidden = !showRelatedLabel;
|
|
1338
|
+
relatedLabel.toggleAttribute('hidden', !showRelatedLabel);
|
|
1339
|
+
relatedLabel.setAttribute('aria-hidden', showRelatedLabel ? 'false' : 'true');
|
|
1340
|
+
relatedLabel.style.display = showRelatedLabel ? '' : 'none';
|
|
1341
|
+
}
|
|
1342
|
+
if (currentView === 'evidence' && focusedChainId) {
|
|
1343
|
+
graph.querySelectorAll('[data-sm-layer="measurement"][data-sm-chain]').forEach(function(node){
|
|
1344
|
+
var related = node.getAttribute('data-sm-chain') === focusedChainId;
|
|
1345
|
+
node.classList.toggle('is-muted', !related);
|
|
1346
|
+
if (related && node.getAttribute('data-sm-node-id') !== selectedNodeId) node.classList.add('is-related');
|
|
1347
|
+
});
|
|
1348
|
+
map.querySelectorAll('.sm-edge--measurement[data-sm-chain]').forEach(function(edge){
|
|
1349
|
+
var related = edge.getAttribute('data-sm-chain') === focusedChainId;
|
|
1350
|
+
edge.classList.toggle('is-muted', !related);
|
|
1351
|
+
edge.classList.toggle('is-related', related);
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1159
1354
|
map.querySelectorAll('[data-sm-evidence-edge="1"]').forEach(function(edge){
|
|
1160
1355
|
var toId = edge.getAttribute('data-sm-edge-to');
|
|
1161
1356
|
var fromId = edge.getAttribute('data-sm-edge-from-node');
|
|
1162
1357
|
var toNode = toId ? graph.querySelector('[data-sm-node-id="' + toId + '"]') : null;
|
|
1163
1358
|
var fromNode = fromId ? graph.querySelector('[data-sm-node-id="' + fromId + '"]') : null;
|
|
1164
|
-
var focused = currentView === 'evidence'
|
|
1359
|
+
var focused = currentView === 'evidence'
|
|
1360
|
+
&& !!selectedNodeId
|
|
1361
|
+
&& (toId === selectedNodeId || fromId === selectedNodeId || fromId === focusedChainId);
|
|
1165
1362
|
if (focused) {
|
|
1166
1363
|
if (toNode) revealNode(toNode, true);
|
|
1167
1364
|
if (fromNode) revealNode(fromNode, true);
|
|
@@ -1173,10 +1370,12 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1173
1370
|
edge.setAttribute('aria-hidden', shouldHide ? 'true' : 'false');
|
|
1174
1371
|
edge.style.display = shouldHide ? 'none' : '';
|
|
1175
1372
|
if (!shouldHide) {
|
|
1373
|
+
edge.classList.add('is-related');
|
|
1176
1374
|
if (toNode && toId !== selectedNodeId) toNode.classList.add('is-related');
|
|
1177
1375
|
if (fromNode && fromId !== selectedNodeId) fromNode.classList.add('is-related');
|
|
1178
1376
|
}
|
|
1179
1377
|
});
|
|
1378
|
+
layoutVisibleNodes(false);
|
|
1180
1379
|
}
|
|
1181
1380
|
|
|
1182
1381
|
function updateVisibility(){
|
|
@@ -1228,6 +1427,7 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1228
1427
|
updateViewButtons();
|
|
1229
1428
|
updateVisibility();
|
|
1230
1429
|
refreshEdges();
|
|
1430
|
+
fitToViewport();
|
|
1231
1431
|
centerRoot();
|
|
1232
1432
|
}
|
|
1233
1433
|
|
|
@@ -1241,6 +1441,8 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1241
1441
|
updateVisibility();
|
|
1242
1442
|
refreshEdges();
|
|
1243
1443
|
if (selectedNodeId && toggle.getAttribute('data-sm-node-id') === selectedNodeId) setSelectedNode(toggle);
|
|
1444
|
+
fitToViewport();
|
|
1445
|
+
centerRoot();
|
|
1244
1446
|
}
|
|
1245
1447
|
|
|
1246
1448
|
function collapseMoreGroups(){
|
|
@@ -1255,10 +1457,9 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1255
1457
|
if (action === 'zoom-in') setZoom(zoom + 0.15);
|
|
1256
1458
|
if (action === 'zoom-out') setZoom(zoom - 0.15);
|
|
1257
1459
|
if (action === 'reset') {
|
|
1258
|
-
zoom = 1;
|
|
1259
1460
|
collapseMoreGroups();
|
|
1260
1461
|
resetNodePositions();
|
|
1261
|
-
|
|
1462
|
+
fitToViewport();
|
|
1262
1463
|
centerRoot();
|
|
1263
1464
|
}
|
|
1264
1465
|
});
|
|
@@ -1271,7 +1472,11 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1271
1472
|
});
|
|
1272
1473
|
|
|
1273
1474
|
map.querySelectorAll('[data-sm-toggle-leaves]').forEach(function(input){
|
|
1274
|
-
input.addEventListener('change',
|
|
1475
|
+
input.addEventListener('change', function(){
|
|
1476
|
+
updateVisibility();
|
|
1477
|
+
fitToViewport();
|
|
1478
|
+
centerRoot();
|
|
1479
|
+
});
|
|
1275
1480
|
});
|
|
1276
1481
|
|
|
1277
1482
|
graph.querySelectorAll('[data-sm-more-toggle]').forEach(function(toggle){
|
|
@@ -1320,8 +1525,12 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1320
1525
|
var deltaX = ev.clientX - dragState.startClientX;
|
|
1321
1526
|
var deltaY = ev.clientY - dragState.startClientY;
|
|
1322
1527
|
if (Math.sqrt(deltaX * deltaX + deltaY * deltaY) > 3) dragState.moved = true;
|
|
1323
|
-
var
|
|
1324
|
-
var
|
|
1528
|
+
var dragBox = nodeBox(node, 154, 58);
|
|
1529
|
+
var horizontalMargin = dragBox.width / 2 + 24;
|
|
1530
|
+
var verticalMargin = dragBox.height / 2 + 24;
|
|
1531
|
+
var nextX = clamp(dragState.startX + deltaX / zoom, horizontalMargin, Math.max(baseWidth, activeWidth) - horizontalMargin);
|
|
1532
|
+
var nextY = clamp(dragState.startY + deltaY / zoom, verticalMargin, Math.max(baseHeight, activeHeight) - verticalMargin);
|
|
1533
|
+
if (positionHasCollision(node, nextX, nextY)) return;
|
|
1325
1534
|
setNodePosition(node, nextX, nextY);
|
|
1326
1535
|
updateEdgesForNode(dragState.nodeId, nextX, nextY);
|
|
1327
1536
|
updateEdgesFromNode(dragState.nodeId, nextX, nextY);
|
|
@@ -1351,13 +1560,13 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1351
1560
|
graph.querySelectorAll('[data-sm-node-id]').forEach(function(node){
|
|
1352
1561
|
node.addEventListener('click', function(){
|
|
1353
1562
|
if (node.hasAttribute('data-sm-more-toggle') && Date.now() < suppressMoreClickUntil) return;
|
|
1354
|
-
setSelectedNode(node);
|
|
1563
|
+
setSelectedNode(node, true);
|
|
1355
1564
|
});
|
|
1356
1565
|
node.addEventListener('keydown', function(ev){
|
|
1357
1566
|
if (ev.key !== 'Enter' && ev.key !== ' ') return;
|
|
1358
1567
|
if (node.hasAttribute('data-sm-more-toggle')) return;
|
|
1359
1568
|
ev.preventDefault();
|
|
1360
|
-
setSelectedNode(node);
|
|
1569
|
+
setSelectedNode(node, true);
|
|
1361
1570
|
});
|
|
1362
1571
|
});
|
|
1363
1572
|
|
|
@@ -1370,12 +1579,12 @@ const SKILL_MAP_INIT_SCRIPT = `
|
|
|
1370
1579
|
}, { passive: false });
|
|
1371
1580
|
|
|
1372
1581
|
updateViewButtons();
|
|
1373
|
-
applyZoom();
|
|
1374
1582
|
updateVisibility();
|
|
1375
1583
|
var initialRoot = graph.querySelector('[data-sm-root]');
|
|
1376
1584
|
if (initialRoot) setSelectedNode(initialRoot);
|
|
1377
1585
|
requestAnimationFrame(function(){
|
|
1378
1586
|
refreshEdges();
|
|
1587
|
+
fitToViewport();
|
|
1379
1588
|
centerRoot();
|
|
1380
1589
|
});
|
|
1381
1590
|
}
|
|
@@ -1689,6 +1898,25 @@ function renderObserveSection(snap, langQ, lang) {
|
|
|
1689
1898
|
}
|
|
1690
1899
|
const gapPct = (snap.gapRate * 100).toFixed(0);
|
|
1691
1900
|
const failPct = (snap.failureRate * 100).toFixed(1);
|
|
1901
|
+
const hasToolCallCount = snap.toolCallCount !== undefined;
|
|
1902
|
+
const toolCallCount = snap.toolCallCount ?? 0;
|
|
1903
|
+
const noToolCalls = hasToolCallCount && toolCallCount === 0;
|
|
1904
|
+
const toolResolvedCount = snap.toolResolvedCount
|
|
1905
|
+
?? (hasToolCallCount ? toolCallCount : undefined);
|
|
1906
|
+
const toolCancelledCount = snap.toolCancelledCount ?? 0;
|
|
1907
|
+
const toolComparableCount = toolResolvedCount === undefined
|
|
1908
|
+
? undefined
|
|
1909
|
+
: Math.max(0, toolResolvedCount - toolCancelledCount);
|
|
1910
|
+
const toolOutcomeUnknown = snap.stability === 'unknown'
|
|
1911
|
+
|| (toolComparableCount === 0 && toolCallCount > 0);
|
|
1912
|
+
const toolOutcomeUnderpowered = toolComparableCount !== undefined
|
|
1913
|
+
&& toolComparableCount > 0
|
|
1914
|
+
&& toolComparableCount < 5;
|
|
1915
|
+
const toolOutcomeEvidence = hasToolCallCount && toolCallCount > 0
|
|
1916
|
+
? (lang === 'zh'
|
|
1917
|
+
? `${toolComparableCount}/${toolCallCount} 个结果可比较${toolCancelledCount > 0 ? `,${toolCancelledCount} 个取消` : ''}`
|
|
1918
|
+
: `${toolComparableCount}/${toolCallCount} outcomes comparable${toolCancelledCount > 0 ? `, ${toolCancelledCount} cancelled` : ''}`)
|
|
1919
|
+
: '';
|
|
1692
1920
|
const alerts = [];
|
|
1693
1921
|
if (snap.gapRate >= 0.2) {
|
|
1694
1922
|
alerts.push(`<div class="si-rule si-rule--warn">
|
|
@@ -1699,25 +1927,50 @@ function renderObserveSection(snap, langQ, lang) {
|
|
|
1699
1927
|
</div>
|
|
1700
1928
|
</div>`);
|
|
1701
1929
|
}
|
|
1702
|
-
if (
|
|
1930
|
+
if (toolOutcomeUnknown) {
|
|
1931
|
+
alerts.push(`<div class="si-rule si-rule--warn">
|
|
1932
|
+
<div class="si-rule-head">
|
|
1933
|
+
<span class="si-rule-icon">⚠</span>
|
|
1934
|
+
<code class="si-rule-id">${toolCancelledCount > 0
|
|
1935
|
+
? (lang === 'zh' ? '工具调用取消' : 'tool calls cancelled')
|
|
1936
|
+
: (lang === 'zh' ? '工具结果状态未知' : 'tool outcomes unavailable')}</code>
|
|
1937
|
+
<span class="si-rule-msg">${toolCancelledCount > 0
|
|
1938
|
+
? (lang === 'zh' ? '工具调用被取消,没有可比较的成功 / 失败结果' : 'tool calls were cancelled, leaving no comparable success/failure outcomes')
|
|
1939
|
+
: (lang === 'zh' ? '运行时未提供可判定的工具结果,失败率不可测' : 'runtime did not expose resolvable tool outcomes, so failure rate is unavailable')}</span>
|
|
1940
|
+
</div>
|
|
1941
|
+
</div>`);
|
|
1942
|
+
}
|
|
1943
|
+
else if (snap.failureRate >= 0.2) {
|
|
1703
1944
|
alerts.push(`<div class="si-rule si-rule--warn">
|
|
1704
1945
|
<div class="si-rule-head">
|
|
1705
1946
|
<span class="si-rule-icon">⚠</span>
|
|
1706
1947
|
<code class="si-rule-id">${lang === 'zh' ? '工具失败率' : 'tool fail rate'}</code>
|
|
1707
|
-
<span class="si-rule-msg">${failPct}% ${lang === 'zh' ? '
|
|
1948
|
+
<span class="si-rule-msg">${failPct}% ${lang === 'zh' ? '工具调用失败,可能是环境问题或 skill 让 LLM 走错路径' : 'tool calls failing'}${toolOutcomeUnderpowered
|
|
1949
|
+
? (lang === 'zh' ? `;仅 ${toolOutcomeEvidence},结论置信度低` : `; only ${toolOutcomeEvidence}, low confidence`)
|
|
1950
|
+
: ''}</span>
|
|
1708
1951
|
</div>
|
|
1709
1952
|
</div>`);
|
|
1710
1953
|
}
|
|
1711
1954
|
const body = alerts.length > 0
|
|
1712
1955
|
? alerts.join('')
|
|
1713
1956
|
: `<div class="si-sect-allpass">✓ ${lang === 'zh' ? '生产观测健康' : 'production observation healthy'}</div>`;
|
|
1714
|
-
const obsCaveat =
|
|
1715
|
-
|
|
1716
|
-
|
|
1957
|
+
const obsCaveat = [
|
|
1958
|
+
snap.confidence === 'underpowered'
|
|
1959
|
+
? (lang === 'zh' ? '样本不足,色带仅供参考' : 'low N, band indicative')
|
|
1960
|
+
: '',
|
|
1961
|
+
toolOutcomeUnderpowered
|
|
1962
|
+
? (lang === 'zh' ? `${toolOutcomeEvidence},工具失败率置信度低` : `${toolOutcomeEvidence}, low-confidence failure rate`)
|
|
1963
|
+
: '',
|
|
1964
|
+
].filter(Boolean).map((value) => ` · ⚠ ${value}`).join('');
|
|
1965
|
+
const toolFailureMeta = toolOutcomeUnknown
|
|
1966
|
+
? (lang === 'zh' ? '工具失败率不可测' : 'tool fail unavailable')
|
|
1967
|
+
: noToolCalls
|
|
1968
|
+
? (lang === 'zh' ? '无工具调用' : 'no tool calls')
|
|
1969
|
+
: `${lang === 'zh' ? '工具失败率' : 'tool fail'} ${failPct}%${toolOutcomeEvidence ? `(${toolOutcomeEvidence})` : ''}`;
|
|
1717
1970
|
return `<section id="section-observe" class="si-sect si-sect--${effectiveObserveBand(snap)}">
|
|
1718
1971
|
<div class="si-sect-h">
|
|
1719
1972
|
<span class="si-sect-title">👁 ${lang === 'zh' ? '线上观测 (observe)' : 'Live stability (observe)'}</span>
|
|
1720
|
-
<span class="si-sect-meta">${snap.segmentCount} ${lang === 'zh' ? '段' : 'segs'} · gap ${gapPct}% · ${
|
|
1973
|
+
<span class="si-sect-meta">${snap.segmentCount} ${lang === 'zh' ? '段' : 'segs'} · gap ${gapPct}% · ${toolFailureMeta} · ${relTime(snap.generatedAt, lang)}${obsCaveat}</span>
|
|
1721
1974
|
</div>
|
|
1722
1975
|
<div class="si-sect-body">
|
|
1723
1976
|
${body}
|
|
@@ -1726,11 +1979,14 @@ function renderObserveSection(snap, langQ, lang) {
|
|
|
1726
1979
|
</section>`;
|
|
1727
1980
|
}
|
|
1728
1981
|
function skillMapBand(entry) {
|
|
1729
|
-
|
|
1982
|
+
const observeBand = effectiveObserveBand(entry.observe);
|
|
1983
|
+
if (entry.doctor?.failCount || entry.eval?.failCount || observeBand === 'red')
|
|
1730
1984
|
return 'red';
|
|
1731
|
-
if (entry.doctor?.warnCount
|
|
1985
|
+
if (entry.doctor?.warnCount
|
|
1986
|
+
|| (entry.eval?.compositeScore != null && entry.eval.compositeScore < 3.5)
|
|
1987
|
+
|| observeBand === 'yellow')
|
|
1732
1988
|
return 'yellow';
|
|
1733
|
-
if (entry.doctor || entry.eval ||
|
|
1989
|
+
if (entry.doctor || entry.eval || observeBand === 'green')
|
|
1734
1990
|
return 'green';
|
|
1735
1991
|
return 'gray';
|
|
1736
1992
|
}
|
|
@@ -1868,7 +2124,7 @@ const SKILL_MAP_WIDTH = 1100;
|
|
|
1868
2124
|
const SKILL_MAP_HEIGHT = 560;
|
|
1869
2125
|
const SKILL_MAP_ROOT_ID = 'skill-root';
|
|
1870
2126
|
const SKILL_MAP_BOUNDARY_ROOT = { x: 560, y: 285 };
|
|
1871
|
-
const
|
|
2127
|
+
const SKILL_MAP_EVIDENCE_ROOT_X = 125;
|
|
1872
2128
|
const SKILL_MAP_ROOT_SIZE = { width: 172, height: 86 };
|
|
1873
2129
|
const SKILL_MAP_NODE_SIZE = { width: 148, height: 54 };
|
|
1874
2130
|
const SKILL_MAP_DEFINITION_GROUPS = [
|
|
@@ -1932,22 +2188,21 @@ const SKILL_MAP_BOUNDARY_ASSET_COORDS = [
|
|
|
1932
2188
|
{ x: 560, y: 510 },
|
|
1933
2189
|
];
|
|
1934
2190
|
const SKILL_MAP_EVIDENCE_DEFINITION_COORDS = [
|
|
1935
|
-
{ x:
|
|
1936
|
-
{ x:
|
|
1937
|
-
{ x:
|
|
1938
|
-
{ x:
|
|
1939
|
-
{ x:
|
|
1940
|
-
{ x:
|
|
1941
|
-
{ x:
|
|
1942
|
-
|
|
1943
|
-
|
|
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 },
|
|
2191
|
+
{ x: 900, y: 80 },
|
|
2192
|
+
{ x: 900, y: 180 },
|
|
2193
|
+
{ x: 900, y: 280 },
|
|
2194
|
+
{ x: 900, y: 380 },
|
|
2195
|
+
{ x: 900, y: 480 },
|
|
2196
|
+
{ x: 1065, y: 130 },
|
|
2197
|
+
{ x: 1065, y: 230 },
|
|
2198
|
+
{ x: 1065, y: 330 },
|
|
2199
|
+
{ x: 1065, y: 430 },
|
|
1950
2200
|
];
|
|
2201
|
+
const SKILL_MAP_EVIDENCE_SAMPLE_X = 370;
|
|
2202
|
+
const SKILL_MAP_EVIDENCE_RESULT_X = 670;
|
|
2203
|
+
const SKILL_MAP_EVIDENCE_ROW_GAP = 140;
|
|
2204
|
+
const SKILL_MAP_EVIDENCE_CHILD_GAP = 66;
|
|
2205
|
+
const SKILL_MAP_EVIDENCE_START_Y = 100;
|
|
1951
2206
|
const SKILL_MAP_NODE_MARGIN = { x: 92, y: 48 };
|
|
1952
2207
|
const SKILL_MAP_STATUS_CLASSES = new Set(['ok', 'warning', 'failed', 'skipped', 'unknown', 'not_measured']);
|
|
1953
2208
|
function graphNodeStatusClass(node) {
|
|
@@ -2248,7 +2503,9 @@ function renderSkillMapNode(item, lang) {
|
|
|
2248
2503
|
const moreToggle = item.moreToggle
|
|
2249
2504
|
? ` 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"`
|
|
2250
2505
|
: ' role="button" tabindex="0"';
|
|
2251
|
-
|
|
2506
|
+
const chain = item.chainId ? ` data-sm-chain="${e(item.chainId)}"` : '';
|
|
2507
|
+
const parentSample = item.parentSampleId ? ` data-sm-parent-sample="${e(item.parentSampleId)}"` : '';
|
|
2508
|
+
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)}${chain}${parentSample}${leaf}${draggable}${overflow}${moreToggle}${renderSkillMapDetailAttrs(item, lang)} style="${positionedNodeStyle(item)}" title="${e(fullTitle)}" aria-label="${e(fullTitle)}">
|
|
2252
2509
|
${toggleIcon}
|
|
2253
2510
|
${kindLabel ? `<div class="sm-node-kind">${e(kindLabel)}</div>` : ''}
|
|
2254
2511
|
<div class="sm-node-title">${e(title)}</div>
|
|
@@ -2271,6 +2528,16 @@ function skillMapPath(from, to, fromBox, toBox) {
|
|
|
2271
2528
|
const c2x = end.x - (end.x >= start.x ? dx * 0.45 : -dx * 0.45);
|
|
2272
2529
|
return `M ${start.x} ${start.y} C ${c1x} ${start.y}, ${c2x} ${end.y}, ${end.x} ${end.y}`;
|
|
2273
2530
|
}
|
|
2531
|
+
function skillMapCoveragePath(from, to, graphHeight) {
|
|
2532
|
+
const start = { x: from.x + SKILL_MAP_NODE_SIZE.width / 2, y: from.y };
|
|
2533
|
+
const end = { x: to.x - SKILL_MAP_NODE_SIZE.width / 2, y: to.y };
|
|
2534
|
+
const routeY = from.y <= SKILL_MAP_BOUNDARY_ROOT.y
|
|
2535
|
+
? Math.max(42, from.y - 92)
|
|
2536
|
+
: Math.min(graphHeight - 42, from.y + 92);
|
|
2537
|
+
const leaveX = from.x + 112;
|
|
2538
|
+
const approachX = Math.max(leaveX + 80, to.x - 105);
|
|
2539
|
+
return `M ${start.x} ${start.y} C ${leaveX} ${start.y}, ${leaveX} ${routeY}, ${leaveX + 48} ${routeY} L ${approachX} ${routeY} C ${to.x - 70} ${routeY}, ${to.x - 70} ${end.y}, ${end.x} ${end.y}`;
|
|
2540
|
+
}
|
|
2274
2541
|
function renderSkillMapEdges(nodes, dimensions, evidenceLinks) {
|
|
2275
2542
|
const rootEdges = nodes
|
|
2276
2543
|
.map((node) => {
|
|
@@ -2279,11 +2546,12 @@ function renderSkillMapEdges(nodes, dimensions, evidenceLinks) {
|
|
|
2279
2546
|
const overflow = node.overflowGroup ? ` data-sm-overflow-group="${node.overflowGroup}" hidden aria-hidden="true"` : '';
|
|
2280
2547
|
const fromNodeId = node.edgeFromId ?? (node.overflowGroup ? `${node.overflowGroup}-more` : SKILL_MAP_ROOT_ID);
|
|
2281
2548
|
const leaf = node.isLeaf === false ? '' : ' data-sm-leaf="1"';
|
|
2282
|
-
|
|
2549
|
+
const chain = node.chainId ? ` data-sm-chain="${e(node.chainId)}"` : '';
|
|
2550
|
+
return `<path class="sm-edge sm-edge--${node.layer}" data-sm-layer="${node.layer}"${chain}${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>`;
|
|
2283
2551
|
})
|
|
2284
2552
|
.join('');
|
|
2285
2553
|
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="${
|
|
2554
|
+
.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="${skillMapCoveragePath(link.from, link.to, dimensions.height)}" data-sm-edge-index="${index}" hidden aria-hidden="true"></path>`)
|
|
2287
2555
|
.join('');
|
|
2288
2556
|
return `<svg class="sm-svg" viewBox="0 0 ${dimensions.width} ${dimensions.height}" aria-hidden="true">${rootEdges}${evidenceEdges}</svg>`;
|
|
2289
2557
|
}
|
|
@@ -2305,16 +2573,21 @@ function skillMapEvidenceLinks(nodes, evalGraph) {
|
|
|
2305
2573
|
if (!evalGraph?.declaredCoverageEdges.length)
|
|
2306
2574
|
return [];
|
|
2307
2575
|
const targetByStableKey = new Map();
|
|
2576
|
+
const sampleByStableKey = new Map();
|
|
2308
2577
|
const sampleByLabel = new Map();
|
|
2309
2578
|
for (const node of nodes) {
|
|
2310
2579
|
if (node.node.stableKey)
|
|
2311
2580
|
targetByStableKey.set(node.node.stableKey, node);
|
|
2312
|
-
if (node.node.nodeKind === 'sample')
|
|
2581
|
+
if (node.node.nodeKind === 'sample') {
|
|
2582
|
+
if (node.node.stableKey)
|
|
2583
|
+
sampleByStableKey.set(node.node.stableKey, node);
|
|
2313
2584
|
sampleByLabel.set(node.node.label, node);
|
|
2585
|
+
}
|
|
2314
2586
|
}
|
|
2315
2587
|
return evalGraph.declaredCoverageEdges
|
|
2316
2588
|
.map((edge) => {
|
|
2317
|
-
const from =
|
|
2589
|
+
const from = (edge.sampleStableKey ? sampleByStableKey.get(edge.sampleStableKey) : undefined)
|
|
2590
|
+
?? sampleByLabel.get(edge.sampleLabel);
|
|
2318
2591
|
const to = targetByStableKey.get(edge.targetStableKey);
|
|
2319
2592
|
if (!from || !to)
|
|
2320
2593
|
return null;
|
|
@@ -2482,7 +2755,8 @@ function skillMapDimensions(nodes) {
|
|
|
2482
2755
|
function renderSkillStageRail(entry, lang) {
|
|
2483
2756
|
const doctorStatus = entry.doctor?.failCount ? 'failed' : entry.doctor?.warnCount ? 'warning' : entry.doctor ? 'ok' : 'pending';
|
|
2484
2757
|
const evalStatus = entry.eval?.failCount ? 'failed' : entry.eval ? 'ok' : 'pending';
|
|
2485
|
-
const
|
|
2758
|
+
const observeBand = effectiveObserveBand(entry.observe);
|
|
2759
|
+
const observeStatus = observeBand === 'red' ? 'failed' : observeBand === 'yellow' ? 'warning' : observeBand === 'green' ? 'ok' : 'pending';
|
|
2486
2760
|
const card = (key, value, status) => `<div class="sm-stage-card sm-stage-card--${status}">
|
|
2487
2761
|
<div class="sm-stage-k">${e(key)}</div>
|
|
2488
2762
|
<div class="sm-stage-v">${e(value)}</div>
|
|
@@ -2529,6 +2803,107 @@ function measurementStatusPriority(node) {
|
|
|
2529
2803
|
return 2;
|
|
2530
2804
|
return 3;
|
|
2531
2805
|
}
|
|
2806
|
+
function buildMeasurementMapNodes(candidates, lang) {
|
|
2807
|
+
const samples = candidates.filter((node) => node.nodeKind === 'sample').slice(0, 5);
|
|
2808
|
+
const sampleStableKeys = new Set(samples.map((node) => node.stableKey).filter((value) => Boolean(value)));
|
|
2809
|
+
const childrenBySample = new Map();
|
|
2810
|
+
const overflow = candidates.filter((node) => node.nodeKind === 'sample').slice(5);
|
|
2811
|
+
for (const node of candidates.filter((candidate) => candidate.nodeKind !== 'sample')) {
|
|
2812
|
+
const parent = node.parentSampleStableKey;
|
|
2813
|
+
if (!parent || !sampleStableKeys.has(parent)) {
|
|
2814
|
+
overflow.push(node);
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
const children = childrenBySample.get(parent) ?? [];
|
|
2818
|
+
if (children.length < 3)
|
|
2819
|
+
children.push(node);
|
|
2820
|
+
else
|
|
2821
|
+
overflow.push(node);
|
|
2822
|
+
childrenBySample.set(parent, children);
|
|
2823
|
+
}
|
|
2824
|
+
const nodes = [];
|
|
2825
|
+
let cursorY = SKILL_MAP_EVIDENCE_START_Y;
|
|
2826
|
+
for (const [sampleIndex, sample] of samples.entries()) {
|
|
2827
|
+
const sampleId = `measurement-sample-${sampleIndex}`;
|
|
2828
|
+
const children = sample.stableKey ? childrenBySample.get(sample.stableKey) ?? [] : [];
|
|
2829
|
+
const childSpan = Math.max(SKILL_MAP_EVIDENCE_ROW_GAP, (children.length - 1) * SKILL_MAP_EVIDENCE_CHILD_GAP + 84);
|
|
2830
|
+
const sampleY = cursorY + childSpan / 2;
|
|
2831
|
+
const samplePosition = { x: SKILL_MAP_EVIDENCE_SAMPLE_X, y: sampleY };
|
|
2832
|
+
nodes.push({
|
|
2833
|
+
id: sampleId,
|
|
2834
|
+
node: sample,
|
|
2835
|
+
layer: 'measurement',
|
|
2836
|
+
x: samplePosition.x,
|
|
2837
|
+
y: samplePosition.y,
|
|
2838
|
+
boundaryPosition: samplePosition,
|
|
2839
|
+
evidencePosition: samplePosition,
|
|
2840
|
+
chainId: sampleId,
|
|
2841
|
+
});
|
|
2842
|
+
children.forEach((child, childIndex) => {
|
|
2843
|
+
const childY = sampleY + (childIndex - (children.length - 1) / 2) * SKILL_MAP_EVIDENCE_CHILD_GAP;
|
|
2844
|
+
const childPosition = { x: SKILL_MAP_EVIDENCE_RESULT_X, y: childY };
|
|
2845
|
+
nodes.push({
|
|
2846
|
+
id: `${sampleId}-evidence-${childIndex}`,
|
|
2847
|
+
node: child,
|
|
2848
|
+
layer: 'measurement',
|
|
2849
|
+
x: childPosition.x,
|
|
2850
|
+
y: childPosition.y,
|
|
2851
|
+
boundaryPosition: childPosition,
|
|
2852
|
+
evidencePosition: childPosition,
|
|
2853
|
+
edgeFrom: samplePosition,
|
|
2854
|
+
edgeFromId: sampleId,
|
|
2855
|
+
chainId: sampleId,
|
|
2856
|
+
parentSampleId: sampleId,
|
|
2857
|
+
});
|
|
2858
|
+
});
|
|
2859
|
+
cursorY += childSpan;
|
|
2860
|
+
}
|
|
2861
|
+
const samplePositions = nodes.filter((node) => node.node.nodeKind === 'sample').map((node) => node.y);
|
|
2862
|
+
const rootY = samplePositions.length > 0
|
|
2863
|
+
? ((samplePositions[0] ?? SKILL_MAP_BOUNDARY_ROOT.y) + (samplePositions[samplePositions.length - 1] ?? SKILL_MAP_BOUNDARY_ROOT.y)) / 2
|
|
2864
|
+
: SKILL_MAP_BOUNDARY_ROOT.y;
|
|
2865
|
+
const rootPosition = {
|
|
2866
|
+
x: SKILL_MAP_EVIDENCE_ROOT_X,
|
|
2867
|
+
y: Number.isFinite(rootY) ? rootY : SKILL_MAP_BOUNDARY_ROOT.y,
|
|
2868
|
+
};
|
|
2869
|
+
if (overflow.length > 0) {
|
|
2870
|
+
const morePosition = {
|
|
2871
|
+
x: SKILL_MAP_EVIDENCE_SAMPLE_X,
|
|
2872
|
+
y: Math.max(cursorY + 18, rootPosition.y + SKILL_MAP_EVIDENCE_ROW_GAP),
|
|
2873
|
+
};
|
|
2874
|
+
nodes.push({
|
|
2875
|
+
id: 'measurement-more',
|
|
2876
|
+
node: moreNode(`+ ${overflow.length} ${lang === 'zh' ? '更多证据节点' : 'more evidence nodes'}`),
|
|
2877
|
+
layer: 'measurement',
|
|
2878
|
+
x: morePosition.x,
|
|
2879
|
+
y: morePosition.y,
|
|
2880
|
+
boundaryPosition: morePosition,
|
|
2881
|
+
evidencePosition: morePosition,
|
|
2882
|
+
isLeaf: false,
|
|
2883
|
+
moreToggle: {
|
|
2884
|
+
group: 'measurement',
|
|
2885
|
+
collapsedLabel: `+ ${overflow.length} ${lang === 'zh' ? '更多证据节点' : 'more evidence nodes'}`,
|
|
2886
|
+
expandedLabel: lang === 'zh' ? '收起证据节点' : 'collapse evidence nodes',
|
|
2887
|
+
},
|
|
2888
|
+
});
|
|
2889
|
+
overflow.forEach((node, index) => {
|
|
2890
|
+
const position = overflowNodePosition('measurement', index, 'evidence');
|
|
2891
|
+
nodes.push({
|
|
2892
|
+
id: `measurement-overflow-${index}`,
|
|
2893
|
+
node,
|
|
2894
|
+
layer: 'measurement',
|
|
2895
|
+
x: position.x,
|
|
2896
|
+
y: position.y,
|
|
2897
|
+
boundaryPosition: position,
|
|
2898
|
+
evidencePosition: position,
|
|
2899
|
+
edgeFrom: morePosition,
|
|
2900
|
+
edgeFromId: 'measurement-more',
|
|
2901
|
+
overflowGroup: 'measurement',
|
|
2902
|
+
});
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
return { nodes, rootPosition };
|
|
2906
|
+
}
|
|
2532
2907
|
function applyDefinitionCoverageDeclarations(nodes, declaredStableKeys, coveredByStableKey) {
|
|
2533
2908
|
if (!nodes)
|
|
2534
2909
|
return undefined;
|
|
@@ -2663,44 +3038,9 @@ function renderSkillMapSection(entry, lang) {
|
|
|
2663
3038
|
diagnostic: 2,
|
|
2664
3039
|
assertion: 3,
|
|
2665
3040
|
}).sort((a, b) => measurementStatusPriority(a) - measurementStatusPriority(b));
|
|
2666
|
-
const measurementLimit = SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS.length - 1;
|
|
2667
|
-
const measurementVisible = measurementCandidates.slice(0, measurementLimit);
|
|
2668
|
-
const measurementOverflow = measurementCandidates.slice(measurementLimit);
|
|
2669
|
-
const measurementMorePosition = SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS.length - 1];
|
|
2670
3041
|
const definitionNodes = buildDefinitionMapNodes(definitionCandidates, lang);
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
id: `measurement-${index}`,
|
|
2674
|
-
node,
|
|
2675
|
-
layer: 'measurement',
|
|
2676
|
-
...SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
|
|
2677
|
-
boundaryPosition: SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
|
|
2678
|
-
evidencePosition: SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
|
|
2679
|
-
})),
|
|
2680
|
-
...(measurementOverflow.length > 0 ? [{
|
|
2681
|
-
id: 'measurement-more',
|
|
2682
|
-
node: moreNode(`+ ${measurementOverflow.length} ${zh ? '更多测量节点' : 'more measurement nodes'}`),
|
|
2683
|
-
layer: 'measurement',
|
|
2684
|
-
boundaryPosition: measurementMorePosition,
|
|
2685
|
-
evidencePosition: measurementMorePosition,
|
|
2686
|
-
moreToggle: {
|
|
2687
|
-
group: 'measurement',
|
|
2688
|
-
collapsedLabel: `+ ${measurementOverflow.length} ${zh ? '更多测量节点' : 'more measurement nodes'}`,
|
|
2689
|
-
expandedLabel: zh ? '收起测量节点' : 'collapse measurement nodes',
|
|
2690
|
-
},
|
|
2691
|
-
...measurementMorePosition,
|
|
2692
|
-
}] : []),
|
|
2693
|
-
...measurementOverflow.map((node, index) => ({
|
|
2694
|
-
id: `measurement-overflow-${index}`,
|
|
2695
|
-
node,
|
|
2696
|
-
layer: 'measurement',
|
|
2697
|
-
edgeFrom: measurementMorePosition,
|
|
2698
|
-
overflowGroup: 'measurement',
|
|
2699
|
-
...overflowNodePosition('measurement', index, 'evidence'),
|
|
2700
|
-
boundaryPosition: overflowNodePosition('measurement', index, 'evidence'),
|
|
2701
|
-
evidencePosition: overflowNodePosition('measurement', index, 'evidence'),
|
|
2702
|
-
})),
|
|
2703
|
-
];
|
|
3042
|
+
const measurementLayout = buildMeasurementMapNodes(measurementCandidates, lang);
|
|
3043
|
+
const measurementNodes = measurementLayout.nodes;
|
|
2704
3044
|
const mapNodes = [...definitionNodes, ...measurementNodes];
|
|
2705
3045
|
const evidenceLinks = skillMapEvidenceLinks(mapNodes, evalGraph);
|
|
2706
3046
|
const mapDimensions = skillMapDimensions(mapNodes);
|
|
@@ -2715,10 +3055,10 @@ function renderSkillMapSection(entry, lang) {
|
|
|
2715
3055
|
${renderSkillMapEvidenceStrip(entry, doctor, evalGraph, lang)}
|
|
2716
3056
|
<div class="sm-toolbar" aria-label="${zh ? 'Skill Map 控制' : 'Skill Map controls'}">
|
|
2717
3057
|
<div class="sm-tools">
|
|
2718
|
-
<button class="sm-tool" type="button" data-sm-action="zoom-out" title="${zh ? '缩小' : 'Zoom out'}"
|
|
3058
|
+
<button class="sm-tool sm-tool--icon" type="button" data-sm-action="zoom-out" title="${zh ? '缩小' : 'Zoom out'}" aria-label="${zh ? '缩小' : 'Zoom out'}">${icon('zoom-out', { size: 15 })}</button>
|
|
2719
3059
|
<span class="sm-zoom-label" data-sm-zoom-label>100%</span>
|
|
2720
|
-
<button class="sm-tool" type="button" data-sm-action="zoom-in" title="${zh ? '放大' : 'Zoom in'}"
|
|
2721
|
-
<button class="sm-tool" type="button" data-sm-action="reset" title="${zh ? '
|
|
3060
|
+
<button class="sm-tool sm-tool--icon" type="button" data-sm-action="zoom-in" title="${zh ? '放大' : 'Zoom in'}" aria-label="${zh ? '放大' : 'Zoom in'}">${icon('zoom-in', { size: 15 })}</button>
|
|
3061
|
+
<button class="sm-tool" type="button" data-sm-action="reset" title="${zh ? '适配画布并重置节点' : 'Fit canvas and reset nodes'}">${icon('maximize-2', { size: 14 })}${zh ? '适配' : 'Fit'}</button>
|
|
2722
3062
|
</div>
|
|
2723
3063
|
<div class="sm-toggles">
|
|
2724
3064
|
<div class="sm-view-switch" role="group" aria-label="${zh ? '图谱视图' : 'Map view'}">
|
|
@@ -2733,8 +3073,11 @@ function renderSkillMapSection(entry, lang) {
|
|
|
2733
3073
|
<div class="sm-graph" data-sm-graph style="width:${mapDimensions.width}px;height:${mapDimensions.height}px">
|
|
2734
3074
|
<div class="sm-zone-label sm-zone-label--definition" data-sm-layer="definition">${zh ? '知识边界' : 'Knowledge'}</div>
|
|
2735
3075
|
<div class="sm-zone-label sm-zone-label--measurement" data-sm-layer="measurement">${zh ? '评测证据' : 'Evidence'}</div>
|
|
3076
|
+
<div class="sm-flow-label sm-flow-label--samples" data-sm-layer="measurement" data-sm-evidence-only>${zh ? '评测用例' : 'Eval samples'}</div>
|
|
3077
|
+
<div class="sm-flow-label sm-flow-label--results" data-sm-layer="measurement" data-sm-evidence-only>${zh ? '断言与诊断' : 'Checks & diagnostics'}</div>
|
|
3078
|
+
<div class="sm-flow-label sm-flow-label--targets" data-sm-layer="measurement" data-sm-evidence-only data-sm-related-label hidden aria-hidden="true">${zh ? '关联知识' : 'Related knowledge'}</div>
|
|
2736
3079
|
${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="${
|
|
3080
|
+
<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="${measurementLayout.rootPosition.x}" data-sm-evidence-y="${measurementLayout.rootPosition.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)}">
|
|
2738
3081
|
<div class="sm-node-title">SKILL.md</div>
|
|
2739
3082
|
<div class="sm-node-meta">${e(entry.skillName)}</div>
|
|
2740
3083
|
</div>
|