import { notFound } from "next/navigation"; import { ReviewReport } from "@/components/review/ReviewReport"; import { fetchChapters, fetchDraft, fetchOutline, fetchProject, fetchReviews, } from "@/lib/api/server"; import type { ProjectResponse } from "@/lib/api/types"; import { buildReviewChapterOptions, defaultReviewChapter, } from "@/lib/review/chapterNav"; import { latestReview } from "@/lib/review/history"; import type { ChapterEntry } from "@/lib/workbench/chapter"; interface PageProps { params: Promise<{ id: string }>; searchParams: Promise<{ chapter?: string }>; } // 审稿报告页(UX §6.4)。Server Component 取项目 + 审稿留痕历史(新→旧); // ReviewReport(Client)承载 SSE 重审 / 裁决 / 验收交互。 export default async function ReviewPage({ params, searchParams }: PageProps) { const { id } = await params; const { chapter } = await searchParams; // 仅当项目确实取不到时才判 404;审稿/草稿/大纲等次级拉取的瞬时错误不应把整页变成「找不到」。 let project: ProjectResponse; try { project = await fetchProject(id); } catch { notFound(); } // 真实存在的章(含可审/已审标记,错误→[]):供选章枚举 + 决定默认章。 const chapterList = await fetchChapters(id); // 无 ?chapter 时默认落到最近一个有草稿的章,而非硬编码第 1 章。 const chapterNo = parsePositiveInt(chapter) ?? defaultReviewChapter(chapterList); // 审稿留痕(GET .../reviews,失败→空历史)。 let initialReview; try { const history = await fetchReviews(id, chapterNo); initialReview = latestReview(history.reviews); } catch { initialReview = undefined; } // 终稿初值取已存草稿(GET .../draft,404→null→"");否则 final_text 为空 → accept 422。 let initialDraft = ""; try { const draft = await fetchDraft(id, chapterNo); initialDraft = draft?.content ?? ""; } catch { initialDraft = ""; } // 大纲章节作章节导航目录(fetchOutline 已对空/错误降级为 []);首个节拍当短标题。 const chapters: ChapterEntry[] = (await fetchOutline(id)).map((c) => ({ no: c.no, title: c.beats?.[0], })); // 选章下拉选项:以真实章为准(覆盖「写过但不在大纲」的章),大纲标题补短名,标注可审/已审。 const chapterOptions = buildReviewChapterOptions( chapterList, chapters, chapterNo, ); return ( // key=章号:客户端切章(?chapter=N 变化)时强制重挂载,用新章审稿/草稿刷新状态, // 避免 ReviewReport 内 useState 基线沿用上一章。 ); } function parsePositiveInt(raw: string | undefined): number | null { if (!raw) return null; const n = Number.parseInt(raw, 10); return Number.isInteger(n) && n > 0 ? n : null; }