This commit is contained in:
hupenglong1
2026-05-20 15:46:36 +08:00
parent 1a94cec822
commit 20690cdef3
5 changed files with 8134 additions and 7358 deletions
+1
View File
@@ -1,5 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:is(.dark *));
@@ -2,7 +2,8 @@
import { ExternalLinkIcon, FileIcon, RefreshCwIcon } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
import { cn } from "@/lib/utils";
@@ -106,7 +107,7 @@ export function StepDetailSheet({
) : detail.format === "empty" ? (
<EmptyView detail={detail} />
) : detail.format === "markdown" ? (
<MarkdownText text={detail.content} />
<MarkdownView text={detail.content} />
) : detail.format === "json" ? (
<JsonView text={detail.content} />
) : detail.format === "jsonl" ? (
@@ -166,54 +167,774 @@ function EmptyView({ detail }: { detail: StepDetail }) {
);
}
type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };
function JsonView({ text }: { text: string }) {
const pretty = useMemo(() => {
const parsed = useMemo<{ ok: true; value: JsonValue } | { ok: false }>(() => {
try {
return JSON.stringify(JSON.parse(text), null, 2);
return { ok: true, value: JSON.parse(text) as JsonValue };
} catch {
return text;
return { ok: false };
}
}, [text]);
if (!parsed.ok) {
return (
<pre className="overflow-x-auto rounded-md border bg-muted/30 p-3 font-mono text-[11px] leading-relaxed">
{pretty}
{text}
</pre>
);
}
const value = parsed.value;
if (Array.isArray(value)) {
return (
<div className="space-y-3">
<div className="text-[10px] text-muted-foreground">
{value.length}
</div>
{value.map((item, idx) => (
<details
key={idx}
open={idx === 0}
className="rounded-lg border bg-card shadow-sm"
>
<summary className="cursor-pointer border-b px-4 py-2 font-mono text-[11px] text-muted-foreground hover:bg-muted/30">
#{idx}
</summary>
<div className="px-4 py-3">
<JsonValueView value={item} depth={0} />
</div>
</details>
))}
</div>
);
}
if (value !== null && typeof value === "object") {
return (
<div className="rounded-lg border bg-card shadow-sm">
<div className="space-y-3 px-4 py-3">
<JsonObjectView value={value as Record<string, JsonValue>} depth={0} />
</div>
</div>
);
}
return (
<div className="rounded-md border bg-muted/20 px-3 py-2 font-mono text-[12px]">
<JsonPrimitive value={value} />
</div>
);
}
function JsonObjectView({
value,
depth,
}: {
value: Record<string, JsonValue>;
depth: number;
}) {
const keys = Object.keys(value);
if (keys.length === 0) {
return (
<span className="font-mono text-[11px] text-muted-foreground">
{`{}`}
</span>
);
}
return (
<>
{keys.map((k) => (
<Field key={k} label={k}>
<JsonValueView value={value[k]} depth={depth + 1} />
</Field>
))}
</>
);
}
function JsonValueView({
value,
depth,
}: {
value: JsonValue;
depth: number;
}) {
if (value === null || typeof value !== "object") {
return <JsonPrimitive value={value} />;
}
if (Array.isArray(value)) {
if (value.length === 0) {
return (
<span className="font-mono text-[11px] text-muted-foreground">
[]
</span>
);
}
const allPrimitive = value.every(
(v) => v === null || typeof v !== "object",
);
if (allPrimitive) {
return (
<div className="flex flex-wrap gap-1.5">
{value.map((v, i) => (
<span
key={i}
className="inline-flex rounded border bg-muted/30 px-1.5 py-0.5 font-mono text-[11px]"
>
<JsonPrimitive value={v} />
</span>
))}
</div>
);
}
const allObjects = value.every(
(v) => v !== null && typeof v === "object" && !Array.isArray(v),
);
if (allObjects) {
const cols = Array.from(
new Set(
value.flatMap((v) =>
Object.keys(v as Record<string, JsonValue>),
),
),
);
const flat = cols.length <= 8 && depth <= 1;
if (flat) {
return (
<div className="overflow-x-auto rounded-md border">
<table className="w-full border-collapse text-[12px]">
<thead className="bg-muted/50">
<tr>
{cols.map((c) => (
<th
key={c}
className="border-b px-2 py-1 text-left font-medium"
>
{c}
</th>
))}
</tr>
</thead>
<tbody>
{value.map((row, i) => (
<tr
key={i}
className="border-b last:border-b-0 hover:bg-muted/20"
>
{cols.map((c) => {
const cell = (row as Record<string, JsonValue>)[c];
return (
<td
key={c}
className="px-2 py-1 align-top font-mono text-[11px]"
>
{cell === undefined ? (
<span className="text-muted-foreground"></span>
) : (
<JsonValueView value={cell} depth={depth + 2} />
)}
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
);
}
}
return (
<div className="space-y-1.5">
{value.map((v, i) => (
<details key={i} className="rounded-md border bg-muted/20">
<summary className="cursor-pointer px-3 py-1 font-mono text-[10px] text-muted-foreground hover:bg-muted/40">
#{i}
</summary>
<div className="space-y-2 px-3 pt-1 pb-2">
<JsonValueView value={v} depth={depth + 1} />
</div>
</details>
))}
</div>
);
}
const obj = value as Record<string, JsonValue>;
if (depth >= 1) {
return (
<div className="space-y-2 border-l-2 border-muted pl-3">
<JsonObjectView value={obj} depth={depth} />
</div>
);
}
return (
<div className="space-y-2">
<JsonObjectView value={obj} depth={depth} />
</div>
);
}
function JsonPrimitive({ value }: { value: JsonValue }) {
if (value === null) {
return (
<span className="font-mono text-[11px] text-muted-foreground italic">
null
</span>
);
}
if (typeof value === "boolean") {
return (
<span
className={cn(
"font-mono text-[11px]",
value
? "text-emerald-600 dark:text-emerald-400"
: "text-rose-600 dark:text-rose-400",
)}
>
{String(value)}
</span>
);
}
if (typeof value === "number") {
return (
<span className="font-mono text-[12px] text-foreground">{value}</span>
);
}
if (typeof value !== "string") return null;
const str = value;
if (str.includes("\n")) {
return (
<pre className="overflow-x-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed">
{str}
</pre>
);
}
return <span className="break-words text-foreground">{str}</span>;
}
type IterationEntry = {
iteration?: number;
runDic?: number;
timestamp?: string;
hypothesis?: string;
intervention?: { type?: string; summary?: string } | string;
prediction?: Record<string, number | string>;
results?: Record<string, number | string>;
verdict?: string;
root_cause_findings?: Array<{
pattern?: string;
class?: string;
cases?: number;
}>;
error_delta?: { persistent?: number; new?: number; fixed?: number };
next_hypothesis?: string;
[key: string]: unknown;
};
const KNOWN_KEYS = new Set([
"iteration",
"runDic",
"timestamp",
"hypothesis",
"intervention",
"prediction",
"results",
"verdict",
"root_cause_findings",
"error_delta",
"next_hypothesis",
]);
function JsonLinesView({ text }: { text: string }) {
const lines = useMemo(
() =>
text
const entries = useMemo(() => {
return text
.split("\n")
.map((l) => l.trim())
.filter(Boolean)
.map((l) => {
try {
return JSON.stringify(JSON.parse(l), null, 2);
return { ok: true as const, data: JSON.parse(l) as IterationEntry };
} catch {
return l;
return { ok: false as const, raw: l };
}
}),
[text],
);
});
}, [text]);
if (entries.length === 0) {
return (
<div className="space-y-2">
<div className="text-[10px] text-muted-foreground">{lines.length} </div>
{lines.map((l, idx) => (
<details
<p className="text-muted-foreground text-xs"></p>
);
}
return (
<div className="space-y-3">
<div className="text-[10px] text-muted-foreground">
{entries.length}
</div>
{entries.map((entry, idx) =>
entry.ok ? (
<IterationCard
key={idx}
open={idx === 0}
className="rounded-md border bg-muted/20"
entry={entry.data}
defaultOpen={idx === entries.length - 1}
/>
) : (
<div
key={idx}
className="rounded-md border border-destructive/40 bg-destructive/5 p-3 font-mono text-[11px] text-destructive"
>
<summary className="cursor-pointer px-3 py-1.5 font-mono text-[11px] text-muted-foreground hover:bg-muted/40">
#{idx + 1}
#{idx + 1} JSON
<pre className="mt-1 whitespace-pre-wrap break-all text-muted-foreground">
{entry.raw}
</pre>
</div>
),
)}
</div>
);
}
function IterationCard({
entry,
defaultOpen,
}: {
entry: IterationEntry;
defaultOpen: boolean;
}) {
const intervention =
typeof entry.intervention === "string"
? { summary: entry.intervention }
: (entry.intervention ?? {});
const otherKeys = Object.keys(entry).filter((k) => !KNOWN_KEYS.has(k));
return (
<details
open={defaultOpen}
className="group rounded-lg border bg-card shadow-sm"
>
<summary className="flex cursor-pointer items-center gap-2 border-b px-4 py-2.5 hover:bg-muted/30">
<span className="rounded bg-primary/10 px-2 py-0.5 font-mono text-[11px] font-semibold text-primary">
R{entry.iteration ?? "?"}
</span>
{entry.runDic != null ? (
<span className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
runDic={entry.runDic}
</span>
) : null}
{entry.verdict ? <VerdictPill verdict={entry.verdict} /> : null}
<span className="ml-auto truncate font-mono text-[10px] text-muted-foreground">
{entry.timestamp ?? ""}
</span>
</summary>
<div className="space-y-3 px-4 py-3 text-[13px]">
{entry.hypothesis ? (
<Field label="假设" tone="primary">
{entry.hypothesis}
</Field>
) : null}
{intervention.summary || intervention.type ? (
<Field label="干预">
<div className="flex flex-wrap items-center gap-2">
{intervention.type ? (
<span className="rounded bg-amber-500/15 px-2 py-0.5 font-mono text-[11px] text-amber-700 dark:text-amber-300">
{intervention.type}
</span>
) : null}
{intervention.summary ? (
<span className="text-foreground">{intervention.summary}</span>
) : null}
</div>
</Field>
) : null}
{entry.prediction || entry.results ? (
<MetricsCompare
prediction={entry.prediction}
results={entry.results}
/>
) : null}
{entry.error_delta ? (
<Field label="错误增量">
<div className="flex flex-wrap gap-2">
<DeltaPill
label="persistent"
value={entry.error_delta.persistent}
tone="muted"
/>
<DeltaPill
label="new"
value={entry.error_delta.new}
tone="rose"
/>
<DeltaPill
label="fixed"
value={entry.error_delta.fixed}
tone="emerald"
/>
</div>
</Field>
) : null}
{entry.root_cause_findings && entry.root_cause_findings.length > 0 ? (
<Field label="根因发现">
<div className="overflow-x-auto rounded-md border">
<table className="w-full border-collapse text-[12px]">
<thead className="bg-muted/50">
<tr>
<th className="border-b px-2 py-1 text-left font-medium">
pattern
</th>
<th className="border-b px-2 py-1 text-left font-medium">
class
</th>
<th className="border-b px-2 py-1 text-right font-medium">
cases
</th>
</tr>
</thead>
<tbody>
{entry.root_cause_findings.map((f, i) => (
<tr
key={i}
className="border-b last:border-b-0 hover:bg-muted/20"
>
<td className="px-2 py-1">{f.pattern ?? "—"}</td>
<td className="px-2 py-1">
{f.class ? (
<span className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px]">
{f.class}
</span>
) : (
"—"
)}
</td>
<td className="px-2 py-1 text-right font-mono">
{f.cases ?? "—"}
</td>
</tr>
))}
</tbody>
</table>
</div>
</Field>
) : null}
{entry.next_hypothesis ? (
<Field label="下一步假设" tone="accent">
{entry.next_hypothesis}
</Field>
) : null}
{otherKeys.length > 0 ? (
<details className="rounded-md border bg-muted/20">
<summary className="cursor-pointer px-3 py-1.5 text-[11px] text-muted-foreground hover:bg-muted/40">
{otherKeys.length}
</summary>
<pre className="overflow-x-auto px-3 pt-0 pb-2 font-mono text-[11px] leading-relaxed">
{l}
{JSON.stringify(
Object.fromEntries(otherKeys.map((k) => [k, entry[k]])),
null,
2,
)}
</pre>
</details>
))}
) : null}
</div>
</details>
);
}
function Field({
label,
children,
tone,
}: {
label: string;
children: React.ReactNode;
tone?: "primary" | "accent";
}) {
return (
<div>
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
{label}
</div>
<div
className={cn(
"rounded-md border bg-muted/20 px-3 py-2 leading-relaxed",
tone === "primary" && "border-primary/30 bg-primary/5",
tone === "accent" && "border-amber-500/30 bg-amber-500/5",
)}
>
{children}
</div>
</div>
);
}
function VerdictPill({ verdict }: { verdict: string }) {
const map: Record<string, string> = {
pass: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
partial: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
miss: "bg-rose-500/15 text-rose-700 dark:text-rose-300",
replication: "bg-sky-500/15 text-sky-700 dark:text-sky-300",
};
return (
<span
className={cn(
"rounded-full px-2 py-0.5 font-medium text-[10px] tracking-wider",
map[verdict.toLowerCase()] ?? "bg-muted text-muted-foreground",
)}
>
{verdict.toUpperCase()}
</span>
);
}
function DeltaPill({
label,
value,
tone,
}: {
label: string;
value: number | undefined;
tone: "rose" | "emerald" | "muted";
}) {
if (value == null) return null;
const toneMap: Record<typeof tone, string> = {
rose: "bg-rose-500/15 text-rose-700 dark:text-rose-300",
emerald: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
muted: "bg-muted text-muted-foreground",
};
return (
<span
className={cn(
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-mono text-[11px]",
toneMap[tone],
)}
>
<span className="opacity-70">{label}</span>
<span className="font-semibold">{value}</span>
</span>
);
}
function MetricsCompare({
prediction,
results,
}: {
prediction?: Record<string, number | string>;
results?: Record<string, number | string>;
}) {
const keys = useMemo(() => {
const set = new Set<string>();
Object.keys(prediction ?? {}).forEach((k) => set.add(k));
Object.keys(results ?? {}).forEach((k) => set.add(k));
return Array.from(set);
}, [prediction, results]);
if (keys.length === 0) return null;
return (
<Field label="指标">
<div className="overflow-x-auto rounded-md border">
<table className="w-full border-collapse text-[12px]">
<thead className="bg-muted/50">
<tr>
<th className="border-b px-2 py-1 text-left font-medium">
metric
</th>
<th className="border-b px-2 py-1 text-right font-medium">
prediction
</th>
<th className="border-b px-2 py-1 text-right font-medium">
result
</th>
<th className="border-b px-2 py-1 text-right font-medium">
Δ
</th>
</tr>
</thead>
<tbody>
{keys.map((k) => {
const p = prediction?.[k];
const r = results?.[k];
const pn = typeof p === "number" ? p : Number(p);
const rn = typeof r === "number" ? r : Number(r);
const delta =
Number.isFinite(pn) && Number.isFinite(rn)
? rn - pn
: null;
const deltaTone =
delta == null
? ""
: delta > 0
? "text-emerald-600 dark:text-emerald-400"
: delta < 0
? "text-rose-600 dark:text-rose-400"
: "text-muted-foreground";
return (
<tr
key={k}
className="border-b last:border-b-0 hover:bg-muted/20"
>
<td className="px-2 py-1 font-mono text-[11px]">{k}</td>
<td className="px-2 py-1 text-right font-mono">
{p ?? "—"}
</td>
<td className="px-2 py-1 text-right font-mono font-semibold">
{r ?? "—"}
</td>
<td
className={cn(
"px-2 py-1 text-right font-mono",
deltaTone,
)}
>
{delta == null
? "—"
: `${delta > 0 ? "+" : ""}${delta.toFixed(2)}`}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</Field>
);
}
const PROSE_CLASSES = cn(
"prose prose-sm max-w-none dark:prose-invert",
"prose-headings:scroll-m-20 prose-headings:font-semibold",
"prose-h1:mt-4 prose-h1:mb-2 prose-h1:text-lg",
"prose-h2:mt-4 prose-h2:mb-2 prose-h2:text-base prose-h2:border-b prose-h2:border-border prose-h2:pb-1",
"prose-h3:mt-3 prose-h3:mb-1.5 prose-h3:text-sm",
"prose-p:my-2 prose-p:leading-relaxed",
"prose-a:text-primary prose-a:no-underline hover:prose-a:underline",
"prose-strong:text-foreground prose-strong:font-semibold",
"prose-blockquote:my-2 prose-blockquote:border-l-4 prose-blockquote:border-muted-foreground/30 prose-blockquote:pl-3 prose-blockquote:text-muted-foreground prose-blockquote:not-italic",
"prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5",
"prose-hr:my-3 prose-hr:border-border",
"prose-table:my-3 prose-table:text-xs",
"prose-thead:bg-muted prose-th:px-2 prose-th:py-1 prose-th:font-semibold prose-th:text-left",
"prose-td:px-2 prose-td:py-1 prose-tr:border-b prose-tr:border-border",
"prose-code:rounded prose-code:bg-muted prose-code:px-1.5 prose-code:py-0.5 prose-code:text-[0.85em] prose-code:before:content-none prose-code:after:content-none",
"prose-pre:my-2 prose-pre:rounded-md prose-pre:border prose-pre:border-border prose-pre:bg-muted/40 prose-pre:p-3 prose-pre:text-[12px] prose-pre:text-foreground",
);
type MarkdownSection = { title: string | null; body: string };
const HIDDEN_MD_SECTION_PATTERNS: RegExp[] = [
/^(?:\d+(?:\.\d+)*\s+)?根因归类/,
/^(?:\d+(?:\.\d+)*\s+)?下一轮假设候选/,
/^(?:\d+(?:\.\d+)*\s+)?error_registry\s+baseline/i,
];
function isHiddenHeading(title: string): boolean {
const trimmed = title.trim();
return HIDDEN_MD_SECTION_PATTERNS.some((re) => re.test(trimmed));
}
function stripHiddenSections(text: string): string {
const lines = text.split("\n");
const out: string[] = [];
let skipUntilLevel: number | null = null;
for (const line of lines) {
const m = line.match(/^(#{1,6})\s+(.+?)\s*$/);
if (m) {
const level = m[1].length;
const title = m[2];
if (skipUntilLevel !== null && level <= skipUntilLevel) {
skipUntilLevel = null;
}
if (skipUntilLevel === null && isHiddenHeading(title)) {
skipUntilLevel = level;
continue;
}
}
if (skipUntilLevel !== null) continue;
out.push(line);
}
return out.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
}
function splitMarkdownSections(text: string): MarkdownSection[] {
const sections: MarkdownSection[] = [];
let current: MarkdownSection = { title: null, body: "" };
const lines = text.split("\n");
for (const line of lines) {
const m = line.match(/^##\s+(.+?)\s*$/);
if (m) {
if (current.title !== null || current.body.trim().length > 0) {
sections.push(current);
}
current = { title: m[1], body: "" };
} else {
current.body += (current.body ? "\n" : "") + line;
}
}
if (current.title !== null || current.body.trim().length > 0) {
sections.push(current);
}
return sections;
}
function MarkdownView({ text }: { text: string }) {
const sections = useMemo(() => {
const stripped = stripHiddenSections(text);
return splitMarkdownSections(stripped).filter(
(s) => s.title === null || !isHiddenHeading(s.title),
);
}, [text]);
if (sections.length === 0) {
return (
<p className="text-muted-foreground text-xs"></p>
);
}
return (
<div className="space-y-3">
{sections.map((section, idx) =>
section.title === null ? (
<div
key={idx}
className="rounded-lg border bg-card px-4 py-3 shadow-sm"
>
<article className={PROSE_CLASSES}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{section.body}
</ReactMarkdown>
</article>
</div>
) : (
<details
key={idx}
open={idx <= 1}
className="group rounded-lg border bg-card shadow-sm"
>
<summary className="flex cursor-pointer items-center gap-2 border-b px-4 py-2.5 hover:bg-muted/30">
<span className="font-semibold text-foreground text-sm">
{section.title}
</span>
<span className="ml-auto text-[10px] text-muted-foreground transition-transform group-open:rotate-180">
</span>
</summary>
<div className="px-4 py-3">
<article className={PROSE_CLASSES}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{section.body}
</ReactMarkdown>
</article>
</div>
</details>
),
)}
</div>
);
}
+49
View File
@@ -26,6 +26,7 @@
"radix-ui": "^1.4.3",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
@@ -35,6 +36,7 @@
"devDependencies": {
"@biomejs/biome": "^2.4.13",
"@tailwindcss/postcss": "^4.2.4",
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
@@ -4676,6 +4678,19 @@
"tailwindcss": "4.2.4"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
@@ -4930,6 +4945,19 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -6422,6 +6450,20 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
@@ -7247,6 +7289,13 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+2
View File
@@ -30,6 +30,7 @@
"radix-ui": "^1.4.3",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
@@ -39,6 +40,7 @@
"devDependencies": {
"@biomejs/biome": "^2.4.13",
"@tailwindcss/postcss": "^4.2.4",
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
@@ -212,6 +212,9 @@ for k in prev:
4. **跨轮 diff**:对每个 B case 查 `error_registry.jsonl`,标记 `persistent` / `new` / `regressed`
5. 按 query 内容归类 pattern:过召、丢失、误判、噪声、意图漂移等。
6. 每个 pattern 列 25 个典型 case**优先列 new/regressed 的**。
- **每条 case 必须包含 `label:` 字段**,无论用哪种排版(多行、按桶分组、紧凑一行)。
- **禁止**只写 `query → pred` 而省略 `label`。读者要靠 `label` 才能判断 pred 对不对。
- 按桶/簇/类型分组列 case 时(如"多约束路线规划 (N 条)"这种小标题),组内每条仍然必须含 `label`
7. 做归因(见 2.4)。
8. 更新 `error_registry.jsonl`:写入本轮所有 B case 的 `(query_hash, runDic, was_wrong=True)`
9. 写入 `results/workflow<runDic>.md`