Files
writer-work-flow/apps/web/lib/style/useRefine.ts

105 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 };
}