oh-my-knowledge 0.35.0 → 0.37.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 +5 -3
- package/README.zh.md +5 -3
- package/dist/assets/agent-skills/omk/SKILL.md +4 -2
- package/dist/assets/agent-skills/omk/references/commands.md +121 -2
- package/dist/authoring/evolver.js +1 -1
- package/dist/cli/commands/doctor.js +21 -5
- package/dist/cli/commands/eval/index.js +7 -6
- package/dist/cli/commands/evolve.d.ts +11 -0
- package/dist/cli/commands/evolve.js +105 -5
- package/dist/cli/commands/list.d.ts +25 -0
- package/dist/cli/commands/list.js +118 -0
- package/dist/cli/commands/promote.d.ts +24 -0
- package/dist/cli/commands/promote.js +158 -0
- package/dist/cli/commands/rollback.d.ts +20 -0
- package/dist/cli/commands/rollback.js +95 -0
- package/dist/cli/commands/sample.d.ts +1 -0
- package/dist/cli/commands/sample.js +10 -4
- package/dist/cli/lib/cell-format.d.ts +17 -0
- package/dist/cli/lib/cell-format.js +19 -0
- package/dist/cli/lib/cmd-flags.d.ts +1 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +8 -0
- package/dist/cli/lib/i18n-dict/list.d.ts +3 -0
- package/dist/cli/lib/i18n-dict/list.js +36 -0
- package/dist/cli/lib/i18n-dict/promote.d.ts +3 -0
- package/dist/cli/lib/i18n-dict/promote.js +54 -0
- package/dist/cli/lib/i18n-dict/rollback.d.ts +3 -0
- package/dist/cli/lib/i18n-dict/rollback.js +22 -0
- package/dist/cli/lib/i18n-dict.d.ts +4 -1
- package/dist/cli/lib/i18n-dict.js +6 -0
- package/dist/cli/lib/progress.d.ts +3 -0
- package/dist/cli/lib/progress.js +22 -0
- package/dist/cli/lib/run-tally.js +1 -1
- package/dist/cli/lib/shared.js +1 -1
- package/dist/cli/lib/source-probe.d.ts +15 -0
- package/dist/cli/lib/source-probe.js +129 -0
- package/dist/doctor/endpoint-rule.d.ts +60 -0
- package/dist/doctor/endpoint-rule.js +381 -0
- package/dist/doctor/health/load-custom-dimensions.d.ts +6 -0
- package/dist/doctor/health/load-custom-dimensions.js +43 -3
- package/dist/doctor/index.js +9 -2
- package/dist/eval-core/evaluation-reporting.d.ts +1 -0
- package/dist/eval-core/evaluation-reporting.js +6 -4
- package/dist/eval-workflows/batch-evaluation-workflow.js +3 -2
- package/dist/eval-workflows/run-evaluation.js +2 -2
- package/dist/executors/codex-cli.js +5 -1
- package/dist/inputs/skill-loader.js +6 -3
- package/dist/managed/index.d.ts +2 -0
- package/dist/managed/index.js +2 -0
- package/dist/managed/list-view.d.ts +58 -0
- package/dist/managed/list-view.js +70 -0
- package/dist/managed/promote-gate.d.ts +40 -0
- package/dist/managed/promote-gate.js +37 -0
- package/dist/managed/store.d.ts +25 -2
- package/dist/managed/store.js +137 -11
- package/dist/observability/experience.d.ts +2 -0
- package/dist/observability/experience.js +31 -2
- package/dist/observability/inbox.js +31 -5
- package/dist/observability/review-state.js +22 -11
- package/dist/observability/soft-standards/llm-extractor.js +3 -3
- package/dist/observability/soft-standards/skill-standards-store.d.ts +1 -0
- package/dist/observability/soft-standards/skill-standards-store.js +77 -14
- package/dist/observability/soft-standards/types.d.ts +2 -2
- package/dist/renderer/doctor-detail-renderer.d.ts +9 -0
- package/dist/renderer/doctor-detail-renderer.js +114 -0
- package/dist/renderer/html-renderer.d.ts +3 -2
- package/dist/renderer/html-renderer.js +102 -102
- package/dist/renderer/icons.d.ts +29 -0
- package/dist/renderer/icons.js +66 -0
- package/dist/renderer/layout.js +81 -68
- package/dist/renderer/observation-inbox/styles.d.ts +1 -1
- package/dist/renderer/observation-inbox/styles.js +53 -53
- package/dist/renderer/report-shell.d.ts +77 -0
- package/dist/renderer/report-shell.js +223 -0
- package/dist/renderer/skill-detail-renderer.js +147 -352
- package/dist/renderer/skill-health-renderer.js +50 -73
- package/dist/renderer/skill-list-renderer.js +358 -306
- package/dist/renderer/summary.js +272 -162
- package/dist/renderer/test-view.d.ts +4 -3
- package/dist/renderer/test-view.js +386 -135
- package/dist/server/report-server.js +142 -28
- package/dist/server/report-store.d.ts +1 -1
- package/dist/server/report-store.js +16 -15
- package/dist/server/skill-index.js +5 -4
- package/dist/types/doctor.d.ts +24 -2
- package/dist/types/doctor.js +1 -1
- package/dist/types/managed.d.ts +17 -3
- package/dist/types/observability.d.ts +6 -6
- package/dist/types/report.d.ts +9 -4
- package/package.json +1 -1
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 旧 report 缺 sampleSnapshots 时降级显示("无设计快照,只有执行结果")。
|
|
15
15
|
*/
|
|
16
|
-
import { e } from './layout.js';
|
|
16
|
+
import { e, t } from './layout.js';
|
|
17
|
+
import { icon } from './icons.js';
|
|
17
18
|
// ──────────────────────────────────────────────────────────────────
|
|
18
19
|
// Assertion → Turn 绑定:扫执行轨迹找断言匹配的 turn 编号
|
|
19
20
|
// ──────────────────────────────────────────────────────────────────
|
|
@@ -531,30 +532,134 @@ function rootCauseLabel(c, lang) {
|
|
|
531
532
|
};
|
|
532
533
|
return (lang === 'zh' ? zh : en)[c] ?? c;
|
|
533
534
|
}
|
|
534
|
-
/**
|
|
535
|
-
|
|
536
|
-
|
|
535
|
+
/** 用例设计 fold(prompt/rubric/期望断言/mocks)— 来自 snapshot,与 variant 无关。
|
|
536
|
+
* 多 variant 模式下只渲染一次(共享)。 */
|
|
537
|
+
function renderDesignFold(snapshot, lang) {
|
|
538
|
+
return `<details class="tv-section"><summary>${lang === 'zh' ? '用例设计' : 'Test design'}</summary><div class="tv-section-body">${renderDesignSection(snapshot, lang)}</div></details>`;
|
|
539
|
+
}
|
|
540
|
+
/** 执行轨迹 + 断言结果 fold(per-variant)。两段绑在一起,因为断言的 turn 绑定靠同一份轨迹解析。 */
|
|
541
|
+
function renderTraceAndAssertionsFolds(variant, snapshot, lang) {
|
|
537
542
|
const toolCalls = variant.toolCalls ?? [];
|
|
538
543
|
const turns = variant.turns;
|
|
539
544
|
const summary = renderAssertionsSummary(variant.assertions?.details, snapshot?.assertions, toolCalls, turns, lang);
|
|
540
545
|
const matchedByTurn = buildMatchedMocksByTurn(summary.matchedMocks, snapshot?.assertions, toolCalls, turns);
|
|
541
|
-
const designHtml = renderDesignSection(snapshot, lang);
|
|
542
546
|
const traceHtml = renderExecutionTrace(variant, summary.detailsByTurn, matchedByTurn, lang);
|
|
543
547
|
const totalAsserts = variant.assertions?.total ?? 0;
|
|
544
548
|
const passedAsserts = variant.assertions?.passed ?? 0;
|
|
545
549
|
return `
|
|
546
|
-
<details class="tv-section"><summary>${lang === 'zh' ? '用例设计' : 'Test design'}</summary><div class="tv-section-body">${designHtml}</div></details>
|
|
547
550
|
<details class="tv-section"><summary>${lang === 'zh' ? '执行轨迹' : 'Execution trace'}<span class="tv-section-stat">(${turns?.length ?? 0} ${lang === 'zh' ? '步' : 'steps'} · ${toolCalls.length} ${lang === 'zh' ? '工具调用' : 'tool calls'})</span></summary><div class="tv-section-body">${traceHtml}</div></details>
|
|
548
551
|
<details class="tv-section"><summary>${lang === 'zh' ? '断言结果' : 'Assertion results'}<span class="tv-section-stat">(${passedAsserts}/${totalAsserts})</span></summary><div class="tv-section-body">${summary.html}</div></details>
|
|
549
552
|
`;
|
|
550
553
|
}
|
|
554
|
+
/** 把 design / trace / assertions 三段子内容包成 <details>(默认折叠)。单 variant 用。 */
|
|
555
|
+
function renderSubSections(d, lang) {
|
|
556
|
+
return renderDesignFold(d.snapshot, lang) + renderTraceAndAssertionsFolds(d.variant, d.snapshot, lang);
|
|
557
|
+
}
|
|
551
558
|
function buildCardData(sample_id, snapshot, variant) {
|
|
552
559
|
const rawVerdict = variant.ok === false ? 'fail' : sampleVerdict(variant.assertions?.details);
|
|
553
560
|
const isTripwire = rawVerdict === 'fail' && isTripwireSample(snapshot, variant);
|
|
554
561
|
return { sample_id, snapshot, variant, cardKind: isTripwire ? 'tripwire' : rawVerdict };
|
|
555
562
|
}
|
|
556
|
-
|
|
557
|
-
|
|
563
|
+
// ──────────────────────────────────────────────────────────────────
|
|
564
|
+
// 融合视角:评分内容(原"评测视角"逐用例)直接长进单测卡片
|
|
565
|
+
// - 头部:综合分 badge(一眼看分高低)
|
|
566
|
+
// - 体内:三层分布条(事实/行为/评委)+ 断言计数 + 评委评价文本
|
|
567
|
+
// 这样一条用例 = 既是功能测试(pass/fail/诊断)也是评分卡,不再渲染两遍。
|
|
568
|
+
// ──────────────────────────────────────────────────────────────────
|
|
569
|
+
/** 1-5 分 → 色档 class 后缀(>=4 通过 / >=3 警示 / 其余失败)。 */
|
|
570
|
+
function scoreBand(score) {
|
|
571
|
+
return score >= 4 ? 'pass' : score >= 3 ? 'warn' : 'fail';
|
|
572
|
+
}
|
|
573
|
+
/** 卡头综合分 badge:`4.09/5`,按色档着色。无分(纯断言用例 / executor 错误)返回空串。 */
|
|
574
|
+
function renderCompositeBadge(variant) {
|
|
575
|
+
const score = variant.compositeScore ?? variant.llmScore;
|
|
576
|
+
if (typeof score !== 'number')
|
|
577
|
+
return '';
|
|
578
|
+
return `<span class="tv-composite tv-composite--${scoreBand(score)}">${score.toFixed(2)}<span class="tv-composite-unit">/5</span></span>`;
|
|
579
|
+
}
|
|
580
|
+
/** 一个分数 chip:`[icon] 事实 4.0`。primary(综合分)加粗。 */
|
|
581
|
+
function scoreChip(label, score, primary = false) {
|
|
582
|
+
return `<span class="tv-score-item${primary ? ' tv-score-item--primary' : ''}">${label} <b class="tv-score-v tv-score-v--${scoreBand(score)}">${score.toFixed(2)}</b></span>`;
|
|
583
|
+
}
|
|
584
|
+
/** 体内分布条:综合分 + 三层(事实/行为/评委)+ 断言计数。无任何分数返回空串。 */
|
|
585
|
+
function renderScoreStrip(variant, lang) {
|
|
586
|
+
const items = [];
|
|
587
|
+
const composite = variant.compositeScore ?? variant.llmScore;
|
|
588
|
+
if (typeof composite === 'number')
|
|
589
|
+
items.push(scoreChip(lang === 'zh' ? '综合' : 'Composite', composite, true));
|
|
590
|
+
const ls = variant.layeredScores;
|
|
591
|
+
// 三层图标沿用六维共享映射(fact/behavior/judge);chip 是 baseline 对齐的 inline-flex,svg 单独居中。
|
|
592
|
+
const chipIcon = (name) => icon(name, { size: 13, style: 'align-self:center' });
|
|
593
|
+
if (ls?.factScore != null)
|
|
594
|
+
items.push(scoreChip(`${chipIcon('fact')} ${lang === 'zh' ? '事实' : 'Fact'}`, ls.factScore));
|
|
595
|
+
if (ls?.behaviorScore != null)
|
|
596
|
+
items.push(scoreChip(`${chipIcon('behavior')} ${lang === 'zh' ? '行为' : 'Behavior'}`, ls.behaviorScore));
|
|
597
|
+
if (ls?.judgeScore != null)
|
|
598
|
+
items.push(scoreChip(`${chipIcon('judge')} ${lang === 'zh' ? '评委' : 'Judge'}`, ls.judgeScore));
|
|
599
|
+
if (variant.assertions && variant.assertions.total > 0) {
|
|
600
|
+
const a = variant.assertions;
|
|
601
|
+
const allPass = a.passed === a.total;
|
|
602
|
+
items.push(`<span class="tv-score-item">${lang === 'zh' ? '断言' : 'Assertions'} <b class="tv-score-v tv-score-v--${allPass ? 'pass' : 'fail'}">${a.passed}/${a.total}</b></span>`);
|
|
603
|
+
}
|
|
604
|
+
if (items.length === 0)
|
|
605
|
+
return '';
|
|
606
|
+
return `<div class="tv-scores">${items.join('')}</div>`;
|
|
607
|
+
}
|
|
608
|
+
/** 多评委 ensemble 分项(≥2 评委时):每个评委的分 + 推理,折叠展示。一致性指标 MAD/Pearson 收底。 */
|
|
609
|
+
function renderEnsembleBlock(ensemble, agreement, lang) {
|
|
610
|
+
if (!ensemble || ensemble.length < 2)
|
|
611
|
+
return '';
|
|
612
|
+
const rows = ensemble.map((j) => {
|
|
613
|
+
const stddev = j.scoreStddev != null && j.scoreStddev > 0 ? ` <span class="tv-judge-stab">±${j.scoreStddev}</span>` : '';
|
|
614
|
+
const fail = j.judgeFailureCount && j.judgeFailureCount > 0
|
|
615
|
+
? ` <span class="tv-judge-stab tv-judge-stab--fail">${j.judgeFailureCount}/${j.scoreSamples?.length || '?'} fail</span>`
|
|
616
|
+
: '';
|
|
617
|
+
const reasoning = j.reasoning
|
|
618
|
+
? `<details class="tv-collapsible"><summary>${t('judgeReasoning', lang)} ${t('judgeReasoningExpand', lang)}</summary><pre class="tv-pre">${e(j.reasoning)}</pre></details>`
|
|
619
|
+
: '';
|
|
620
|
+
return `<div class="tv-ensemble-row"><b>${e(j.judge)}</b>: ${j.score}${stddev}${fail}${reasoning}</div>`;
|
|
621
|
+
}).join('');
|
|
622
|
+
const agreementLine = agreement && agreement.pairCount > 0
|
|
623
|
+
? `<div class="tv-ensemble-agree" title="${t('madDesc', lang)}">MAD ${agreement.meanAbsDiff}${agreement.pearson != null ? ` · Pearson ${agreement.pearson}` : ''}</div>`
|
|
624
|
+
: '';
|
|
625
|
+
return `<details class="tv-collapsible tv-ensemble"><summary>${t('ensembleHeader', lang)} (${ensemble.length})</summary>${rows}${agreementLine}</details>`;
|
|
626
|
+
}
|
|
627
|
+
/** 评委评价:打分理由(llmReason)+ CoT 折叠 + 自一致性(stddev/失败数)+ 多评委 ensemble 分项。
|
|
628
|
+
* 无任何 judge 信息(--no-judge / 纯断言)返回空串。与 diagnostic 互补:judge 答"打几分 + 为什么"。 */
|
|
629
|
+
function renderJudgeBlock(variant, lang) {
|
|
630
|
+
const hasReason = !!variant.llmReason;
|
|
631
|
+
const hasEnsemble = !!variant.llmEnsemble && variant.llmEnsemble.length >= 2;
|
|
632
|
+
const hasStddev = !!variant.llmScoreSamples && variant.llmScoreSamples.length > 1 && variant.llmScoreStddev != null;
|
|
633
|
+
if (!hasReason && !hasEnsemble && !hasStddev)
|
|
634
|
+
return '';
|
|
635
|
+
// Judge 自一致性:同一份输出多次评分的标准差 + 失败数(--judge-repeat ≥ 2 时)。
|
|
636
|
+
let stabTag = '';
|
|
637
|
+
if (hasStddev) {
|
|
638
|
+
stabTag += `<span class="tv-judge-stab" title="${t('judgeStddevDesc', lang)}">±${variant.llmScoreStddev}</span>`;
|
|
639
|
+
if (variant.llmScoreFailures && variant.llmScoreFailures > 0) {
|
|
640
|
+
stabTag += `<span class="tv-judge-stab tv-judge-stab--fail" title="${t('judgeFailuresDesc', lang)}">${variant.llmScoreFailures}/${variant.llmScoreSamples.length} fail</span>`;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const reasonHtml = hasReason
|
|
644
|
+
? (() => {
|
|
645
|
+
const reason = variant.llmReason.length > 280 ? variant.llmReason.slice(0, 280) + '…' : variant.llmReason;
|
|
646
|
+
const cot = variant.llmReasoning
|
|
647
|
+
? `<details class="tv-collapsible"><summary>${lang === 'zh' ? '展开完整 CoT' : 'show full CoT'}</summary><pre class="tv-pre">${e(variant.llmReasoning)}</pre></details>`
|
|
648
|
+
: '';
|
|
649
|
+
return `<div class="tv-judge-text">${e(reason)}</div>${cot}`;
|
|
650
|
+
})()
|
|
651
|
+
: '';
|
|
652
|
+
return `<div class="tv-judge">
|
|
653
|
+
<div class="tv-judge-h">${icon('judge', { size: 14 })}${lang === 'zh' ? '评委评价' : 'Judge'}${stabTag}</div>
|
|
654
|
+
${reasonHtml}
|
|
655
|
+
${renderEnsembleBlock(variant.llmEnsemble, variant.llmAgreement, lang)}
|
|
656
|
+
</div>`;
|
|
657
|
+
}
|
|
658
|
+
/** 评分区 = 分布条 + 评委评价,放在卡片 body 顶部(诊断之前)。 */
|
|
659
|
+
function renderScoringPart(variant, lang) {
|
|
660
|
+
return `${renderScoreStrip(variant, lang)}${renderJudgeBlock(variant, lang)}`;
|
|
661
|
+
}
|
|
662
|
+
/** Header 公共部分:状态点 + sample_id + 标题 + 综合分 badge + 耗时。 */
|
|
558
663
|
function renderCardHeader(d, lang) {
|
|
559
664
|
const { sample_id, snapshot, variant, cardKind } = d;
|
|
560
665
|
const dur = variant.timing?.execMs ?? variant.durationMs;
|
|
@@ -566,14 +671,15 @@ function renderCardHeader(d, lang) {
|
|
|
566
671
|
<span class="tv-dot tv-dot--${cardKind}"></span>
|
|
567
672
|
<code class="tv-id">${e(sample_id)}</code>
|
|
568
673
|
<span class="tv-title">${e(titleSnippet)}${snapshot?.prompt && snapshot.prompt.length > 80 ? '…' : ''}</span>
|
|
674
|
+
${renderCompositeBadge(variant)}
|
|
569
675
|
<span class="tv-meta">${durTag}</span>
|
|
570
676
|
`;
|
|
571
677
|
}
|
|
572
|
-
/** Pass
|
|
678
|
+
/** Pass 用例:单行折叠,点击展开看分布条 + 评委评价 + design/trace/assertions。 */
|
|
573
679
|
function renderPassRow(d, lang) {
|
|
574
680
|
return `<details class="tv-row tv-row--pass">
|
|
575
681
|
<summary class="tv-row-h">${renderCardHeader(d, lang)}</summary>
|
|
576
|
-
<div class="tv-row-body">${renderSubSections(d, lang)}</div>
|
|
682
|
+
<div class="tv-row-body">${renderScoringPart(d.variant, lang)}${renderSubSections(d, lang)}</div>
|
|
577
683
|
</details>`;
|
|
578
684
|
}
|
|
579
685
|
/** Fail / tripwire 卡片:默认展开,顶部诊断区,sub-section 折叠。 */
|
|
@@ -597,14 +703,143 @@ function renderFailCard(d, lang) {
|
|
|
597
703
|
${diagSummaryPreview}
|
|
598
704
|
<div class="tv-card-body">
|
|
599
705
|
${tripwireBanner}
|
|
706
|
+
${renderScoreStrip(d.variant, lang)}
|
|
600
707
|
${diagnosticHtml}
|
|
708
|
+
${renderJudgeBlock(d.variant, lang)}
|
|
601
709
|
${renderSubSections(d, lang)}
|
|
602
710
|
</div>
|
|
603
711
|
</details>`;
|
|
604
712
|
}
|
|
605
713
|
// ──────────────────────────────────────────────────────────────────
|
|
714
|
+
// 多 variant:每条用例详展「主轮(最新)」+ 各轮分数轨迹条 + 其他轮折叠
|
|
715
|
+
// ──────────────────────────────────────────────────────────────────
|
|
716
|
+
/** 该 sample 的主轮 = variants 顺序里最后一个有结果的 variant(通常是最新/treatment)。 */
|
|
717
|
+
function pickPrimaryVariant(variantResults, variants) {
|
|
718
|
+
for (let i = variants.length - 1; i >= 0; i--) {
|
|
719
|
+
if (variantResults[variants[i]])
|
|
720
|
+
return variants[i];
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
/** 各轮分数轨迹条:`round-0 3.86 › round-1 1.71 › round-2 1.59 [最新]`,按分着色,主轮高亮。 */
|
|
725
|
+
function renderRoundStrip(variants, variantResults, primaryName, lang) {
|
|
726
|
+
const chips = variants.map((name) => {
|
|
727
|
+
const v = variantResults[name];
|
|
728
|
+
const isPrimary = name === primaryName;
|
|
729
|
+
if (!v) {
|
|
730
|
+
return `<span class="tv-round tv-round--missing">${e(name)} —</span>`;
|
|
731
|
+
}
|
|
732
|
+
const score = v.compositeScore ?? v.llmScore;
|
|
733
|
+
const hasScore = typeof score === 'number';
|
|
734
|
+
const band = hasScore ? scoreBand(score) : 'fail';
|
|
735
|
+
const kind = v.ok === false ? 'fail' : sampleVerdict(v.assertions?.details);
|
|
736
|
+
const icon = kind === 'pass' ? '✓' : '✗';
|
|
737
|
+
const curTag = isPrimary ? `<span class="tv-round-cur">${lang === 'zh' ? '最新' : 'latest'}</span>` : '';
|
|
738
|
+
return `<span class="tv-round tv-round--${band}${isPrimary ? ' tv-round--current' : ''}"><span class="tv-round-icon tv-round-icon--${kind === 'pass' ? 'pass' : 'fail'}">${icon}</span>${e(name)} <b>${hasScore ? score.toFixed(2) : '—'}</b>${curTag}</span>`;
|
|
739
|
+
}).join('<span class="tv-round-arrow">·</span>');
|
|
740
|
+
return `<div class="tv-rounds">${chips}</div>`;
|
|
741
|
+
}
|
|
742
|
+
/** 单条用例的多 variant 卡片:主轮详情 + 轨迹条 + 共享设计 + 其他轮折叠。 */
|
|
743
|
+
function renderMultiSampleCard(sample_id, snapshot, variantResults, variants, primaryName, lang) {
|
|
744
|
+
const primary = variantResults[primaryName];
|
|
745
|
+
const d = buildCardData(sample_id, snapshot, primary);
|
|
746
|
+
const verdict = d.cardKind === 'pass' ? 'pass' : 'fail';
|
|
747
|
+
const tripwireBanner = d.cardKind === 'tripwire'
|
|
748
|
+
? `<div class="tv-tripwire-banner">
|
|
749
|
+
<div class="tv-tripwire-banner-title">${lang === 'zh' ? '◆ 这是诱错样本' : '◆ This is a tripwire sample'}</div>
|
|
750
|
+
<div class="tv-tripwire-banner-body">${lang === 'zh'
|
|
751
|
+
? '用户 prompt 故意诱导 LLM 走错。LLM 失败 = 已记录的 LLM 边界数据点,不是要修的 bug。'
|
|
752
|
+
: 'The user prompt intentionally misdirects the LLM. A failure here is a recorded LLM-susceptibility data point — not a bug to fix.'}</div>
|
|
753
|
+
</div>`
|
|
754
|
+
: '';
|
|
755
|
+
// 其他轮(非主轮)折叠:每轮分数条 + 诊断 + 评委 + 轨迹/断言。evolve 多轮也不塞爆。
|
|
756
|
+
const otherRounds = variants.filter((n) => n !== primaryName && variantResults[n]);
|
|
757
|
+
const otherRoundsHtml = otherRounds.map((name) => {
|
|
758
|
+
const v = variantResults[name];
|
|
759
|
+
const rd = buildCardData(sample_id, snapshot, v);
|
|
760
|
+
const rv = rd.cardKind === 'pass' ? 'pass' : 'fail';
|
|
761
|
+
const score = v.compositeScore ?? v.llmScore;
|
|
762
|
+
const scoreText = typeof score === 'number' ? score.toFixed(2) : '—';
|
|
763
|
+
const icon = rd.cardKind === 'pass' ? '✓' : rd.cardKind === 'tripwire' ? '◆' : '✗';
|
|
764
|
+
return `<div class="tv-round-detail">
|
|
765
|
+
<div class="tv-round-detail-h"><span class="tv-round-detail-icon tv-round-detail-icon--${rd.cardKind}">${icon}</span> ${e(name)} <span class="tv-round-detail-score">${lang === 'zh' ? '综合' : 'composite'} ${scoreText}</span></div>
|
|
766
|
+
${renderScoreStrip(v, lang)}
|
|
767
|
+
${renderDiagnosticSection(v, rv, lang)}
|
|
768
|
+
${renderJudgeBlock(v, lang)}
|
|
769
|
+
${renderTraceAndAssertionsFolds(v, snapshot, lang)}
|
|
770
|
+
</div>`;
|
|
771
|
+
}).join('');
|
|
772
|
+
const otherRoundsFold = otherRoundsHtml
|
|
773
|
+
? `<details class="tv-section"><summary>${lang === 'zh' ? '其他轮详情' : 'Other rounds'}<span class="tv-section-stat">(${otherRounds.length})</span></summary><div class="tv-section-body">${otherRoundsHtml}</div></details>`
|
|
774
|
+
: '';
|
|
775
|
+
const diagSummaryPreview = primary.diagnostic?.ok && primary.diagnostic.summary
|
|
776
|
+
? `<div class="tv-card-preview">${e(primary.diagnostic.summary.slice(0, 120))}${primary.diagnostic.summary.length > 120 ? '…' : ''}</div>`
|
|
777
|
+
: '';
|
|
778
|
+
return `<details class="tv-card tv-card--${d.cardKind}">
|
|
779
|
+
<summary class="tv-card-h">${renderCardHeader(d, lang)}</summary>
|
|
780
|
+
${diagSummaryPreview}
|
|
781
|
+
<div class="tv-card-body">
|
|
782
|
+
${renderRoundStrip(variants, variantResults, primaryName, lang)}
|
|
783
|
+
${tripwireBanner}
|
|
784
|
+
${renderScoreStrip(primary, lang)}
|
|
785
|
+
${renderDiagnosticSection(primary, verdict, lang)}
|
|
786
|
+
${renderJudgeBlock(primary, lang)}
|
|
787
|
+
${renderDesignFold(snapshot, lang)}
|
|
788
|
+
${renderTraceAndAssertionsFolds(primary, snapshot, lang)}
|
|
789
|
+
${otherRoundsFold}
|
|
790
|
+
</div>
|
|
791
|
+
</details>`;
|
|
792
|
+
}
|
|
793
|
+
// ──────────────────────────────────────────────────────────────────
|
|
606
794
|
// 主入口
|
|
607
795
|
// ──────────────────────────────────────────────────────────────────
|
|
796
|
+
/** 单 variant 的逐用例 body:统计条 + 三组(失败/诱错/通过)。 */
|
|
797
|
+
function renderSingleVariantBody(report, variant, lang) {
|
|
798
|
+
const results = report.results || [];
|
|
799
|
+
const snapshots = report.sampleSnapshots ?? {};
|
|
800
|
+
const passData = [];
|
|
801
|
+
const failData = [];
|
|
802
|
+
const tripwireData = [];
|
|
803
|
+
for (const r of results) {
|
|
804
|
+
const v = r.variants[variant];
|
|
805
|
+
if (!v)
|
|
806
|
+
continue;
|
|
807
|
+
const d = buildCardData(r.sample_id, snapshots[r.sample_id], v);
|
|
808
|
+
if (d.cardKind === 'pass')
|
|
809
|
+
passData.push(d);
|
|
810
|
+
else if (d.cardKind === 'tripwire')
|
|
811
|
+
tripwireData.push(d);
|
|
812
|
+
else
|
|
813
|
+
failData.push(d);
|
|
814
|
+
}
|
|
815
|
+
const passSection = passData.length > 0
|
|
816
|
+
? `<div class="tv-group-sep">${lang === 'zh' ? '通过用例' : 'Passed'}</div>
|
|
817
|
+
<div class="tv-rows">${passData.map((d) => renderPassRow(d, lang)).join('')}</div>`
|
|
818
|
+
: '';
|
|
819
|
+
const failSection = failData.length > 0
|
|
820
|
+
? `<div class="tv-group-sep">${lang === 'zh' ? '失败用例' : 'Failed'}</div>
|
|
821
|
+
${failData.map((d) => renderFailCard(d, lang)).join('')}`
|
|
822
|
+
: '';
|
|
823
|
+
const tripwireSection = tripwireData.length > 0
|
|
824
|
+
? `<div class="tv-group-sep">${lang === 'zh' ? '诱错样本' : 'Tripwire samples'}</div>
|
|
825
|
+
${tripwireData.map((d) => renderFailCard(d, lang)).join('')}`
|
|
826
|
+
: '';
|
|
827
|
+
const stats = computeVariantStats(report, variant);
|
|
828
|
+
return `${renderStatsBar(stats, lang)}${failSection}${tripwireSection}${passSection}`;
|
|
829
|
+
}
|
|
830
|
+
/** 统计条:通过 / 失败 / 诱错 / 平均分。可加 prefix(多 variant 标主轮名)。 */
|
|
831
|
+
function renderStatsBar(stats, lang, prefix = '') {
|
|
832
|
+
const tripwireStat = stats.tripwire > 0
|
|
833
|
+
? `<span class="tv-stat tv-stat--tripwire"><span class="tv-stat-num">${stats.tripwire}</span>${lang === 'zh' ? '诱错触发' : 'tripwire'}</span>`
|
|
834
|
+
: '';
|
|
835
|
+
return `<div class="tv-variant-stats">
|
|
836
|
+
${prefix}
|
|
837
|
+
<span class="tv-stat tv-stat--pass"><span class="tv-stat-num">${stats.passed}</span>${lang === 'zh' ? '通过' : 'passed'}</span>
|
|
838
|
+
<span class="tv-stat tv-stat--fail"><span class="tv-stat-num">${stats.failed}</span>${lang === 'zh' ? '失败' : 'failed'}</span>
|
|
839
|
+
${tripwireStat}
|
|
840
|
+
<span class="tv-stat"><span class="tv-stat-num">${stats.avgScore.toFixed(1)}</span>${lang === 'zh' ? '平均分' : 'avg / 5'}</span>
|
|
841
|
+
</div>`;
|
|
842
|
+
}
|
|
608
843
|
export function renderTestView(report, lang) {
|
|
609
844
|
const variants = report.meta.variants || [];
|
|
610
845
|
const results = report.results || [];
|
|
@@ -614,59 +849,46 @@ export function renderTestView(report, lang) {
|
|
|
614
849
|
}
|
|
615
850
|
const hasSnapshots = Object.keys(snapshots).length > 0;
|
|
616
851
|
const fallbackBanner = hasSnapshots ? '' : `<div class="tv-warn">${lang === 'zh' ? '⚠ 该 report 缺 sampleSnapshots(早期版本生成),只能展示执行结果,无设计信息' : '⚠ Report has no sampleSnapshots (older format); only execution results shown'}</div>`;
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
? `<span class="tv-stat tv-stat--tripwire"><span class="tv-stat-num">${variantStats.tripwire}</span>${lang === 'zh' ? '诱错触发' : 'tripwire'}</span>`
|
|
649
|
-
: '';
|
|
650
|
-
return `<div class="tv-variant-panel" data-variant="${e(variant)}" ${idx === 0 ? '' : 'hidden'}>
|
|
651
|
-
<div class="tv-variant-stats">
|
|
652
|
-
<span class="tv-stat tv-stat--pass"><span class="tv-stat-num">${variantStats.passed}</span>${lang === 'zh' ? '通过' : 'passed'}</span>
|
|
653
|
-
<span class="tv-stat tv-stat--fail"><span class="tv-stat-num">${variantStats.failed}</span>${lang === 'zh' ? '失败' : 'failed'}</span>
|
|
654
|
-
${tripwireStat}
|
|
655
|
-
<span class="tv-stat"><span class="tv-stat-num">${variantStats.avgScore.toFixed(1)}</span>${lang === 'zh' ? '平均分' : 'avg / 5'}</span>
|
|
656
|
-
</div>
|
|
657
|
-
${failSection}
|
|
658
|
-
${tripwireSection}
|
|
659
|
-
${passSection}
|
|
660
|
-
</div>`;
|
|
661
|
-
}).join('');
|
|
662
|
-
// 只 1 个 variant 时不显示切换器(SOLO 模式没对照,显示 tab 反而困惑)。
|
|
663
|
-
const variantTabsHtml = variants.length > 1
|
|
664
|
-
? `<div class="tv-variant-tabs">${variants.map((variant, idx) => `<button type="button" class="tv-vtab ${idx === 0 ? 'tv-vtab--active' : ''}" data-target="${e(variant)}" onclick="omkSwitchVariant(this)">${e(variant)}</button>`).join('')}</div>`
|
|
852
|
+
// 单 variant:无对照,直接分组列用例。
|
|
853
|
+
if (variants.length === 1) {
|
|
854
|
+
return `<div class="tv-root">${fallbackBanner}${renderSingleVariantBody(report, variants[0], lang)}</div>`;
|
|
855
|
+
}
|
|
856
|
+
// 多 variant:主轮(最新)领头,各轮分数轨迹条 + 其他轮折叠,不再用 variant tabs。
|
|
857
|
+
const primaryName = variants[variants.length - 1];
|
|
858
|
+
const failCards = [];
|
|
859
|
+
const tripwireCards = [];
|
|
860
|
+
const passCards = [];
|
|
861
|
+
for (const r of results) {
|
|
862
|
+
const samplePrimary = pickPrimaryVariant(r.variants, variants) ?? primaryName;
|
|
863
|
+
const primary = r.variants[samplePrimary];
|
|
864
|
+
if (!primary)
|
|
865
|
+
continue;
|
|
866
|
+
const d = buildCardData(r.sample_id, snapshots[r.sample_id], primary);
|
|
867
|
+
const card = renderMultiSampleCard(r.sample_id, snapshots[r.sample_id], r.variants, variants, samplePrimary, lang);
|
|
868
|
+
if (d.cardKind === 'pass')
|
|
869
|
+
passCards.push(card);
|
|
870
|
+
else if (d.cardKind === 'tripwire')
|
|
871
|
+
tripwireCards.push(card);
|
|
872
|
+
else
|
|
873
|
+
failCards.push(card);
|
|
874
|
+
}
|
|
875
|
+
const failSection = failCards.length > 0
|
|
876
|
+
? `<div class="tv-group-sep">${lang === 'zh' ? '失败用例' : 'Failed'}</div>${failCards.join('')}`
|
|
877
|
+
: '';
|
|
878
|
+
const tripwireSection = tripwireCards.length > 0
|
|
879
|
+
? `<div class="tv-group-sep">${lang === 'zh' ? '诱错样本' : 'Tripwire samples'}</div>${tripwireCards.join('')}`
|
|
880
|
+
: '';
|
|
881
|
+
const passSection = passCards.length > 0
|
|
882
|
+
? `<div class="tv-group-sep">${lang === 'zh' ? '通过用例' : 'Passed'}</div>${passCards.join('')}`
|
|
665
883
|
: '';
|
|
666
|
-
|
|
884
|
+
const stats = computeVariantStats(report, primaryName);
|
|
885
|
+
const primaryNote = `<span class="tv-primary-note">${lang === 'zh' ? `主轮 ${e(primaryName)}` : `Primary ${e(primaryName)}`}</span>`;
|
|
667
886
|
return `<div class="tv-root">${fallbackBanner}
|
|
668
|
-
|
|
669
|
-
${
|
|
887
|
+
<div class="tv-multi-hint">${lang === 'zh' ? '以主轮(最新)状态分组;展开看各轮分数轨迹与逐轮对比' : 'Grouped by primary (latest) status; expand for per-round score trajectory and comparison'}</div>
|
|
888
|
+
${renderStatsBar(stats, lang, primaryNote)}
|
|
889
|
+
${failSection}
|
|
890
|
+
${tripwireSection}
|
|
891
|
+
${passSection}</div>`;
|
|
670
892
|
}
|
|
671
893
|
function computeVariantStats(report, variant) {
|
|
672
894
|
const snapshots = report.sampleSnapshots ?? {};
|
|
@@ -712,61 +934,78 @@ function computeVariantStats(report, variant) {
|
|
|
712
934
|
* - **default-collapsed**:通过的用例只显一行;失败/诱错展开诊断,其余 section 折叠。
|
|
713
935
|
*/
|
|
714
936
|
export const TEST_VIEW_CSS = `
|
|
715
|
-
/* tv-root:
|
|
937
|
+
/* tv-root: 白底卡片(与 hero / 结论 / fold 同一视觉语言),内部逐用例是 hairline 分隔的列表项。
|
|
938
|
+
仍保留一套 scoped 变量给富内容(诊断 / 轨迹)用,色调对齐外层冷灰页面。 */
|
|
716
939
|
.tv-root {
|
|
717
|
-
--tv-bg:#
|
|
718
|
-
--tv-text:#
|
|
719
|
-
--tv-divider:#
|
|
720
|
-
/*
|
|
721
|
-
|
|
722
|
-
--tv-pass
|
|
723
|
-
--tv-
|
|
724
|
-
--tv-
|
|
725
|
-
--tv-shadow:0
|
|
726
|
-
--tv-
|
|
727
|
-
|
|
728
|
-
padding:
|
|
940
|
+
--tv-bg:#ffffff; --tv-bg-card:#ffffff; --tv-bg-soft:rgba(99,112,131,.05);
|
|
941
|
+
--tv-text:#2c333f; --tv-text-2:#637083; --tv-text-3:#9ca3af;
|
|
942
|
+
--tv-divider:#edf0f5;
|
|
943
|
+
/* pass / fail / tripwire 用形状 + 色相区分(色盲友好) */
|
|
944
|
+
--tv-pass:#1f9d63; --tv-fail:#dc2626; --tv-tripwire:#7c3aed;
|
|
945
|
+
--tv-pass-soft:rgba(31,157,99,.10); --tv-fail-soft:rgba(220,38,38,.10); --tv-tripwire-soft:rgba(124,58,237,.10);
|
|
946
|
+
--tv-accent:#4f46e5; --tv-warn:#d97706;
|
|
947
|
+
--tv-shadow:0 1px 3px rgba(0,0,0,.06);
|
|
948
|
+
--tv-shadow-hover:0 2px 8px rgba(0,0,0,.10);
|
|
949
|
+
background:#fff; color:var(--tv-text);
|
|
950
|
+
border:1px solid #e4e8f1; border-radius:12px; box-shadow:0 8px 24px rgba(31,41,55,.04);
|
|
951
|
+
padding:8px 16px 12px; margin:0;
|
|
729
952
|
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Segoe UI",sans-serif;
|
|
730
953
|
font-size:15px; line-height:1.75;
|
|
731
954
|
}
|
|
732
|
-
/*
|
|
733
|
-
|
|
734
|
-
.tv-root .tv-
|
|
735
|
-
|
|
736
|
-
.tv-root .tv-
|
|
737
|
-
.tv-root .tv-
|
|
738
|
-
.tv-root .tv-
|
|
739
|
-
|
|
955
|
+
/* 多 variant 提示 + 主轮标签 */
|
|
956
|
+
.tv-root .tv-multi-hint { font-size:13px;color:var(--tv-text-2);margin:0 0 16px;line-height:1.6 }
|
|
957
|
+
.tv-root .tv-primary-note { font-size:13px;color:var(--tv-text-3);font-family:"SF Mono",Menlo,monospace;margin-right:6px }
|
|
958
|
+
/* 各轮分数轨迹条 — round-0 3.86 › round-1 1.71 › round-2 1.59[最新],按分着色,主轮加框 */
|
|
959
|
+
.tv-root .tv-rounds { display:flex;flex-wrap:wrap;align-items:center;gap:6px 4px;margin:0 0 18px }
|
|
960
|
+
.tv-root .tv-round { display:inline-flex;align-items:center;gap:5px;font-size:13px;padding:5px 10px;border-radius:14px;background:var(--tv-bg-soft);color:var(--tv-text-2);border:1px solid transparent }
|
|
961
|
+
.tv-root .tv-round b { font-variant-numeric:tabular-nums;font-family:"SF Mono",Menlo,monospace;font-weight:700 }
|
|
962
|
+
.tv-root .tv-round--pass b { color:var(--tv-pass) }
|
|
963
|
+
.tv-root .tv-round--warn b { color:var(--tv-warn) }
|
|
964
|
+
.tv-root .tv-round--fail b { color:var(--tv-fail) }
|
|
965
|
+
.tv-root .tv-round--current { border-color:var(--tv-accent);background:rgba(79,70,229,.06);color:var(--tv-text);font-weight:600 }
|
|
966
|
+
.tv-root .tv-round--missing { color:var(--tv-text-3);font-style:italic }
|
|
967
|
+
.tv-root .tv-round-icon { font-size:11px;font-weight:700 }
|
|
968
|
+
.tv-root .tv-round-icon--pass { color:var(--tv-pass) }
|
|
969
|
+
.tv-root .tv-round-icon--fail { color:var(--tv-fail) }
|
|
970
|
+
.tv-root .tv-round-cur { font-size:11px;color:var(--tv-accent);font-weight:600;margin-left:2px }
|
|
971
|
+
.tv-root .tv-round-arrow { color:var(--tv-text-3);font-size:13px;margin:0 1px }
|
|
972
|
+
/* 其他轮详情块(折叠内) */
|
|
973
|
+
.tv-root .tv-round-detail { padding:14px 0;border-top:1px dashed var(--tv-divider) }
|
|
974
|
+
.tv-root .tv-round-detail:first-child { border-top:none;padding-top:4px }
|
|
975
|
+
.tv-root .tv-round-detail-h { font-size:14px;font-weight:600;color:var(--tv-text);margin-bottom:10px;display:flex;align-items:center;gap:8px }
|
|
976
|
+
.tv-root .tv-round-detail-icon { font-size:13px;font-weight:700 }
|
|
977
|
+
.tv-root .tv-round-detail-icon--pass { color:var(--tv-pass) }
|
|
978
|
+
.tv-root .tv-round-detail-icon--fail { color:var(--tv-fail) }
|
|
979
|
+
.tv-root .tv-round-detail-icon--tripwire { color:var(--tv-tripwire) }
|
|
980
|
+
.tv-root .tv-round-detail-score { font-family:"SF Mono",Menlo,monospace;font-weight:400;color:var(--tv-text-2);font-size:13px }
|
|
740
981
|
/* Stats — 更扁平的数字+标签布局 */
|
|
741
|
-
.tv-root .tv-variant-stats { display:flex;gap:32px;margin:0 0
|
|
982
|
+
.tv-root .tv-variant-stats { display:flex;gap:32px;margin:6px 0 10px;padding:0 14px;font-size:14px;color:var(--tv-text-2);align-items:baseline }
|
|
742
983
|
.tv-root .tv-stat { display:flex;align-items:baseline;gap:6px }
|
|
743
984
|
.tv-root .tv-stat-num { color:var(--tv-text);font-weight:600;font-size:18px;font-variant-numeric:tabular-nums }
|
|
744
985
|
.tv-root .tv-stat--pass .tv-stat-num { color:var(--tv-pass) }
|
|
745
986
|
.tv-root .tv-stat--fail .tv-stat-num { color:var(--tv-fail) }
|
|
746
987
|
.tv-root .tv-stat--tripwire .tv-stat-num { color:var(--tv-tripwire) }
|
|
747
|
-
/* Group separator */
|
|
748
|
-
.tv-root .tv-group-sep { display:flex;align-items:center;gap:
|
|
988
|
+
/* Group separator — 卡片内分组小标题 */
|
|
989
|
+
.tv-root .tv-group-sep { display:flex;align-items:center;gap:12px;margin:18px 0 4px;padding:0 14px;color:var(--tv-text-3);font-size:11.5px;text-transform:uppercase;letter-spacing:0.08em;font-weight:600 }
|
|
749
990
|
.tv-root .tv-group-sep::after { content:'';flex:1;height:1px;background:var(--tv-divider) }
|
|
750
|
-
/*
|
|
751
|
-
|
|
752
|
-
.tv-root .tv-
|
|
753
|
-
.tv-root .tv-row
|
|
754
|
-
.tv-root .tv-row
|
|
755
|
-
.tv-root .tv-row-
|
|
756
|
-
.tv-root .tv-row-
|
|
757
|
-
|
|
758
|
-
.tv-root .tv-row[open] .tv-
|
|
759
|
-
|
|
760
|
-
.tv-root
|
|
761
|
-
.tv-root
|
|
762
|
-
.tv-root .tv-card
|
|
763
|
-
.tv-root .tv-card
|
|
764
|
-
.tv-root .tv-card-h {
|
|
765
|
-
.tv-root .tv-
|
|
766
|
-
.tv-root .tv-card-h::after { content:'›';margin-left:8px;color:var(--tv-text-3);font-size:18px;transition:transform .15s }
|
|
767
|
-
.tv-root .tv-card[open] .tv-card-h::after { transform:rotate(90deg) }
|
|
991
|
+
/* 列表项 — pass row 与 fail/tripwire card 统一成 hairline 分隔的行,无阴影/无左色条;
|
|
992
|
+
状态靠左侧状态点,hover/展开时整行淡底高亮。 */
|
|
993
|
+
.tv-root .tv-rows { display:flex;flex-direction:column;margin:0 }
|
|
994
|
+
.tv-root .tv-row, .tv-root .tv-card { background:transparent;border:none;border-bottom:1px solid var(--tv-divider);border-radius:8px;box-shadow:none;margin:0;transition:background .12s }
|
|
995
|
+
.tv-root .tv-row:hover, .tv-root .tv-card:hover { background:var(--tv-bg-soft) }
|
|
996
|
+
.tv-root .tv-row[open], .tv-root .tv-card[open] { background:var(--tv-bg-soft);border-bottom-color:transparent }
|
|
997
|
+
.tv-root .tv-rows .tv-row:last-child, .tv-root .tv-card:last-child { border-bottom:none }
|
|
998
|
+
/* 聚焦:展开某条用例时,其余收起的行淡化,突出当前;hover 被淡化的行可临时恢复。 */
|
|
999
|
+
.tv-root:has(.tv-card[open], .tv-row[open]) .tv-card:not([open]),
|
|
1000
|
+
.tv-root:has(.tv-card[open], .tv-row[open]) .tv-row:not([open]) { opacity:.42;transition:opacity .15s }
|
|
1001
|
+
.tv-root:has(.tv-card[open], .tv-row[open]) .tv-card:not([open]):hover,
|
|
1002
|
+
.tv-root:has(.tv-card[open], .tv-row[open]) .tv-row:not([open]):hover { opacity:1 }
|
|
1003
|
+
.tv-root .tv-row-h, .tv-root .tv-card-h { display:flex;align-items:center;gap:13px;padding:14px;cursor:pointer;list-style:none;font-size:14.5px }
|
|
1004
|
+
.tv-root .tv-row-h::-webkit-details-marker, .tv-root .tv-card-h::-webkit-details-marker { display:none }
|
|
1005
|
+
.tv-root .tv-row-h::after, .tv-root .tv-card-h::after { content:'›';margin-left:4px;color:var(--tv-text-3);font-size:17px;transition:transform .15s }
|
|
1006
|
+
.tv-root .tv-row[open] .tv-row-h::after, .tv-root .tv-card[open] .tv-card-h::after { transform:rotate(90deg) }
|
|
768
1007
|
.tv-root .tv-card[open] .tv-card-h { border-bottom:1px solid var(--tv-divider) }
|
|
769
|
-
.tv-root .tv-card-preview { padding:0
|
|
1008
|
+
.tv-root .tv-card-preview { padding:0 14px 13px;color:var(--tv-text-2);font-size:13.5px;line-height:1.6 }
|
|
770
1009
|
.tv-root .tv-card[open] .tv-card-preview { display:none }
|
|
771
1010
|
/* Status dots — 用形状 + 色 + 字符的三重区分,避免色盲 / 远看雷同。
|
|
772
1011
|
pass: 实心圆 sage;fail: 红 ✗ 字符(更显眼);tripwire: 紫色 ◆ 菱形。 */
|
|
@@ -777,16 +1016,41 @@ export const TEST_VIEW_CSS = `
|
|
|
777
1016
|
.tv-root .tv-id { font-family:"SF Mono","JetBrains Mono",Menlo,Consolas,monospace;font-size:13px;color:var(--tv-text-3) }
|
|
778
1017
|
.tv-root .tv-title { flex:1;font-size:15px;color:var(--tv-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap }
|
|
779
1018
|
.tv-root .tv-meta { font-size:13px;color:var(--tv-text-3);font-variant-numeric:tabular-nums;font-family:"SF Mono","JetBrains Mono",Menlo,monospace }
|
|
1019
|
+
/* Composite badge in header — 综合分,卡头右侧紧贴耗时,一眼看分高低 */
|
|
1020
|
+
.tv-root .tv-composite { flex-shrink:0;font-size:16px;font-weight:700;font-variant-numeric:tabular-nums;font-family:"SF Mono","JetBrains Mono",Menlo,monospace;letter-spacing:-0.02em }
|
|
1021
|
+
.tv-root .tv-composite--pass { color:var(--tv-pass) }
|
|
1022
|
+
.tv-root .tv-composite--warn { color:var(--tv-warn) }
|
|
1023
|
+
.tv-root .tv-composite--fail { color:var(--tv-fail) }
|
|
1024
|
+
.tv-root .tv-composite-unit { font-size:11px;color:var(--tv-text-3);font-weight:400;margin-left:1px }
|
|
780
1025
|
/* Body padding */
|
|
781
|
-
.tv-root .tv-card-body, .tv-root .tv-row-body { padding:
|
|
1026
|
+
.tv-root .tv-card-body, .tv-root .tv-row-body { padding:4px 14px 20px }
|
|
1027
|
+
/* Score strip — body 顶部分布条:综合 + 三层 + 断言,浅底 chip 行 */
|
|
1028
|
+
.tv-root .tv-scores { display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 20px;margin:0 0 18px;padding:12px 16px;background:var(--tv-bg-soft);border-radius:6px;font-size:13.5px }
|
|
1029
|
+
.tv-root .tv-score-item { color:var(--tv-text-2);display:inline-flex;align-items:baseline;gap:5px }
|
|
1030
|
+
.tv-root .tv-score-item--primary { color:var(--tv-text);font-weight:600 }
|
|
1031
|
+
.tv-root .tv-score-v { font-weight:700;font-variant-numeric:tabular-nums;font-family:"SF Mono","JetBrains Mono",Menlo,monospace }
|
|
1032
|
+
.tv-root .tv-score-v--pass { color:var(--tv-pass) }
|
|
1033
|
+
.tv-root .tv-score-v--warn { color:var(--tv-warn) }
|
|
1034
|
+
.tv-root .tv-score-v--fail { color:var(--tv-fail) }
|
|
1035
|
+
/* Judge 评价文本块 — 与 diagnostic 并列,judge 答"打几分 + 为什么" */
|
|
1036
|
+
.tv-root .tv-judge { margin:0 0 18px }
|
|
1037
|
+
.tv-root .tv-judge-h { font-size:13px;font-weight:600;color:var(--tv-text-2);margin-bottom:6px;letter-spacing:0.02em;display:flex;align-items:center;gap:8px;flex-wrap:wrap }
|
|
1038
|
+
.tv-root .tv-judge-text { font-size:14px;line-height:1.75;color:var(--tv-text);white-space:pre-wrap;word-break:break-word }
|
|
1039
|
+
.tv-root .tv-judge-stab { font-size:12px;color:var(--tv-text-3);font-weight:500;font-family:"SF Mono",Menlo,monospace;background:var(--tv-bg-soft);padding:1px 6px;border-radius:3px }
|
|
1040
|
+
.tv-root .tv-judge-stab--fail { color:var(--tv-fail);background:var(--tv-fail-soft) }
|
|
1041
|
+
/* 多评委 ensemble 分项 */
|
|
1042
|
+
.tv-root .tv-ensemble { margin-top:10px }
|
|
1043
|
+
.tv-root .tv-ensemble-row { font-size:13.5px;margin-top:6px;color:var(--tv-text-2) }
|
|
1044
|
+
.tv-root .tv-ensemble-row b { color:var(--tv-text);font-weight:600 }
|
|
1045
|
+
.tv-root .tv-ensemble-agree { font-size:12px;color:var(--tv-text-3);margin-top:8px }
|
|
782
1046
|
/* Tripwire banner — 卡片顶部的紫色解释带 */
|
|
783
|
-
.tv-root .tv-tripwire-banner { margin
|
|
1047
|
+
.tv-root .tv-tripwire-banner { margin:0 0 18px;padding:14px 16px;background:var(--tv-tripwire-soft);border-radius:8px }
|
|
784
1048
|
.tv-root .tv-tripwire-banner-title { font-size:14px;font-weight:600;color:var(--tv-tripwire);margin-bottom:6px;letter-spacing:0.02em }
|
|
785
1049
|
.tv-root .tv-tripwire-banner-body { font-size:14px;color:var(--tv-text-2);line-height:1.7 }
|
|
786
1050
|
/* Diagnosis — 一段白话 + 单一蓝色建议块 + 折叠细节 */
|
|
787
1051
|
.tv-root .tv-diag { margin:4px 0 16px }
|
|
788
1052
|
.tv-root .tv-diag-text { font-size:15px;line-height:1.75;color:var(--tv-text);margin:0 0 16px }
|
|
789
|
-
.tv-root .tv-suggestion { background:rgba(
|
|
1053
|
+
.tv-root .tv-suggestion { background:rgba(79,70,229,.06);border-left:3px solid var(--tv-accent);padding:14px 18px;margin:12px 0;border-radius:0 4px 4px 0 }
|
|
790
1054
|
.tv-root .tv-suggestion-label { display:inline-block;color:var(--tv-accent);font-weight:600;font-size:13px;margin-right:8px;letter-spacing:0.02em }
|
|
791
1055
|
.tv-root .tv-suggestion-body { margin:6px 0 0;font-size:14.5px;line-height:1.7;color:var(--tv-text) }
|
|
792
1056
|
.tv-root .tv-diag-details { margin-top:14px }
|
|
@@ -800,18 +1064,18 @@ export const TEST_VIEW_CSS = `
|
|
|
800
1064
|
/* RootCause inline pill */
|
|
801
1065
|
.tv-root .tv-diag-rc-pill { font-size:11px;padding:2px 8px;border-radius:10px;background:var(--tv-bg-soft);color:var(--tv-text-2);margin-right:6px;display:inline-block }
|
|
802
1066
|
.tv-root .tv-diag-rc--tripwire_intentional { background:rgba(139,124,147,.12);color:var(--tv-tripwire) }
|
|
803
|
-
.tv-root .tv-diag-rc--sample_design { background:rgba(
|
|
804
|
-
.tv-root .tv-diag-rc--skill_doc_unclear, .tv-diag-rc--skill_doc_missing, .tv-root .tv-diag-rc--llm_misread { background:rgba(
|
|
1067
|
+
.tv-root .tv-diag-rc--sample_design { background:rgba(79,70,229,.10);color:var(--tv-accent) }
|
|
1068
|
+
.tv-root .tv-diag-rc--skill_doc_unclear, .tv-diag-rc--skill_doc_missing, .tv-root .tv-diag-rc--llm_misread { background:rgba(217,119,6,.10);color:var(--tv-warn) }
|
|
805
1069
|
/* Failure-mode chips — 中文标签,显著展示在 summary 下 */
|
|
806
1070
|
.tv-root .tv-diag-modes { display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:8px 0 14px }
|
|
807
1071
|
.tv-root .tv-diag-modes-label { font-size:12px;color:var(--tv-text-2);margin-right:4px }
|
|
808
1072
|
.tv-root .tv-diag-mode-pill { font-size:12px;padding:3px 10px;border-radius:11px;background:var(--tv-bg-soft);color:var(--tv-text);font-weight:500 }
|
|
809
|
-
.tv-root .tv-diag-mode--workflow-skip { background:rgba(
|
|
810
|
-
.tv-root .tv-diag-mode--hardcoded { background:rgba(
|
|
1073
|
+
.tv-root .tv-diag-mode--workflow-skip { background:rgba(217,119,6,.12);color:var(--tv-warn) }
|
|
1074
|
+
.tv-root .tv-diag-mode--hardcoded { background:rgba(217,119,6,.12);color:var(--tv-warn) }
|
|
811
1075
|
.tv-root .tv-diag-mode--hallucination { background:rgba(180,75,75,.14);color:#b44b4b }
|
|
812
|
-
.tv-root .tv-diag-mode--tool-misuse { background:rgba(
|
|
813
|
-
.tv-root .tv-diag-mode--env-blocked { background:rgba(
|
|
814
|
-
.tv-root .tv-diag-mode--misread { background:rgba(
|
|
1076
|
+
.tv-root .tv-diag-mode--tool-misuse { background:rgba(217,119,6,.12);color:var(--tv-warn) }
|
|
1077
|
+
.tv-root .tv-diag-mode--env-blocked { background:rgba(79,70,229,.12);color:var(--tv-accent) }
|
|
1078
|
+
.tv-root .tv-diag-mode--misread { background:rgba(217,119,6,.12);color:var(--tv-warn) }
|
|
815
1079
|
.tv-root .tv-diag-mode--other { background:var(--tv-bg-soft);color:var(--tv-text-2) }
|
|
816
1080
|
/* Workflow checklist — 默认展开,放在 suggestion 之上,直观展示 LLM 走对哪几步 */
|
|
817
1081
|
.tv-root .tv-diag-workflow { margin:14px 0;padding:12px 14px;background:var(--tv-bg-soft);border-radius:6px }
|
|
@@ -858,14 +1122,14 @@ export const TEST_VIEW_CSS = `
|
|
|
858
1122
|
.tv-root .tv-turn-dur { color:var(--tv-text-3);font-weight:400;font-size:12px;margin-left:6px }
|
|
859
1123
|
.tv-root .tv-thinking { margin:6px 0;font-size:13px }
|
|
860
1124
|
.tv-root .tv-thinking summary { cursor:pointer;color:var(--tv-text-3);font-style:italic }
|
|
861
|
-
.tv-root .tv-thinking-body { padding:10px 14px;background:rgba(
|
|
1125
|
+
.tv-root .tv-thinking-body { padding:10px 14px;background:rgba(0,0,0,.04);border-radius:4px;color:var(--tv-text-2);white-space:pre-wrap;font-size:13px;line-height:1.7;margin-top:6px }
|
|
862
1126
|
/* Tool calls */
|
|
863
|
-
.tv-root .tv-call { margin:6px 0;padding:10px 14px;background:rgba(
|
|
1127
|
+
.tv-root .tv-call { margin:6px 0;padding:10px 14px;background:rgba(0,0,0,.03);border-radius:4px;font-size:13px }
|
|
864
1128
|
.tv-root .tv-call--err { border-left:2px solid var(--tv-fail) }
|
|
865
1129
|
.tv-root .tv-call-h { font-weight:500;color:var(--tv-text);margin-bottom:4px;font-size:14px }
|
|
866
1130
|
.tv-root .tv-call details { margin:4px 0;font-size:12px }
|
|
867
1131
|
.tv-root .tv-call summary { cursor:pointer;color:var(--tv-text-3);padding:2px 0 }
|
|
868
|
-
.tv-root .tv-pre { font-family:"SF Mono","JetBrains Mono",Menlo,Consolas,monospace;font-size:13px;background:rgba(
|
|
1132
|
+
.tv-root .tv-pre { font-family:"SF Mono","JetBrains Mono",Menlo,Consolas,monospace;font-size:13px;background:rgba(0,0,0,.04);padding:10px 14px;border-radius:4px;overflow:auto;max-height:320px;white-space:pre-wrap;margin:4px 0;color:var(--tv-text-2);line-height:1.55 }
|
|
869
1133
|
.tv-root .tv-mock-badge { font-size:12px;color:var(--tv-pass);background:rgba(125,155,118,.12);padding:2px 8px;border-radius:10px;margin-left:8px }
|
|
870
1134
|
.tv-root .tv-trace-assert { font-size:13px;font-family:"SF Mono",Menlo,monospace;padding:3px 0;margin-top:6px;line-height:1.5 }
|
|
871
1135
|
.tv-root .tv-trace-assert.tv-assert--fail { color:var(--tv-fail) }
|
|
@@ -877,28 +1141,15 @@ export const TEST_VIEW_CSS = `
|
|
|
877
1141
|
.tv-root .tv-assert { font-size:14px;font-family:"SF Mono",Menlo,monospace;padding:5px 0;line-height:1.6 }
|
|
878
1142
|
.tv-root .tv-assert--pass { color:var(--tv-text-2) }
|
|
879
1143
|
.tv-root .tv-assert--fail { color:var(--tv-fail) }
|
|
880
|
-
.tv-root .tv-assert-value { background:rgba(
|
|
1144
|
+
.tv-root .tv-assert-value { background:rgba(0,0,0,.04);padding:2px 6px;border-radius:3px;margin:0 2px }
|
|
881
1145
|
.tv-root .tv-turn-tag { font-size:12px;color:var(--tv-text-3);margin-left:8px;font-style:italic;font-family:-apple-system,sans-serif }
|
|
882
1146
|
.tv-root .tv-turn-tag--ok { color:var(--tv-pass) }
|
|
883
1147
|
.tv-root .tv-turn-tag--miss { color:var(--tv-fail) }
|
|
884
1148
|
.tv-root .tv-assert-msg { font-size:13px;color:var(--tv-text-3);margin-left:20px;margin-top:3px }
|
|
885
1149
|
/* Empty/warn states */
|
|
886
1150
|
.tv-root .tv-empty, .tv-root .tv-empty-page { color:var(--tv-text-3);font-style:italic;padding:14px 0;font-size:14px }
|
|
887
|
-
.tv-root .tv-warn { color:var(--tv-warn);background:rgba(
|
|
888
|
-
`;
|
|
889
|
-
/** 单测视角配套 JS:variant 切换 + view tab 切换。挂在 layout 末尾。 */
|
|
890
|
-
export const TEST_VIEW_JS = `
|
|
891
|
-
function omkSwitchVariant(btn) {
|
|
892
|
-
var target = btn.getAttribute('data-target');
|
|
893
|
-
var tabs = btn.parentElement.querySelectorAll('.tv-vtab');
|
|
894
|
-
tabs.forEach(function(t){ t.classList.toggle('tv-vtab--active', t === btn); });
|
|
895
|
-
var panels = btn.parentElement.parentElement.querySelectorAll('.tv-variant-panel');
|
|
896
|
-
panels.forEach(function(p){ p.hidden = (p.getAttribute('data-variant') !== target); });
|
|
897
|
-
}
|
|
898
|
-
function omkSwitchView(name) {
|
|
899
|
-
var tabs = document.querySelectorAll('.omk-view-tab');
|
|
900
|
-
tabs.forEach(function(t){ t.classList.toggle('omk-view-tab--active', t.getAttribute('data-view') === name); });
|
|
901
|
-
var panels = document.querySelectorAll('.omk-view-panel');
|
|
902
|
-
panels.forEach(function(p){ p.hidden = (p.getAttribute('data-view') !== name); });
|
|
903
|
-
}
|
|
1151
|
+
.tv-root .tv-warn { color:var(--tv-warn);background:rgba(217,119,6,.08);padding:14px 18px;border-radius:4px;margin:8px 24px;font-size:14px;line-height:1.7 }
|
|
904
1152
|
`;
|
|
1153
|
+
/** 融合单页全部用原生 <details> 折叠,逐用例与各轮对比都不再需要 JS 切换。
|
|
1154
|
+
* 保留导出(html-renderer 仍引用),内容置空。 */
|
|
1155
|
+
export const TEST_VIEW_JS = '';
|