"use client"; import { useCallback, useState } from "react"; import { api } from "@/lib/api/client"; import { useToast } from "@/components/Toast"; import type { CharacterCardView } from "@/lib/api/types"; import { buildCharacterGenerateRequest, buildCharacterIngestRequest, extractIngestConflicts, generationErrorMessage, type IngestConflicts, } from "./cards"; export type CharacterGenStatus = "idle" | "generating" | "preview" | "error"; export type IngestStatus = "idle" | "ingesting" | "conflict" | "done" | "error"; export interface CharacterGenInput { projectId: string; brief: string; count: number; role?: string | null; } export interface UseCharacterGen { genStatus: CharacterGenStatus; cards: CharacterCardView[]; ingestStatus: IngestStatus; // 409 冲突详情(待作者裁决);裁决后带 acknowledge 重发。 conflicts: IngestConflicts | null; created: string[]; generate: (input: CharacterGenInput) => Promise; // 入库选中卡;acknowledge 用于过 409(已查看冲突后重发)。 ingest: ( projectId: string, cards: readonly CharacterCardView[], acknowledge?: boolean, ) => Promise; reset: () => void; } // 角色生成器(UX §6.6):生成预览 → 作者选/改 → 入库(处理 409 裁决流)。 export function useCharacterGen(): UseCharacterGen { const [genStatus, setGenStatus] = useState("idle"); const [cards, setCards] = useState([]); const [ingestStatus, setIngestStatus] = useState("idle"); const [conflicts, setConflicts] = useState(null); const [created, setCreated] = useState([]); const toast = useToast(); const generate = useCallback( async ({ projectId, brief, count, role }) => { setGenStatus("generating"); setCards([]); setConflicts(null); setIngestStatus("idle"); try { const { data, error } = await api.POST( "/projects/{project_id}/characters/generate", { params: { path: { project_id: projectId } }, body: buildCharacterGenerateRequest(brief, count, role), }, ); if (error || !data) { setGenStatus("error"); toast(generationErrorMessage(error), "error"); return; } setCards(data.cards ?? []); setGenStatus("preview"); } catch { setGenStatus("error"); toast("生成请求异常,请检查网络。", "error"); } }, [toast], ); const ingest = useCallback( async (projectId, ingestCards, acknowledge = false) => { if (ingestCards.length === 0) { toast("请至少选择一张角色卡。", "error"); return false; } setIngestStatus("ingesting"); try { const { data, error } = await api.POST( "/projects/{project_id}/characters", { params: { path: { project_id: projectId } }, body: buildCharacterIngestRequest(ingestCards, acknowledge), }, ); 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} 个角色`, "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"); setCards([]); setIngestStatus("idle"); setConflicts(null); setCreated([]); }, []); return { genStatus, cards, ingestStatus, conflicts, created, generate, ingest, reset, }; }