fix(web): expose organize recovery and file episode evidence
This commit is contained in:
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>
|
||||
|
||||
Reference in new issue
Block a user