"use client"; import { useCallback, useState } from "react"; import { api } from "@/lib/api/client"; import { useToast } from "@/components/Toast"; import { extractIngestConflicts, generationErrorMessage, type IngestConflicts, } from "@/lib/generation/cards"; import { mapPreview, type PreviewModel } from "./toolbox"; import { buildIngestRequest, type IngestBuildInput } from "./ingest"; import type { ToolGenerateRequest } from "@/lib/api/types"; export type GenStatus = "idle" | "generating" | "preview" | "error"; export type IngestStatus = "idle" | "ingesting" | "conflict" | "done" | "error"; // 一次生成的产物(供调用方在结束后留痕;与 hook state 同源)。 export interface GeneratorArtifact { preview: PreviewModel; rawPreview: Record | undefined; outputKind: string; } export interface UseGenerator { genStatus: GenStatus; preview: PreviewModel | null; // 原始结构化产物(供入库按选中下标形变;与 preview.items 同源对齐)。 rawPreview: Record | undefined; outputKind: string | null; ingestStatus: IngestStatus; // 409 冲突详情(待作者裁决);裁决后带 acknowledge 重发。 conflicts: IngestConflicts | null; created: string[]; generate: ( projectId: string, toolKey: string, body: ToolGenerateRequest, ) => Promise; // 入库选中行;acknowledge 用于过 409(已查看冲突后重发)。 ingest: ( projectId: string, toolKey: string, input: IngestBuildInput, ) => Promise; reset: () => void; } // 通用生成器状态机(idle→generating→preview)+ 入库 409 裁决流,仿 useCharacterGen。 // 声明驱动:generate/ingest 不感知具体工具,按 tool_key + descriptor 形变后调通用端点。 export function useGenerator(): UseGenerator { const [genStatus, setGenStatus] = useState("idle"); const [preview, setPreview] = useState(null); const [rawPreview, setRawPreview] = useState< Record | undefined >(undefined); const [outputKind, setOutputKind] = useState(null); const [ingestStatus, setIngestStatus] = useState("idle"); const [conflicts, setConflicts] = useState(null); const [created, setCreated] = useState([]); const toast = useToast(); const generate = useCallback( async (projectId, toolKey, body) => { setGenStatus("generating"); setPreview(null); setRawPreview(undefined); setOutputKind(null); setConflicts(null); setIngestStatus("idle"); setCreated([]); try { const { data, error } = await api.POST( "/projects/{project_id}/skills/{tool_key}/generate", { params: { path: { project_id: projectId, tool_key: toolKey } }, body, }, ); if (error || !data) { setGenStatus("error"); toast(generationErrorMessage(error), "error"); return null; } const mapped = mapPreview(data.output_kind, data.preview); setPreview(mapped); setRawPreview(data.preview); setOutputKind(data.output_kind); setGenStatus("preview"); return { preview: mapped, rawPreview: data.preview, outputKind: data.output_kind, }; } catch { setGenStatus("error"); toast("生成请求异常,请检查网络。", "error"); return null; } }, [toast], ); const ingest = useCallback( async (projectId, toolKey, input) => { const body = buildIngestRequest(input); if (!body) { toast("该生成器不支持入库。", "error"); return false; } if (input.rows.length === 0) { toast("请至少选择一项入库。", "error"); return false; } setIngestStatus("ingesting"); try { const { data, error } = await api.POST( "/projects/{project_id}/skills/{tool_key}/ingest", { params: { path: { project_id: projectId, tool_key: toolKey } }, body, }, ); if (error || !data) { const conflict = extractIngestConflicts(error); if (conflict) { // 409 CONFLICT_UNRESOLVED:展示冲突让作者裁决,再带 acknowledge 重发。 setConflicts(conflict); setIngestStatus("conflict"); return false; } setIngestStatus("error"); toast(generationErrorMessage(error), "error"); return false; } setCreated(data.created ?? []); setConflicts(null); setIngestStatus("done"); toast(`已入库 ${data.created?.length ?? 0} 项至 ${data.table}`, "success"); if (data.rejected_tables && data.rejected_tables.length > 0) { toast(`越权写表被丢弃:${data.rejected_tables.join("、")}`, "info"); } return true; } catch { setIngestStatus("error"); toast("入库请求异常,请检查网络。", "error"); return false; } }, [toast], ); const reset = useCallback((): void => { setGenStatus("idle"); setPreview(null); setRawPreview(undefined); setOutputKind(null); setIngestStatus("idle"); setConflicts(null); setCreated([]); }, []); return { genStatus, preview, rawPreview, outputKind, ingestStatus, conflicts, created, generate, ingest, reset, }; }