From 542c2abac928da85fb882594071f6cf5db5aa66c Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Wed, 8 Jul 2026 07:12:04 +0200 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E6=95=B4=E7=AB=A0=E3=80=8C?= =?UTF-8?q?=E5=86=8D=E6=B2=9F=E9=80=9A/=E9=87=8D=E5=86=99=E3=80=8D?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E2=80=94=E2=80=94=E7=BB=99=E6=84=8F=E8=A7=81?= =?UTF-8?q?=E6=B5=81=E5=BC=8F=E9=87=8D=E5=86=99=E6=95=B4=E7=AB=A0=EF=BC=8C?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=A0=88=E8=BF=AD=E4=BB=A3=EF=BC=8C=E6=8E=A5?= =?UTF-8?q?=E5=8F=97=E6=9B=BF=E6=8D=A2=E6=AD=A3=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WFW-8 整章级再沟通(前端)。底栏新增「整章重写」→ ChapterRewritePanel: - 作者给意见 → useChapterRewrite 消费 POST .../rewrite 的 SSE 流(复用 lib/stream/sse 帧解析),流式 live 展示新一版; - 每轮完成压入版本栈,「再改一版」以最新版为 prior_draft + 新意见迭代(多轮对话式); - 接受某版才「替换整章正文」落草稿(HITL,AI 绝不静默覆盖);停/错误/网络异常均归一处理。 四张 AI 结果卡(润色/续写/工具箱/整章重写)互斥。gen:api 重生成 TS 客户端。 门禁绿:tsc/lint/vitest 636(+7 useChapterRewrite)/build/coverage 95.36%。 --- .../workbench/ChapterRewritePanel.tsx | 130 ++++++++++++++ apps/web/components/workbench/Workbench.tsx | 40 ++++- apps/web/lib/api/schema.d.ts | 73 ++++++++ .../lib/workbench/useChapterRewrite.test.ts | 162 +++++++++++++++++ apps/web/lib/workbench/useChapterRewrite.ts | 163 ++++++++++++++++++ 5 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 apps/web/components/workbench/ChapterRewritePanel.tsx create mode 100644 apps/web/lib/workbench/useChapterRewrite.test.ts create mode 100644 apps/web/lib/workbench/useChapterRewrite.ts diff --git a/apps/web/components/workbench/ChapterRewritePanel.tsx b/apps/web/components/workbench/ChapterRewritePanel.tsx new file mode 100644 index 0000000..edc889b --- /dev/null +++ b/apps/web/components/workbench/ChapterRewritePanel.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { useState } from "react"; +import { Check, RotateCcw, Square, X } from "lucide-react"; + +import { ThinkingIndicator } from "@/components/ThinkingIndicator"; +import { Button } from "@/components/ui/Button"; +import { SectionHeader } from "@/components/ui/SectionHeader"; +import { StatusNote } from "@/components/ui/StatusNote"; +import { TextArea } from "@/components/ui/TextArea"; +import { friendlyError } from "@/lib/errors/messages"; +import { useChapterRewrite } from "@/lib/workbench/useChapterRewrite"; + +interface ChapterRewritePanelProps { + projectId: string; + chapterNo: number; + // 当前整章正文(首轮重写的 prior_draft)。 + currentDraft: string; + // 接受某一版 → 替换整章正文(HITL,作者显式点选才落草稿)。 + onAccept: (text: string) => void; + onClose: () => void; +} + +// 整章「再沟通 / 重写」结果卡(内联,非模态):作者给意见 → AI 就着当前整章 + 完整记忆注入 +// 流式重写一版 → 版本栈可多轮迭代 → 接受某版才替换整章正文。生成不写库(HITL,不变量 #3)。 +export function ChapterRewritePanel({ + projectId, + chapterNo, + currentDraft, + onAccept, + onClose, +}: ChapterRewritePanelProps) { + const rewrite = useChapterRewrite(); + const [feedback, setFeedback] = useState(""); + + const hasVersions = rewrite.versions.length > 0; + const canSend = feedback.trim().length > 0 && !rewrite.isStreaming; + // 下一轮以最新版正文为基础(无版本则用当前整章正文)迭代。 + const priorDraft = rewrite.latest?.text ?? currentDraft; + const err = rewrite.state.phase === "error" ? rewrite.state.error : null; + + const onSend = (): void => { + if (!canSend) return; + const fb = feedback.trim(); + setFeedback(""); + void rewrite.send(projectId, chapterNo, fb, priorDraft); + }; + + return ( +
+
+ + +
+ +
+