105 lines
3.4 KiB
TypeScript
105 lines
3.4 KiB
TypeScript
"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<RefineResult | null>;
|
||
// 取消在途回炉(段切换/卸载时调用)——不触碰 state,安全用于 effect 清理。
|
||
abort: () => void;
|
||
reset: () => void;
|
||
}
|
||
|
||
// 回炉(POST .../refine):同步返回新旧 diff,不写库(不变量#3)。
|
||
// 503 LLM_UNAVAILABLE(无凭据)优雅提示去设置;其余失败 toast。
|
||
export function useRefine(): UseRefine {
|
||
const [status, setStatus] = useState<RefineStatus>("idle");
|
||
const [result, setResult] = useState<RefineResult | null>(null);
|
||
const controllerRef = useRef<AbortController | null>(null);
|
||
const toast = useToast();
|
||
|
||
const refine = useCallback<UseRefine["refine"]>(
|
||
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 };
|
||
}
|