fix(web): expose organize recovery and file episode evidence

This commit is contained in:
root committed 2026-10-13 20:26:43 +08:00
1 parent b7883eaf4f
commit 2b3ccb89e4
12 files changed
+390 -72

No files matched your search

@@ -49,6 +49,8 @@ export interface EpisodeDecisionView {
releaseGroup: string | null;
releases: { id: string; title: string; source: string }[];
count: number;
releaseCount: number;
files: { recordId: string; path: string; pending: boolean; source: string; canonical: string }[];
suggestion: Suggestion | null;
/** 采用建议规则时写进作品识别词的那一行 */
suggestionLine: string | null;
@@ -73,6 +75,17 @@ export function episodeDecisionView(d: Decision): EpisodeDecisionView {
source: r.source ? sourceText({ source: r.source }) : "—",
}))
: [];
const files = Array.isArray(e.fileSamples)
? (e.fileSamples as { recordId: string; path: string; resolution: EpisodeResolutionView }[])
.filter((f) => typeof f.recordId === "string" && typeof f.path === "string" && f.resolution?.source)
.map((f) => ({
recordId: f.recordId,
path: f.path,
pending: f.resolution.method === "unresolved",
source: sourceText(f.resolution),
canonical: canonicalText(f.resolution),
}))
: [];
const s = e.suggestion as Suggestion | null | undefined;
const a = e.agentSuggestion as AgentSuggestion | null | undefined;
return {
@@ -80,6 +93,14 @@ export function episodeDecisionView(d: Decision): EpisodeDecisionView {
season: typeof e.season === "number" ? e.season : null,
releaseGroup: typeof e.releaseGroup === "string" ? e.releaseGroup : null,
releases,
files,
releaseCount: Array.isArray(e.sourceReleaseIds)
? e.sourceReleaseIds.length
: files.length
? releases.length
: typeof e.count === "number"
? e.count
: releases.length,
count: typeof e.count === "number" ? e.count : releases.length,
suggestion: s && typeof s.expression === "string" ? s : null,
agentSuggestion:
@@ -142,27 +163,70 @@ export function AgentSuggestionBody({ s }: { s: AgentSuggestion }) {
);
}
function EpisodeFileSamples({ files }: { files: EpisodeDecisionView["files"] }) {
return (
<ul className="space-y-1">
{files.slice(-5).map((f) => (
<li key={`${f.recordId}:${f.path}`} className="break-all">
<span className="font-mono tabular-nums">
{f.source}
{f.pending ? " · 待确认" : ` → ${f.canonical}`}
</span>
<div className="font-mono text-meta text-muted-foreground">{f.path}</div>
</li>
))}
{files.length > 5 ? <li className="text-meta text-muted-foreground">另有 {files.length - 5} 个文件</li> : null}
</ul>
);
}
/** 展示部分(纯渲染,单测直接渲染) */
export function EpisodeDecisionBody({ view, simulation }: { view: EpisodeDecisionView; simulation: string | null }) {
const shown = view.releases.slice(-5);
const pending = view.files.filter((f) => f.pending);
const guards = view.files.filter((f) => !f.pending);
return (
<dl className="grid gap-1.5 text-caption sm:grid-cols-[5rem_1fr]">
<dt className="text-muted-foreground">范围</dt>
<dd className="text-foreground">{scopeText(view.season, view.releaseGroup)}</dd>
<dt className="text-muted-foreground">来源季集</dt>
<dd className="min-w-0 text-foreground">
<ul className="space-y-0.5">
{shown.map((r) => (
<li key={r.id} className="break-all">
<span className="font-mono tabular-nums">{r.source}</span>
<span className="ml-2 text-meta text-muted-foreground">{r.title}</span>
</li>
))}
{view.count > shown.length ? (
<li className="text-meta text-muted-foreground">另有 {view.count - shown.length} 条</li>
) : null}
</ul>
</dd>
{view.releaseCount ? (
<>
<dt className="text-muted-foreground">来源季集</dt>
<dd className="min-w-0 text-foreground">
<ul className="space-y-0.5">
{shown.map((r) => (
<li key={r.id} className="break-all">
<span className="font-mono tabular-nums">{r.source}</span>
<span className="ml-2 text-meta text-muted-foreground">{r.title}</span>
</li>
))}
{view.releaseCount > shown.length ? (
<li className="text-meta text-muted-foreground">另有 {view.releaseCount - shown.length} 条</li>
) : null}
</ul>
</dd>
</>
) : null}
{pending.length ? (
<>
<dt className="text-muted-foreground">待确认文件</dt>
<dd className="min-w-0 text-foreground">
<EpisodeFileSamples files={pending} />
</dd>
</>
) : null}
{guards.length ? (
<>
<dt className="text-muted-foreground">校验文件</dt>
<dd className="min-w-0">
<details className="text-caption text-muted-foreground">
<summary className="cursor-pointer">已对上的 {guards.length} 个文件</summary>
<p className="my-1 text-meta">用于核对新规则,避免影响这些文件的季集。</p>
<EpisodeFileSamples files={guards} />
</details>
</dd>
</>
) : null}
{view.agentSuggestion ? (
<>
<dt className="text-muted-foreground">智能体建议</dt>
@@ -190,7 +254,7 @@ export function EpisodeDecisionBody({ view, simulation }: { view: EpisodeDecisio
{simulation ? <div className="mt-0.5 text-meta text-muted-foreground">{simulation}</div> : null}
</>
) : (
<span className="text-muted-foreground">样本不够推出唯一的规则,在上面的作品识别词里手动写</span>
<span className="text-muted-foreground">样本不够推出唯一的规则,展开作品识别词后手动写</span>
)}
</dd>
</dl>