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
|
@@ -2,7 +2,7 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { createExecutor } from '../../executors/index.js';
|
|
3
3
|
import { readPromptDocument } from '../../shared/llm-prompts/index.js';
|
|
4
4
|
import { DEFAULT_LLM_ENHANCED_REVIEW_MODEL, PROMPTS_DIR, SOFT_STANDARD_PROMPT_ID, SOFT_STANDARD_PROMPT_VERSION, } from './constants.js';
|
|
5
|
-
import { hashText, loadExisting, markStale, skillDerivedStandardsDir, skillDerivedStandardsPath, } from './skill-standards-store.js';
|
|
5
|
+
import { hashText, loadExisting, markStale, SKILL_DERIVED_STANDARDS_SCHEMA_VERSION, skillDerivedStandardsDir, skillDerivedStandardsPath, } from './skill-standards-store.js';
|
|
6
6
|
import { evaluateRuntimeStandardNodes } from './runtime-evaluator.js';
|
|
7
7
|
export async function extractSkillSoftStandards(options) {
|
|
8
8
|
const { observationsDir, skillChain } = options;
|
|
@@ -47,8 +47,8 @@ export async function extractSkillSoftStandards(options) {
|
|
|
47
47
|
source: 'llm_soft_standard',
|
|
48
48
|
}));
|
|
49
49
|
const record = {
|
|
50
|
-
|
|
51
|
-
schemaVersion:
|
|
50
|
+
kind: 'observe-skill-derived-standards',
|
|
51
|
+
schemaVersion: SKILL_DERIVED_STANDARDS_SCHEMA_VERSION,
|
|
52
52
|
skillName: skillChain.skillName,
|
|
53
53
|
sourceSkillPath: skillChain.definition.path,
|
|
54
54
|
sourceHash,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResolveSkillStandardsOptions, ResolvedSkillStandards, SkillDerivedStandardStatus, SkillDerivedStandards } from './types.js';
|
|
2
|
+
export declare const SKILL_DERIVED_STANDARDS_SCHEMA_VERSION = 2;
|
|
2
3
|
export declare function skillDerivedStandardsDir(observationsDir: string): string;
|
|
3
4
|
export declare function skillDerivedStandardsPath(observationsDir: string, skillName: string): string;
|
|
4
5
|
export declare function loadSkillDerivedStandards(observationsDir: string): Record<string, SkillDerivedStandards>;
|
|
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { buildObservationSkillChain } from '../skill-chain.js';
|
|
5
|
+
export const SKILL_DERIVED_STANDARDS_SCHEMA_VERSION = 2;
|
|
5
6
|
export function skillDerivedStandardsDir(observationsDir) {
|
|
6
7
|
return join(observationsDir, 'skill-derived');
|
|
7
8
|
}
|
|
@@ -17,8 +18,8 @@ export function loadSkillDerivedStandards(observationsDir) {
|
|
|
17
18
|
if (!file.endsWith('.json'))
|
|
18
19
|
continue;
|
|
19
20
|
try {
|
|
20
|
-
const parsed = JSON.parse(readFileSync(join(dir, file), 'utf-8'));
|
|
21
|
-
if (
|
|
21
|
+
const parsed = normalizeSkillDerivedStandards(JSON.parse(readFileSync(join(dir, file), 'utf-8')));
|
|
22
|
+
if (parsed)
|
|
22
23
|
out[parsed.skillName] = parsed;
|
|
23
24
|
}
|
|
24
25
|
catch {
|
|
@@ -51,11 +52,13 @@ export function updateSkillDerivedStandardStatus(observationsDir, skillName, sta
|
|
|
51
52
|
}
|
|
52
53
|
export function resolveSkillStandards(skillName, options) {
|
|
53
54
|
const skillChain = options.skillChain ?? buildObservationSkillChain(skillName, options.cwd ?? process.cwd());
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
const directDerived = normalizeSkillDerivedStandards(options.derivedStandards);
|
|
56
|
+
const mappedDerived = options.derivedStandards && !isSkillDerivedStandards(options.derivedStandards)
|
|
57
|
+
? normalizeSkillDerivedStandards(options.derivedStandards[skillName])
|
|
58
|
+
: undefined;
|
|
59
|
+
const derived = directDerived
|
|
60
|
+
?? mappedDerived
|
|
61
|
+
?? loadSkillDerivedStandards(options.observationsDir)[skillName];
|
|
59
62
|
const active = [];
|
|
60
63
|
const candidates = [];
|
|
61
64
|
const hasFrontmatterHardRules = skillChain.healthCheck.hardRules.declared && skillChain.healthCheck.hardRules.rules.length > 0;
|
|
@@ -112,8 +115,7 @@ export function loadExisting(path) {
|
|
|
112
115
|
if (!existsSync(path))
|
|
113
116
|
return undefined;
|
|
114
117
|
try {
|
|
115
|
-
|
|
116
|
-
return isSkillDerivedStandards(parsed) ? parsed : undefined;
|
|
118
|
+
return normalizeSkillDerivedStandards(JSON.parse(readFileSync(path, 'utf-8')));
|
|
117
119
|
}
|
|
118
120
|
catch {
|
|
119
121
|
return undefined;
|
|
@@ -129,13 +131,74 @@ export function markStale(record, generatedAt) {
|
|
|
129
131
|
};
|
|
130
132
|
}
|
|
131
133
|
export function isSkillDerivedStandards(value) {
|
|
134
|
+
return normalizeSkillDerivedStandards(value) !== undefined;
|
|
135
|
+
}
|
|
136
|
+
function normalizeSkillDerivedStandards(value) {
|
|
132
137
|
if (!value || typeof value !== 'object')
|
|
133
|
-
return
|
|
138
|
+
return undefined;
|
|
134
139
|
const item = value;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
const kind = item.kind === 'observe-skill-derived-standards' ? item.kind : null;
|
|
141
|
+
if (!kind)
|
|
142
|
+
return undefined;
|
|
143
|
+
if (item.schemaVersion !== SKILL_DERIVED_STANDARDS_SCHEMA_VERSION)
|
|
144
|
+
return undefined;
|
|
145
|
+
if (typeof item.skillName !== 'string' || typeof item.generatedAt !== 'string' || typeof item.model !== 'string' || typeof item.executor !== 'string') {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
if (typeof item.promptId !== 'string' || typeof item.promptVersion !== 'string' || !Array.isArray(item.standards))
|
|
149
|
+
return undefined;
|
|
150
|
+
const standards = item.standards
|
|
151
|
+
.map(normalizeSkillDerivedStandard)
|
|
152
|
+
.filter((record) => record !== null);
|
|
153
|
+
return {
|
|
154
|
+
kind: 'observe-skill-derived-standards',
|
|
155
|
+
schemaVersion: SKILL_DERIVED_STANDARDS_SCHEMA_VERSION,
|
|
156
|
+
skillName: item.skillName,
|
|
157
|
+
...(typeof item.sourceSkillPath === 'string' ? { sourceSkillPath: item.sourceSkillPath } : {}),
|
|
158
|
+
...(typeof item.sourceHash === 'string' ? { sourceHash: item.sourceHash } : {}),
|
|
159
|
+
generatedAt: item.generatedAt,
|
|
160
|
+
model: item.model,
|
|
161
|
+
executor: item.executor,
|
|
162
|
+
promptId: item.promptId,
|
|
163
|
+
promptVersion: item.promptVersion,
|
|
164
|
+
...(typeof item.promptHash === 'string' ? { promptHash: item.promptHash } : {}),
|
|
165
|
+
...(typeof item.runtimeEvidenceHash === 'string' ? { runtimeEvidenceHash: item.runtimeEvidenceHash } : {}),
|
|
166
|
+
...(item.enhancedReview && typeof item.enhancedReview === 'object' ? { enhancedReview: item.enhancedReview } : {}),
|
|
167
|
+
standards,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function normalizeSkillDerivedStandard(value) {
|
|
171
|
+
if (!value || typeof value !== 'object')
|
|
172
|
+
return null;
|
|
173
|
+
const item = value;
|
|
174
|
+
const standardKind = item.standardKind === 'hard_rule_candidate' || item.standardKind === 'workflow_candidate'
|
|
175
|
+
? item.standardKind
|
|
176
|
+
: item.kind === 'hard_rule_candidate' || item.kind === 'workflow_candidate'
|
|
177
|
+
? item.kind
|
|
178
|
+
: null;
|
|
179
|
+
if (!standardKind)
|
|
180
|
+
return null;
|
|
181
|
+
if (typeof item.id !== 'string' || typeof item.title !== 'string' || typeof item.body !== 'string')
|
|
182
|
+
return null;
|
|
183
|
+
if (item.status !== 'pending_review' && item.status !== 'author_confirmed' && item.status !== 'rejected' && item.status !== 'stale') {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (item.source !== 'llm_soft_standard')
|
|
187
|
+
return null;
|
|
188
|
+
if (item.confidence !== 'low' && item.confidence !== 'medium' && item.confidence !== 'high')
|
|
189
|
+
return null;
|
|
190
|
+
if (!Array.isArray(item.evidence) || item.evidence.some((entry) => typeof entry !== 'string'))
|
|
191
|
+
return null;
|
|
192
|
+
return {
|
|
193
|
+
id: item.id,
|
|
194
|
+
standardKind,
|
|
195
|
+
status: item.status,
|
|
196
|
+
title: item.title,
|
|
197
|
+
body: item.body,
|
|
198
|
+
source: item.source,
|
|
199
|
+
confidence: item.confidence,
|
|
200
|
+
evidence: item.evidence,
|
|
201
|
+
};
|
|
139
202
|
}
|
|
140
203
|
function candidateRank(status) {
|
|
141
204
|
if (status === 'pending_review')
|
|
@@ -14,8 +14,8 @@ export interface SkillDerivedStandard {
|
|
|
14
14
|
evidence: string[];
|
|
15
15
|
}
|
|
16
16
|
export interface SkillDerivedStandards {
|
|
17
|
-
|
|
18
|
-
schemaVersion:
|
|
17
|
+
kind: 'observe-skill-derived-standards';
|
|
18
|
+
schemaVersion: 2;
|
|
19
19
|
skillName: string;
|
|
20
20
|
sourceSkillPath?: string;
|
|
21
21
|
sourceHash?: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor 报告详情页 — 逐条规则的健康检查结果。
|
|
3
|
+
*
|
|
4
|
+
* 主次:Hero 综合健康分 → 统计条(通过/警告/失败)→ 规则列表(失败/警告高亮在前,
|
|
5
|
+
* 通过项折叠)。每条规则展开显示 finding 描述 + 修复建议。
|
|
6
|
+
*/
|
|
7
|
+
import { type SkillReportContext } from './report-shell.js';
|
|
8
|
+
import type { Lang, DoctorReport } from '../types/index.js';
|
|
9
|
+
export declare function renderDoctorDetail(report: DoctorReport, skillName: string, langQ: string, lang?: Lang, skillContext?: SkillReportContext): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor 报告详情页 — 逐条规则的健康检查结果。
|
|
3
|
+
*
|
|
4
|
+
* 主次:Hero 综合健康分 → 统计条(通过/警告/失败)→ 规则列表(失败/警告高亮在前,
|
|
5
|
+
* 通过项折叠)。每条规则展开显示 finding 描述 + 修复建议。
|
|
6
|
+
*/
|
|
7
|
+
import { reportShell, healthColor } from './report-shell.js';
|
|
8
|
+
import { icon as svgIcon } from './icons.js';
|
|
9
|
+
import { e, DEFAULT_LANG } from './layout.js';
|
|
10
|
+
function getDetail(r) {
|
|
11
|
+
return (r.detail ?? {});
|
|
12
|
+
}
|
|
13
|
+
const SORT_ORDER = { fail: 0, warn: 1, pass: 2, skipped: 3 };
|
|
14
|
+
function fmtTs(ts) {
|
|
15
|
+
try {
|
|
16
|
+
const d = new Date(ts);
|
|
17
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return ts;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function renderRuleCard(r, lang, forceOpen = false) {
|
|
24
|
+
const det = getDetail(r);
|
|
25
|
+
const name = det.displayName ?? r.ruleId;
|
|
26
|
+
const findings = det.findings ?? [];
|
|
27
|
+
const errors = findings.filter((f) => f.level === '错误');
|
|
28
|
+
const warns = findings.filter((f) => f.level === '警告');
|
|
29
|
+
const tips = findings.filter((f) => f.level === '建议');
|
|
30
|
+
const cardCls = r.status === 'fail' ? 'fail' : r.status === 'warn' ? 'warn' : 'pass';
|
|
31
|
+
const dotCls = r.status === 'fail' ? 'fail' : r.status === 'warn' ? 'warn' : r.status === 'skipped' ? 'skip' : 'pass';
|
|
32
|
+
const badges = [];
|
|
33
|
+
if (errors.length)
|
|
34
|
+
badges.push(`<span class="rs-badge rs-badge--fail">${errors.length} ${lang === 'zh' ? '错误' : 'err'}</span>`);
|
|
35
|
+
if (warns.length)
|
|
36
|
+
badges.push(`<span class="rs-badge rs-badge--warn">${warns.length} ${lang === 'zh' ? '警告' : 'warn'}</span>`);
|
|
37
|
+
if (!errors.length && !warns.length)
|
|
38
|
+
badges.push(`<span class="rs-badge rs-badge--pass">${lang === 'zh' ? '通过' : 'pass'}</span>`);
|
|
39
|
+
const head = `<span class="rs-card-dot rs-card-dot--${dotCls}"></span><span class="rs-card-name">${e(name)}</span><span class="rs-card-badges">${badges.join('')}</span>`;
|
|
40
|
+
const allFindings = [...errors, ...warns, ...tips];
|
|
41
|
+
if (allFindings.length === 0) {
|
|
42
|
+
return `<div class="rs-card rs-card--${cardCls}"><div class="rs-card-head" style="cursor:default">${head}</div></div>`;
|
|
43
|
+
}
|
|
44
|
+
const findingHtml = allFindings.map((f) => {
|
|
45
|
+
const isErr = f.level === '错误';
|
|
46
|
+
const mIcon = isErr
|
|
47
|
+
? svgIcon('x', { size: 14, style: 'color:#dc2626;vertical-align:-2px;margin-right:5px' })
|
|
48
|
+
: svgIcon('warn', { size: 14, style: `color:${f.level === '警告' ? '#d97706' : '#637083'};vertical-align:-2px;margin-right:5px` });
|
|
49
|
+
return `<div class="rs-finding rs-finding--${isErr ? 'err' : 'warn'}">
|
|
50
|
+
<div class="rs-finding-desc">${mIcon}${e(f.description ?? '')}</div>
|
|
51
|
+
${f.suggestion ? `<div class="rs-finding-sug">${svgIcon('bulb', { size: 13, style: 'vertical-align:-2px;margin-right:5px' })}${e(f.suggestion)}</div>` : ''}
|
|
52
|
+
</div>`;
|
|
53
|
+
}).join('');
|
|
54
|
+
return `<details class="rs-card rs-card--${cardCls}" ${errors.length || forceOpen ? 'open' : ''}>
|
|
55
|
+
<summary class="rs-card-head">${head}</summary>
|
|
56
|
+
<div class="rs-card-body">${findingHtml}</div>
|
|
57
|
+
</details>`;
|
|
58
|
+
}
|
|
59
|
+
function renderSkillBlock(sk, lang) {
|
|
60
|
+
const rules = sk.results.filter((r) => !r.ruleId.endsWith(':_summary'));
|
|
61
|
+
const pass = rules.filter((r) => r.status === 'pass').length;
|
|
62
|
+
const warn = rules.filter((r) => r.status === 'warn').length;
|
|
63
|
+
const fail = rules.filter((r) => r.status === 'fail').length;
|
|
64
|
+
const total = rules.length;
|
|
65
|
+
const sorted = [...rules].sort((a, b) => (SORT_ORDER[a.status] ?? 9) - (SORT_ORDER[b.status] ?? 9));
|
|
66
|
+
const issues = sorted.filter((r) => r.status === 'fail' || r.status === 'warn');
|
|
67
|
+
const oks = sorted.filter((r) => r.status === 'pass' || r.status === 'skipped');
|
|
68
|
+
const zh = lang === 'zh';
|
|
69
|
+
const stats = `<div class="rs-stats">
|
|
70
|
+
<div class="rs-stat rs-stat--pass"><span class="rs-stat-num">${pass}</span><span class="rs-stat-label">${zh ? '通过' : 'Pass'}</span></div>
|
|
71
|
+
<div class="rs-stat rs-stat--warn"><span class="rs-stat-num">${warn}</span><span class="rs-stat-label">${zh ? '警告' : 'Warn'}</span></div>
|
|
72
|
+
<div class="rs-stat rs-stat--fail"><span class="rs-stat-num">${fail}</span><span class="rs-stat-label">${zh ? '失败' : 'Fail'}</span></div>
|
|
73
|
+
<div class="rs-stat rs-stat--total"><span class="rs-stat-num">${total}</span><span class="rs-stat-label">${zh ? '规则总数' : 'Rules'}</span></div>
|
|
74
|
+
</div>`;
|
|
75
|
+
const issuesHtml = issues.length
|
|
76
|
+
? `<div class="rs-panel"><div class="rs-panel-title">${zh ? '待处理项' : 'Issues'} <small>${issues.length}</small></div>${issues.map((r, i) => renderRuleCard(r, lang, i === 0)).join('')}</div>`
|
|
77
|
+
: `<div class="rs-panel"><div class="rs-empty">${zh ? '✓ 所有规则通过,无待处理项' : '✓ All rules passed'}</div></div>`;
|
|
78
|
+
const oksHtml = oks.length
|
|
79
|
+
? `<div class="rs-panel"><details class="rs-fold"><summary>${zh ? `展开 ${oks.length} 条通过的规则` : `Show ${oks.length} passed rules`}</summary>${oks.map((r) => renderRuleCard(r, lang)).join('')}</details></div>`
|
|
80
|
+
: '';
|
|
81
|
+
return stats + issuesHtml + oksHtml;
|
|
82
|
+
}
|
|
83
|
+
export function renderDoctorDetail(report, skillName, langQ, lang = DEFAULT_LANG, skillContext) {
|
|
84
|
+
const zh = lang === 'zh';
|
|
85
|
+
// 优先展示点进来的 skill;找不到就展示全部
|
|
86
|
+
const targetSkill = report.skills.find((s) => s.skillName === skillName) ?? report.skills[0];
|
|
87
|
+
const rules = (targetSkill?.results ?? []).filter((r) => !r.ruleId.endsWith(':_summary'));
|
|
88
|
+
const pass = rules.filter((r) => r.status === 'pass').length;
|
|
89
|
+
const warn = rules.filter((r) => r.status === 'warn').length;
|
|
90
|
+
const total = rules.length;
|
|
91
|
+
const score = total > 0 ? Math.round(((pass + warn * 0.5) / total) * 100) : null;
|
|
92
|
+
// 与 eval「综合分 / 5」统一成「X分 / 满分」形式;圆心显原生分数,等级由颜色传达。
|
|
93
|
+
const scoreLabel = zh ? '健康分 / 100' : 'Health / 100';
|
|
94
|
+
const body = targetSkill
|
|
95
|
+
? renderSkillBlock(targetSkill, lang)
|
|
96
|
+
: `<div class="rs-panel"><div class="rs-empty">${zh ? '报告无 skill 数据' : 'No skill data'}</div></div>`;
|
|
97
|
+
return reportShell({
|
|
98
|
+
dim: 'doctor',
|
|
99
|
+
icon: '🩺',
|
|
100
|
+
kindTitle: zh ? '健康体检报告' : 'Doctor Report',
|
|
101
|
+
skillName: targetSkill?.skillName ?? skillName,
|
|
102
|
+
metaItems: [`${svgIcon('clock', { size: 13 })} ${e(fmtTs(report.timestamp))}`, `${svgIcon('chip', { size: 13 })} ${e(report.model)}`, e(report.id)],
|
|
103
|
+
// Hero 展示「综合健康」总分(跨维度,与首页同口径),切 tab 不变;本次体检明细(规则/计数)在 body。
|
|
104
|
+
// 无 skillContext 时回退显本次体检健康分。
|
|
105
|
+
score: skillContext ? skillContext.overall.score : score,
|
|
106
|
+
scoreText: skillContext && skillContext.overall.score != null ? String(skillContext.overall.score) : undefined,
|
|
107
|
+
ringColor: skillContext ? healthColor(skillContext.overall.score) : undefined,
|
|
108
|
+
scoreLabel: skillContext ? (zh ? '综合健康' : 'Health') : scoreLabel,
|
|
109
|
+
backHref: `/${langQ}`,
|
|
110
|
+
backLabel: zh ? '返回列表' : 'Back to list',
|
|
111
|
+
body,
|
|
112
|
+
skillContext,
|
|
113
|
+
}, lang);
|
|
114
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTML report renderer — orchestrates sub-modules.
|
|
3
3
|
*/
|
|
4
|
+
import { type SkillReportContext } from './report-shell.js';
|
|
4
5
|
import type { BatchEvaluationReport, EvaluationReport, Report, ReportDocument, Lang } from '../types/index.js';
|
|
5
6
|
export declare function renderRunList(runs: ReportDocument[], lang?: Lang): string;
|
|
6
|
-
export declare function renderRunDetail(report: EvaluationReport | null, lang?: Lang): string;
|
|
7
|
+
export declare function renderRunDetail(report: EvaluationReport | null, lang?: Lang, skillContext?: SkillReportContext): string;
|
|
7
8
|
export declare function renderBatchEvaluationDetail(report: BatchEvaluationReport | null, lang?: Lang): string;
|
|
8
|
-
export declare function renderReportDocumentDetail(report: ReportDocument | null, lang?: Lang): string;
|
|
9
|
+
export declare function renderReportDocumentDetail(report: ReportDocument | null, lang?: Lang, skillContext?: SkillReportContext): string;
|
|
9
10
|
export declare function renderTrendsPage(variantName: string, runs: Report[], lang?: Lang): string;
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { e, fmtCost, fmtDuration, fmtKnownCost, COLORS, DEFAULT_LANG, t, layout } from './layout.js';
|
|
5
5
|
import { renderAgentOverview, renderAnalysis, renderKnowledgeInteractionSection, renderMethodologyAudit, renderScoringModal, renderSummaryCards, renderVarianceComparisons, renderVerdictPill, levelLabel, levelTooltip, } from './summary.js';
|
|
6
|
-
import { renderSampleTable } from './table.js';
|
|
7
6
|
import { renderTestView, TEST_VIEW_CSS, TEST_VIEW_JS } from './test-view.js';
|
|
8
7
|
import { renderTrendsBody } from './trends.js';
|
|
8
|
+
import { reportShell, healthColor } from './report-shell.js';
|
|
9
|
+
import { icon } from './icons.js';
|
|
9
10
|
import { computeVerdict } from '../eval-core/verdict.js';
|
|
10
11
|
// v0.21 B.4 — 列表页 status pill 用的 dot. PROGRESS/REGRESS 实心(强信号),
|
|
11
12
|
// CAUTIOUS 三角(警示),NOISE 空心圆(有信号但无效果),UNDERPOWERED 部分填充
|
|
@@ -21,7 +22,7 @@ function levelDot(level) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
function isEvaluationReport(document) {
|
|
24
|
-
return document.
|
|
25
|
+
return document.kind === 'evaluation';
|
|
25
26
|
}
|
|
26
27
|
function scoreOf(summary) {
|
|
27
28
|
return summary?.avgCompositeScore ?? summary?.avgLlmScore ?? null;
|
|
@@ -163,7 +164,7 @@ export function renderRunList(runs, lang = DEFAULT_LANG) {
|
|
|
163
164
|
`<span class="rc-score-num rc-score-num--${cls}">${score.toFixed(2)}</span></div>`;
|
|
164
165
|
};
|
|
165
166
|
const cards = runs.map((run) => {
|
|
166
|
-
if (run.
|
|
167
|
+
if (run.kind === 'batch-evaluation') {
|
|
167
168
|
const m = run.meta;
|
|
168
169
|
const scores = run.items.length > 0
|
|
169
170
|
? run.items.map((item) => {
|
|
@@ -316,7 +317,7 @@ export function renderRunList(runs, lang = DEFAULT_LANG) {
|
|
|
316
317
|
</main>
|
|
317
318
|
`, lang);
|
|
318
319
|
}
|
|
319
|
-
export function renderRunDetail(report, lang = DEFAULT_LANG) {
|
|
320
|
+
export function renderRunDetail(report, lang = DEFAULT_LANG, skillContext) {
|
|
320
321
|
const langQ = lang === DEFAULT_LANG ? '' : `?lang=${lang}`;
|
|
321
322
|
if (!report) {
|
|
322
323
|
return layout(t('title', lang), `
|
|
@@ -329,11 +330,24 @@ export function renderRunDetail(report, lang = DEFAULT_LANG) {
|
|
|
329
330
|
const m = report.meta;
|
|
330
331
|
const variants = m.variants || [];
|
|
331
332
|
const summary = report.summary || {};
|
|
332
|
-
const results = report.results || [];
|
|
333
333
|
const cards = renderSummaryCards(variants, summary, lang, report.variance);
|
|
334
334
|
const methodologyAudit = renderMethodologyAudit(report, variants, summary, lang);
|
|
335
|
-
|
|
336
|
-
const
|
|
335
|
+
// 单 variant(SOLO)不显示 verdict — 对照才有的概念,单组是噪音/误导(见融合设计)。
|
|
336
|
+
const verdictPill = variants.length > 1 ? renderVerdictPill(report, lang) : '';
|
|
337
|
+
// Hero:综合分 ring(取 treatment = 最后一个 variant 的综合分)+ skill 名 + 时间戳。
|
|
338
|
+
const treatmentName = variants[variants.length - 1];
|
|
339
|
+
const heroComposite = treatmentName ? (summary[treatmentName]?.avgCompositeScore ?? null) : null;
|
|
340
|
+
const heroScore100 = heroComposite != null ? Math.round((heroComposite / 5) * 100) : null;
|
|
341
|
+
const heroSkillName = report.meta.evolve?.skillName ?? treatmentName ?? report.id;
|
|
342
|
+
const heroTs = (() => {
|
|
343
|
+
try {
|
|
344
|
+
const d = new Date(report.meta.timestamp);
|
|
345
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return '';
|
|
349
|
+
}
|
|
350
|
+
})();
|
|
337
351
|
const totalExecCost = Object.values(summary).reduce((s, v) => s + (v.totalExecCostUSD || 0), 0);
|
|
338
352
|
// 任一 variant exec cost 未报告 → 总 cost 不可靠,renderer 显示「—」+ tooltip。
|
|
339
353
|
// 全部 reported(undefined / true)才正常显示 USD 数字。
|
|
@@ -420,14 +434,6 @@ export function renderRunDetail(report, lang = DEFAULT_LANG) {
|
|
|
420
434
|
` : '';
|
|
421
435
|
// v0.30 重设计 — 评分/单测各自独立 title + meta strip(不共享头部)。
|
|
422
436
|
// 顶部只保留极简身份带:返回链接 + run-id pill。完整 H1 + meta tags 移进各自 tab 面板。
|
|
423
|
-
const idStamp = (() => {
|
|
424
|
-
const idMatch = /^.+?-(\d{8})-(\d{4})$/.exec(report.id);
|
|
425
|
-
if (idMatch) {
|
|
426
|
-
const [, ymd, hm] = idMatch;
|
|
427
|
-
return `${ymd.slice(0, 4)}-${ymd.slice(4, 6)}-${ymd.slice(6, 8)} ${hm.slice(0, 2)}:${hm.slice(2, 4)}`;
|
|
428
|
-
}
|
|
429
|
-
return '';
|
|
430
|
-
})();
|
|
431
437
|
// ──────────── 评分 tab 的 meta tags(原版全部留这里) ────────────
|
|
432
438
|
const scoreMetaTags = `<div class="meta-tags">
|
|
433
439
|
<span class="meta-tag">${t('model', lang)}: ${e(m.model)}</span>
|
|
@@ -469,71 +475,61 @@ export function renderRunDetail(report, lang = DEFAULT_LANG) {
|
|
|
469
475
|
${Object.entries(m.blindMap || {}).map(([label, real]) => `<div style="font-size:13px;color:var(--text-secondary)"><strong>Variant ${e(label)}</strong> → ${e(real)}</div>`).join('')}
|
|
470
476
|
</div>
|
|
471
477
|
</div>` : '';
|
|
472
|
-
// ────────────
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
<nav class="nav"><a href="/${langQ}" data-i18n="backToList">${t('backToList', lang)}</a></nav>
|
|
499
|
-
<div class="report-id-line">
|
|
500
|
-
<code class="report-id-pill">${e(report.id)}</code>
|
|
501
|
-
${idStamp ? `<span class="report-id-stamp">${e(idStamp)}</span>` : ''}
|
|
502
|
-
</div>
|
|
503
|
-
|
|
504
|
-
<div class="omk-view-tabs" role="tablist">
|
|
505
|
-
<button type="button" class="omk-view-tab omk-view-tab--active" data-view="score" onclick="omkSwitchView('score')">${lang === 'zh' ? '📊 评测视角' : '📊 Score view'}</button>
|
|
506
|
-
<button type="button" class="omk-view-tab" data-view="test" onclick="omkSwitchView('test')">${lang === 'zh' ? '✅ 功能视角' : '✅ Functional view'}</button>
|
|
507
|
-
</div>
|
|
508
|
-
|
|
509
|
-
<div class="omk-view-panel" data-view="score">
|
|
510
|
-
<p class="subtitle">${lang === 'zh' ? '主观质量打分(judge)+ 客观断言聚合,产出综合评分与 verdict' : 'Subjective judge + objective assertions aggregated into composite + verdict'}</p>
|
|
511
|
-
${verdictPill}
|
|
512
|
-
${scoreMetaTags}
|
|
478
|
+
// ──────────── 融合单页:Hero → 结论(verdict+六维) → 折叠次要区 → 统一逐用例 ────────────
|
|
479
|
+
const agentOverview = renderAgentOverview(variants, summary, lang);
|
|
480
|
+
const varianceSection = renderVarianceComparisons(report.variance, lang, Boolean(report.meta.layeredStats), summary);
|
|
481
|
+
const analysisSection = renderAnalysis(report, lang);
|
|
482
|
+
const knowledgeSection = renderKnowledgeInteractionSection(report.analysis?.coverage, report.analysis?.gapReports, lang);
|
|
483
|
+
// 折叠区 A:实验如何跑的(配置 / 运行参数 / Agent 概览 / 审计指纹)— 复现/审计才看。
|
|
484
|
+
const setupInner = [
|
|
485
|
+
scoreMetaTags,
|
|
486
|
+
(variantConfigSection || agentOverview) ? `<div class="ctx-strip">${variantConfigSection}${agentOverview}</div>` : '',
|
|
487
|
+
auditFingerprints,
|
|
488
|
+
].filter(Boolean).join('');
|
|
489
|
+
const setupFold = setupInner
|
|
490
|
+
? `<details class="ev-fold"><summary>${lang === 'zh' ? '实验配置与运行参数' : 'Experiment setup & run params'}</summary><div class="ev-fold-body">${setupInner}</div></details>`
|
|
491
|
+
: '';
|
|
492
|
+
// 折叠区 B:深入分析(方法学审计 / 方差 / 自动分析 / 知识交互)— 想验证测量学时展开。
|
|
493
|
+
const analysisInner = [methodologyAudit, varianceSection, analysisSection, knowledgeSection].filter(Boolean).join('');
|
|
494
|
+
const analysisFold = analysisInner
|
|
495
|
+
? `<details class="ev-fold"><summary>${lang === 'zh' ? '方法学审计 · 方差 · 自动分析 · 知识交互' : 'Methodology audit · variance · analysis · knowledge'}</summary><div class="ev-fold-body">${analysisInner}</div></details>`
|
|
496
|
+
: '';
|
|
497
|
+
// 「结论」panel:多 variant = verdict 一句话领头 + 六维表;单 variant = 直接六维分布。
|
|
498
|
+
const conclusionPanel = `<section class="ev-conclusion">${verdictPill}${cards}</section>`;
|
|
499
|
+
const samplesSubtitle = lang === 'zh'
|
|
500
|
+
? '每条用例既是评分卡也是功能测试:综合分 + 三层分布、断言、诊断与执行轨迹合一'
|
|
501
|
+
: 'Each sample is both a score card and a functional test: composite + layered scores, assertions, diagnostics, and trace combined';
|
|
502
|
+
const body = `
|
|
503
|
+
${conclusionPanel}
|
|
513
504
|
${blindReveal}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
<
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
505
|
+
${setupFold}
|
|
506
|
+
${analysisFold}
|
|
507
|
+
<section class="ev-samples">
|
|
508
|
+
<h2 class="ev-samples-h">${lang === 'zh' ? '逐用例' : 'Per-sample'}</h2>
|
|
509
|
+
<p class="subtitle ev-samples-sub">${samplesSubtitle}</p>
|
|
510
|
+
${renderTestView(report, lang)}
|
|
511
|
+
</section>${TEST_VIEW_JS ? `<script>${TEST_VIEW_JS}</script>` : ''}`;
|
|
512
|
+
const evalCss = `${TEST_VIEW_CSS}
|
|
513
|
+
/* 结论 panel — verdict(多 variant)+ 六维,白底卡片成「答案」模块,与 hero / fold 同一视觉语言 */
|
|
514
|
+
.ev-conclusion{background:#fff;border:1px solid #e4e8f1;border-radius:12px;box-shadow:0 8px 24px rgba(31,41,55,4%);padding:18px 24px 20px;margin:0 0 16px}
|
|
515
|
+
.ev-conclusion>:first-child{margin-top:0}
|
|
516
|
+
.ev-conclusion>h2:first-child{margin-top:2px}
|
|
517
|
+
/* 次要折叠区 — 实验配置 / 方法学 / 方差 / 分析,默认收起,审计/复现时展开 */
|
|
518
|
+
.ev-fold{margin:10px 0;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius)}
|
|
519
|
+
.ev-fold>summary{cursor:pointer;list-style:none;font-size:13px;font-weight:500;color:var(--text-secondary);padding:12px 16px}
|
|
520
|
+
.ev-fold>summary::-webkit-details-marker{display:none}
|
|
521
|
+
.ev-fold>summary::before{content:'▸ ';color:var(--text-muted)}
|
|
522
|
+
.ev-fold[open]>summary::before{content:'▾ '}
|
|
523
|
+
.ev-fold[open]>summary{border-bottom:1px solid var(--border);color:var(--text-primary)}
|
|
524
|
+
.ev-fold>summary:hover{color:var(--text-primary)}
|
|
525
|
+
.ev-fold-body{padding:8px 16px 16px}
|
|
526
|
+
.ev-fold-body>section:first-child,.ev-fold-body>.audit-fingerprints:first-child{margin-top:0}
|
|
527
|
+
.ev-fold-body .meta-tags{margin-top:8px}
|
|
528
|
+
/* 逐用例 section 标题 */
|
|
529
|
+
.ev-samples{margin-top:28px}
|
|
530
|
+
.ev-samples-h{font-size:1.4rem;margin:0 0 4px;color:var(--text-primary)}
|
|
531
|
+
.ev-samples-sub{font-size:13.5px;color:var(--text-secondary);margin:0 0 8px;line-height:1.6}
|
|
532
|
+
/* Context strip — 实验配置 + Agent 概览并排,辅助元数据 */
|
|
537
533
|
.ctx-strip { display:grid;gap:14px;grid-template-columns:1fr 1fr;margin:14px 0 24px }
|
|
538
534
|
@media (max-width:760px) { .ctx-strip { grid-template-columns:1fr } }
|
|
539
535
|
.ctx-block { background:var(--bg-surface);border-radius:var(--radius);box-shadow:var(--shadow-sm);padding:12px 16px }
|
|
@@ -543,26 +539,30 @@ export function renderRunDetail(report, lang = DEFAULT_LANG) {
|
|
|
543
539
|
.ctx-row { padding:8px 12px;background:var(--bg-soft);border-radius:4px;display:flex;align-items:center;gap:10px;font-size:13px;line-height:1.5;flex-wrap:wrap }
|
|
544
540
|
.ctx-row-name { font-weight:600;color:var(--text-primary);font-family:"SF Mono",Menlo,monospace;font-size:12.5px;letter-spacing:-0.01em }
|
|
545
541
|
.ctx-row-bits { color:var(--text-secondary);flex:1;min-width:0;font-size:12.5px }
|
|
546
|
-
.ctx-sep { color:var(--text-muted);margin:0 2px;opacity:0.6 }
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
542
|
+
.ctx-sep { color:var(--text-muted);margin:0 2px;opacity:0.6 }`;
|
|
543
|
+
return reportShell({
|
|
544
|
+
dim: 'eval',
|
|
545
|
+
icon: '🧪',
|
|
546
|
+
kindTitle: lang === 'zh' ? '用例评测报告' : 'Eval Report',
|
|
547
|
+
// 头部用规范 skill 名(skill-index entry),与 doctor/observe 一致;无 context 时回退到 report 推导名。
|
|
548
|
+
skillName: skillContext?.skillName ?? heroSkillName,
|
|
549
|
+
metaItems: [`${icon('clock', { size: 13 })} ${e(heroTs)}`, `${icon('chip', { size: 13 })} ${e(m.model)}`, e(report.id)],
|
|
550
|
+
// Hero 展示「综合健康」总分(跨 doctor/eval/observe,与首页同口径),切 tab 不变;
|
|
551
|
+
// 本次 eval 的综合分(4.09/5)在下方结论卡。无 skillContext(batch/孤儿报告)时回退显 eval 综合分。
|
|
552
|
+
score: skillContext ? skillContext.overall.score : heroScore100,
|
|
553
|
+
scoreText: skillContext
|
|
554
|
+
? (skillContext.overall.score != null ? String(skillContext.overall.score) : undefined)
|
|
555
|
+
: (heroComposite != null ? heroComposite.toFixed(2) : undefined),
|
|
556
|
+
ringColor: skillContext
|
|
557
|
+
? healthColor(skillContext.overall.score)
|
|
558
|
+
: (heroComposite != null ? (heroComposite >= 4 ? '#1f9d63' : heroComposite >= 3 ? '#d97706' : '#dc2626') : undefined),
|
|
559
|
+
scoreLabel: skillContext ? (lang === 'zh' ? '综合健康' : 'Health') : (lang === 'zh' ? '综合分 / 5' : 'Composite / 5'),
|
|
560
|
+
backHref: `/${langQ}`,
|
|
561
|
+
backLabel: lang === 'zh' ? '返回列表' : 'Back to list',
|
|
562
|
+
body,
|
|
563
|
+
skillContext,
|
|
564
|
+
extraCss: evalCss,
|
|
565
|
+
}, lang);
|
|
566
566
|
}
|
|
567
567
|
export function renderBatchEvaluationDetail(report, lang = DEFAULT_LANG) {
|
|
568
568
|
const langQ = lang === DEFAULT_LANG ? '' : `?lang=${lang}`;
|
|
@@ -649,12 +649,12 @@ export function renderBatchEvaluationDetail(report, lang = DEFAULT_LANG) {
|
|
|
649
649
|
</main>
|
|
650
650
|
`, lang);
|
|
651
651
|
}
|
|
652
|
-
export function renderReportDocumentDetail(report, lang = DEFAULT_LANG) {
|
|
652
|
+
export function renderReportDocumentDetail(report, lang = DEFAULT_LANG, skillContext) {
|
|
653
653
|
if (!report)
|
|
654
654
|
return renderRunDetail(null, lang);
|
|
655
|
-
return report.
|
|
655
|
+
return report.kind === 'batch-evaluation'
|
|
656
656
|
? renderBatchEvaluationDetail(report, lang)
|
|
657
|
-
: renderRunDetail(report, lang);
|
|
657
|
+
: renderRunDetail(report, lang, skillContext);
|
|
658
658
|
}
|
|
659
659
|
export function renderTrendsPage(variantName, runs, lang = DEFAULT_LANG) {
|
|
660
660
|
const body = renderTrendsBody(variantName, runs, lang);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享单色线性 SVG 图标集(替代各处 emoji)。
|
|
3
|
+
*
|
|
4
|
+
* 设计:统一 24×24 viewBox、`stroke="currentColor"`、单一描边权重、圆角端点。
|
|
5
|
+
* 颜色由调用处的 CSS `color` 控制(图标自身不带色),尺寸/粗细可覆盖。
|
|
6
|
+
* 之所以自带一套而不用图标库:报告是离线自包含 HTML,内联 SVG 无需外链/字体。
|
|
7
|
+
*/
|
|
8
|
+
/** name → viewBox 内的 path/shape 串。 */
|
|
9
|
+
declare const PATHS: Record<string, string>;
|
|
10
|
+
export type IconName = keyof typeof PATHS;
|
|
11
|
+
export interface IconOpts {
|
|
12
|
+
/** 像素尺寸(宽高相同),默认 16。 */
|
|
13
|
+
size?: number;
|
|
14
|
+
/** 额外 class。 */
|
|
15
|
+
cls?: string;
|
|
16
|
+
/** 描边权重,默认 1.7。 */
|
|
17
|
+
sw?: number;
|
|
18
|
+
/** 内联 style。 */
|
|
19
|
+
style?: string;
|
|
20
|
+
}
|
|
21
|
+
/** 返回一个自包含 `<svg>` 串。未知 name 返回空串(不抛错,调用处可安全拼接)。 */
|
|
22
|
+
export declare function icon(name: string, opts?: IconOpts): string;
|
|
23
|
+
/** 维度 → 报告类型图标名。 */
|
|
24
|
+
export declare function dimIconName(dim: 'doctor' | 'eval' | 'observe'): IconName;
|
|
25
|
+
/** 品牌 logo 的完整 `<svg>`(给定像素尺寸)。 */
|
|
26
|
+
export declare function brandLogo(size?: number): string;
|
|
27
|
+
/** 品牌 logo 的原始 `<svg>`(无尺寸,供 favicon data URI 编码用)。 */
|
|
28
|
+
export declare const BRAND_LOGO_RAW = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 120 120\" fill=\"none\"><defs><linearGradient id=\"omkAccent\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\"><stop offset=\"0%\" stop-color=\"#60a5fa\"/><stop offset=\"100%\" stop-color=\"#a78bfa\"/></linearGradient></defs><circle cx=\"60\" cy=\"60\" r=\"56\" fill=\"#0f172a\" stroke=\"#1e293b\" stroke-width=\"1.5\"/><path d=\"M24 60 A12 12 0 1 1 36 72\" stroke=\"url(#omkAccent)\" stroke-width=\"3.5\" stroke-linecap=\"round\" fill=\"none\"/><path d=\"M56 72 V57 Q56 48 61.5 48 Q67 48 67 57 V72 M67 57 Q67 48 72.5 48 Q78 48 78 57 V72\" stroke=\"#e2e8f0\" stroke-width=\"3.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/><path d=\"M86 48 V72 M86 60 L96 48 M86 60 L96 72\" stroke=\"#e2e8f0\" stroke-width=\"3.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/></svg>";
|
|
29
|
+
export {};
|