"use client"; import { useCallback, useRef, useState } from "react"; import { api } from "@/lib/api/client"; import { useToast } from "@/components/Toast"; import { errorCode } from "@/lib/generation/cards"; import { buildRefineRequest } from "./style"; export type RefineStatus = "idle" | "refining" | "done" | "error"; export interface RefineResult { original: string; refined: string; } export interface UseRefine { status: RefineStatus; result: RefineResult | null; // 回炉重写某段(可选改写指令)→ 返回 {original, refined} 或 null(失败)。 refine: ( projectId: string, chapterNo: number, segment: string, instruction?: string, ) => Promise; // 取消在途回炉(段切换/卸载时调用)——不触碰 state,安全用于 effect 清理。 abort: () => void; reset: () => void; } // 回炉(POST .../refine):同步返回新旧 diff,不写库(不变量#3)。 // 503 LLM_UNAVAILABLE(无凭据)优雅提示去设置;其余失败 toast。 export function useRefine(): UseRefine { const [status, setStatus] = useState("idle"); const [result, setResult] = useState(null); const controllerRef = useRef(null); const toast = useToast(); const refine = useCallback( async (projectId, chapterNo, segment, instruction) => { // 取消上一次在途请求,换新 controller(段切换竞态防护,CR-H12)。 controllerRef.current?.abort(); const controller = new AbortController(); controllerRef.current = controller; setStatus("refining"); setResult(null); try { const { data, error } = await api.POST( "/projects/{project_id}/chapters/{chapter_no}/refine", { params: { path: { project_id: projectId, chapter_no: chapterNo }, }, body: buildRefineRequest(segment, instruction), signal: controller.signal, }, ); // 已被后续请求/卸载中止:丢弃迟到响应,不写状态。 if (controller.signal.aborted) return null; if (error || !data) { setStatus("error"); const code = errorCode(error); toast( code === "LLM_UNAVAILABLE" ? "未配置提供商,请先去设置页连一家。" : "回炉失败,请稍后重试。", "error", ); return null; } const outcome: RefineResult = { original: data.original, refined: data.refined, }; setResult(outcome); setStatus("done"); return outcome; } catch { // 主动中止(abort)触发的异常不该报错 toast。 if (controller.signal.aborted) return null; setStatus("error"); toast("回炉请求异常,请检查网络。", "error"); return null; } }, [toast], ); // 仅取消在途请求,不触碰 state——可安全用于 effect 清理 / 卸载。 const abort = useCallback((): void => { controllerRef.current?.abort(); controllerRef.current = null; }, []); const reset = useCallback((): void => { controllerRef.current?.abort(); controllerRef.current = null; setStatus("idle"); setResult(null); }, []); return { status, result, refine, abort, reset }; }