107 lines
4.2 KiB
TypeScript
107 lines
4.2 KiB
TypeScript
/** 资料对照页:来源版本原文、清理候选对照与榜单快照(含失败态)。 */
|
||
import { useState } from "react";
|
||
import { 客户端 } from "../../接口/生成/客户端";
|
||
import { useMuse来源列表, useMuse版本原文 } from "./数据";
|
||
import { Ui反馈状态 } from "../../界面/反馈状态";
|
||
import { useMuse作者会话 } from "../../应用/作者会话";
|
||
|
||
export function Ui资料页() {
|
||
const 会话 = useMuse作者会话();
|
||
const scope = 会话.data ? "session" : null;
|
||
const 来源 = useMuse来源列表(scope);
|
||
const [当前, set当前] = useState<{ id: string; revision: number } | null>(null);
|
||
const 版本 = useMuse版本原文(scope, 当前?.id ?? null, 当前?.revision ?? null);
|
||
const [消息, set消息] = useState("");
|
||
|
||
async function 生成并应用(sourceId: string, revision: number) {
|
||
try {
|
||
const { data, error } = await 客户端.POST(
|
||
"/api/v1/sources/{source_id}/versions/{revision}/clean-candidates",
|
||
{ params: { path: { source_id: sourceId, revision } } },
|
||
);
|
||
if (!data) throw new Error((error as { message?: string })?.message ?? "生成失败");
|
||
const apply = await 客户端.POST(
|
||
"/api/v1/clean-candidates/{candidate_id}/apply",
|
||
{ params: { path: { candidate_id: (data as { candidate_id: string }).candidate_id } } },
|
||
);
|
||
set消息(
|
||
apply.data
|
||
? "清理已应用为对照版本;原文保持不变。"
|
||
: "清理候选已生成,但应用失败,可重试。",
|
||
);
|
||
set当前({ id: sourceId, revision });
|
||
} catch (e) {
|
||
set消息(e instanceof Error ? e.message : "清理流程失败,可重试。");
|
||
}
|
||
}
|
||
|
||
return (
|
||
<main className="content">
|
||
<p className="eyebrow">工作台</p>
|
||
<h1>资料研究</h1>
|
||
<p className="muted">
|
||
来源与原文对照;清理只生成对照结果,原文版本不可改写。榜单与拆书在
|
||
<a className="text-button" href="/research">研究工作区</a>与<a className="text-button" href="/rankings">榜单与选题</a>。
|
||
</p>
|
||
<section className="paper-panel" aria-label="来源清单">
|
||
<div className="panel-heading">
|
||
<h2>来源清单</h2>
|
||
<span className="muted">{来源.data?.length ?? 0} 个来源</span>
|
||
</div>
|
||
{来源.isPending ? (
|
||
<Ui反馈状态 message="正在读取来源…" />
|
||
) : (来源.data?.length ?? 0) === 0 ? (
|
||
<p className="muted">还没有导入来源;经 CLI 或主会话导入后这里可见。</p>
|
||
) : (
|
||
<ul className="event-list">
|
||
{来源.data!.map((源) => (
|
||
<li key={源.source_id}>
|
||
<button
|
||
className="text-button"
|
||
onClick={() => set当前({ id: 源.source_id, revision: 源.最新版本 })}
|
||
>
|
||
{源.title}({源.kind})r{源.最新版本}
|
||
</button>
|
||
<span className="mono muted">{源.origin}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
{来源.error && <Ui反馈状态 message={来源.error.message} error />}
|
||
</section>
|
||
|
||
{当前 && (
|
||
<section className="paper-panel" aria-label="原文与清理对照">
|
||
<div className="panel-heading">
|
||
<h2>原文与清理对照</h2>
|
||
<span className="muted">
|
||
r{当前.revision}
|
||
</span>
|
||
</div>
|
||
{版本.isPending ? (
|
||
<Ui反馈状态 message="正在读取原文…" />
|
||
) : 版本.error ? (
|
||
<Ui反馈状态 message="该版本读取失败。" error />
|
||
) : (
|
||
<>
|
||
<pre className="mono draft-stream" aria-label="版本原文">
|
||
{版本.data?.content}
|
||
</pre>
|
||
<button
|
||
className="primary"
|
||
onClick={() => void 生成并应用(当前.id, 当前.revision)}
|
||
>
|
||
生成并应用清理对照
|
||
</button>
|
||
{消息 && (
|
||
<p role="status">{消息}</p>
|
||
)}
|
||
</>
|
||
)}
|
||
</section>
|
||
)}
|
||
|
||
</main>
|
||
);
|
||
}
|