useRefine.refine/recommunicate→RefineVersion|null、useContinue.generate→string|null、 useChapterRewrite.send→RewriteVersion|null、useGenerator.generate→GeneratorArtifact|null。 向后兼容(既有调用忽略返回值不受影响);各 hook 成功用例补返回值断言。 面板据此 if(artifact) 才 append,空候选/错误路径不留痕。
118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
"use client";
|
||
|
||
import { useCallback, useMemo, useState } from "react";
|
||
|
||
import { api } from "@/lib/api/client";
|
||
import { useToast } from "@/components/Toast";
|
||
|
||
// 编辑器内「润色 / 再沟通」核心(复用同步 /refine,只读、不写库——不变量 #3)。
|
||
// 润色:对选中段回炉一次。再沟通:以上一版产出为输入 + 作者新意见再回炉,形成版本栈,
|
||
// 作者可回看每一版、择一 accept 回填正文(accept/回填由组件负责,此处只管生成与版本历史)。
|
||
|
||
export interface RefineVersion {
|
||
// 本次回炉的输入段:首轮 = 选中原文;再沟通 = 上一版产出。
|
||
segment: string;
|
||
// 本次改写指令(再沟通时为作者的新意见);无则空串。
|
||
instruction: string;
|
||
refined: string;
|
||
}
|
||
|
||
export type RefineStatus = "idle" | "refining" | "ready" | "error";
|
||
|
||
export interface UseRefine {
|
||
status: RefineStatus;
|
||
versions: RefineVersion[];
|
||
// 版本栈顶(最新产出)。
|
||
latest: RefineVersion | null;
|
||
// 最初选中的原文(accept 回填的对比基线)。
|
||
original: string | null;
|
||
// 回炉并返回本版产出(供调用方在结束后留痕);失败返回 null。
|
||
refine: (
|
||
projectId: string,
|
||
chapterNo: number,
|
||
segment: string,
|
||
instruction?: string,
|
||
) => Promise<RefineVersion | null>;
|
||
// 以上一版产出为输入 + 作者新意见再回炉一次。无历史版本时为空操作(返回 null)。
|
||
recommunicate: (
|
||
projectId: string,
|
||
chapterNo: number,
|
||
instruction: string,
|
||
) => Promise<RefineVersion | null>;
|
||
reset: () => void;
|
||
}
|
||
|
||
export function useRefine(): UseRefine {
|
||
const [status, setStatus] = useState<RefineStatus>("idle");
|
||
const [versions, setVersions] = useState<RefineVersion[]>([]);
|
||
const toast = useToast();
|
||
|
||
const runRefine = useCallback(
|
||
async (
|
||
projectId: string,
|
||
chapterNo: number,
|
||
segment: string,
|
||
instruction: string,
|
||
): Promise<RefineVersion | null> => {
|
||
setStatus("refining");
|
||
try {
|
||
const trimmed = instruction.trim();
|
||
const { data, error } = await api.POST(
|
||
"/projects/{project_id}/chapters/{chapter_no}/refine",
|
||
{
|
||
params: { path: { project_id: projectId, chapter_no: chapterNo } },
|
||
body: trimmed ? { segment, instruction: trimmed } : { segment },
|
||
},
|
||
);
|
||
if (error || !data) {
|
||
setStatus("error");
|
||
toast("回炉失败,请重试。", "error");
|
||
return null;
|
||
}
|
||
const version: RefineVersion = {
|
||
segment,
|
||
instruction: trimmed,
|
||
refined: data.refined,
|
||
};
|
||
setVersions((prev) => [...prev, version]);
|
||
setStatus("ready");
|
||
return version;
|
||
} catch {
|
||
setStatus("error");
|
||
toast("回炉请求异常,请检查网络。", "error");
|
||
return null;
|
||
}
|
||
},
|
||
[toast],
|
||
);
|
||
|
||
const refine = useCallback<UseRefine["refine"]>(
|
||
(projectId, chapterNo, segment, instruction) =>
|
||
runRefine(projectId, chapterNo, segment, instruction ?? ""),
|
||
[runRefine],
|
||
);
|
||
|
||
const recommunicate = useCallback<UseRefine["recommunicate"]>(
|
||
(projectId, chapterNo, instruction) => {
|
||
const last = versions[versions.length - 1];
|
||
// 无历史版本 → 无从「再沟通」;空操作,不打后端。
|
||
if (!last) return Promise.resolve(null);
|
||
return runRefine(projectId, chapterNo, last.refined, instruction);
|
||
},
|
||
[runRefine, versions],
|
||
);
|
||
|
||
const reset = useCallback((): void => {
|
||
setStatus("idle");
|
||
setVersions([]);
|
||
}, []);
|
||
|
||
const latest = versions.at(-1) ?? null;
|
||
const original = versions[0]?.segment ?? null;
|
||
|
||
return useMemo(
|
||
() => ({ status, versions, latest, original, refine, recommunicate, reset }),
|
||
[status, versions, latest, original, refine, recommunicate, reset],
|
||
);
|
||
}
|