feat(reports): generate personal reports by section
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

This commit is contained in:
Jesse_Chen
2026-08-30 22:59:17 +08:00
parent 1d1c1d3d24
commit 0ae3e2d796
17 changed files with 1173 additions and 111 deletions
@@ -33,7 +33,7 @@ export type ReportLoadState =
| { phase: "loading" }
| { phase: "unauthorized" }
| { phase: "not-found" }
| { phase: "generating" }
| { phase: "generating"; progressPercent?: number; progressPhase?: string }
| { phase: "timed-out" }
| { phase: "failed"; failureCode: string | null }
| { phase: "invalid"; message: string }
@@ -50,6 +50,8 @@ export interface ReportEnvelopeView {
failureCode: string | null;
createdAt: string;
completedAt: string | null;
progressPercent?: number;
progressPhase?: string;
}
/**
@@ -89,8 +91,15 @@ export function classifyReportEnvelope(statusCode: number, json: unknown): Repor
}
return { phase: "ready", document: parsed.document };
}
case "generating":
return { phase: "generating" };
case "generating": {
const progressPercent = typeof view.progressPercent === "number" ? view.progressPercent : undefined;
const progressPhase = typeof view.progressPhase === "string" ? view.progressPhase : undefined;
return {
phase: "generating",
...(progressPercent === undefined ? {} : { progressPercent }),
...(progressPhase === undefined ? {} : { progressPhase }),
};
}
case "failed": {
const code = typeof view.failureCode === "string" && view.failureCode.length > 0
? view.failureCode
@@ -192,6 +201,11 @@ export function PersonalReportPage({ reportId }: { reportId: string }) {
}, [load]);
const generating = state.phase === "generating";
const progressLabel = generating && state.progressPhase?.startsWith("section:")
? `正在生成主题(进度 ${Math.max(0, Math.min(100, Math.round(state.progressPercent ?? 30)))}%`
: generating && state.progressPhase === "summary" ? "正在汇总全部主题"
: generating && state.progressPhase === "assemble" ? "正在装配报告"
: null;
useVisibilityAwarePoll({
enabled: generating,
@@ -210,7 +224,7 @@ export function PersonalReportPage({ reportId }: { reportId: string }) {
<main className="flex min-h-svh flex-col items-center justify-center gap-4 px-4 text-center">
<InlineSpinner className="text-primary" size={32} />
<p className="text-ink" role="status">
{generating ? "报告正在生成中,请稍候…" : "正在加载报告…"}
{generating ? (progressLabel ?? "报告正在生成中,请稍候…") : "正在加载报告…"}
</p>
{generating && (
<>