"use client"; import { useCallback, useEffect, useRef } from "react"; import { Check, Plus, 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 { useContinue } from "@/lib/workbench/useContinue"; import type { AiTurnInput } from "@/lib/workbench/useAiConversation"; interface ContinuePanelProps { projectId: string; chapterNo: number; // 插入选中候选到正文(追加到章末)。 onInsert: (text: string) => void; // 留痕:每条候选记一条 ai 气泡(续写无作者输入,故无 author 气泡)。 appendMessages: (turn: AiTurnInput) => void; onClose: () => void; } // 续写结果卡(内联,非模态):打开即读该章前文续写一条候选;可「再来一个」累加多候选, // 作者点某条「插入正文」才落回草稿(HITL,不静默写入)。 export function ContinuePanel({ projectId, chapterNo, onInsert, appendMessages, onClose, }: ContinuePanelProps) { const { status, candidates, generate } = useContinue(); const ranRef = useRef(false); // 本面板一次会话 = 一个 thread;多条候选归此组,candidate_index 本地累加。 const threadId = useRef(crypto.randomUUID()).current; const candidateIdxRef = useRef(0); // 生成一条候选并(成功时)留痕:ai-only,meta 存候选序号。 const runGenerate = useCallback(async (): Promise => { const text = await generate(projectId, chapterNo); if (!text) return; const candidateIndex = candidateIdxRef.current; candidateIdxRef.current += 1; appendMessages({ threadId, kind: "continue", toolKey: "continue", chapterNo, messages: [ { role: "ai", content: text, meta: { candidate_index: candidateIndex } }, ], }); }, [generate, projectId, chapterNo, appendMessages, threadId]); useEffect(() => { if (ranRef.current) return; ranRef.current = true; void runGenerate(); }, [runGenerate]); const busy = status === "generating"; return (
{candidates.length === 0 && busy ? (
) : null} {candidates.length === 0 && status === "error" ? ( ) : null}
); }