"use client"; import { useEffect, useRef, useState } from "react"; import { Check, MessageSquarePlus, MessagesSquare, RotateCcw, 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 { useRefine } from "@/lib/workbench/useRefine"; import { useClarify } from "@/lib/workbench/useClarify"; import { foldClarifications, needsClarifyGate } from "@/lib/workbench/clarify"; import { ChoiceChips } from "./ChoiceChips"; // 门控阈值:再沟通意见 trim 后短于此字数才自动预检反问(清晰意见不交往返税)。 const CLARIFY_MIN_CHARS = 10; interface RefinePanelProps { projectId: string; chapterNo: number; // 选中原文;打开即对其回炉一次。 original: string; // 接受回填:把选段替换为该产出(调用方按内容重锚落回草稿)。 onAccept: (refined: string) => void; onClose: () => void; } // 润色 / 再沟通结果卡(内联,非模态):打开即润色选段;作者可「再沟通」追加意见迭代出新版, // 择版接受回填正文,或放弃。原文只读、生成不写库(不变量 #3),accept 才动草稿。 export function RefinePanel({ projectId, chapterNo, original, onAccept, onClose, }: RefinePanelProps) { const { status, versions, latest, refine, recommunicate } = useRefine(); const clarify = useClarify(); const [instruction, setInstruction] = useState(""); const [pendingInstruction, setPendingInstruction] = useState(null); const ranRef = useRef(false); // 打开即对选段回炉一次(仅一次,避免重复请求)。 useEffect(() => { if (ranRef.current) return; ranRef.current = true; void refine(projectId, chapterNo, original); }, [projectId, chapterNo, original, refine]); const busy = status === "refining"; const clarifying = clarify.status === "checking"; const firstQuestion = clarify.decision?.questions[0] ?? null; const asking = clarify.status === "asking" && firstQuestion !== null; // 折入澄清答案(或原意见)后回炉,并清理澄清态。 const runRecommunicate = (finalInstruction: string): void => { clarify.reset(); setPendingInstruction(null); setInstruction(""); void recommunicate(projectId, chapterNo, finalInstruction); }; // 「再改一版」:意见含糊/极短(或 force)先预检反问;否则直接回炉。 const onRecommunicate = async (force: boolean): Promise => { const trimmed = instruction.trim(); if (!trimmed || busy || clarifying) return; if (force || needsClarifyGate(trimmed, CLARIFY_MIN_CHARS)) { const segment = latest?.refined ?? original; const vm = await clarify.check(projectId, chapterNo, segment, trimmed); if (vm.needClarification && vm.questions.length > 0) { setPendingInstruction(trimmed); // 记住原意见,等作者答完折进去 return; } } runRecommunicate(trimmed); }; // 作者答复澄清(点选 value 或自由输入)→ 折进意见 → 回炉。 const onClarifyAnswer = (answer: string): void => { const question = clarify.decision?.questions[0]?.question ?? ""; const base = pendingInstruction ?? instruction.trim(); runRecommunicate(foldClarifications(base, [{ question, answer }])); }; return (
原文

{original}

改写 {versions.length > 0 ? ( 第 {versions.length} 版 ) : null}
{busy && !latest ? ( ) : latest ? (

{latest.refined}

) : status === "error" ? ( ) : null}