Files
writer-work-flow/apps/web/lib/workbench/useRefine.ts
Yaojia Wang 59f2f60a7e refactor(frontend): 四生成 hook 结束后额外返回产物(AC-3 留痕前置)
useRefine.refine/recommunicate→RefineVersion|null、useContinue.generate→string|null、
useChapterRewrite.send→RewriteVersion|null、useGenerator.generate→GeneratorArtifact|null。
向后兼容(既有调用忽略返回值不受影响);各 hook 成功用例补返回值断言。
面板据此 if(artifact) 才 append,空候选/错误路径不留痕。
2026-07-09 17:15:49 +02:00

118 lines
3.8 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, 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],
);
}