"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Database, Sparkles } from "lucide-react"; import { ThinkingIndicator } from "@/components/ThinkingIndicator"; import { Button } from "@/components/ui/Button"; import { Field } from "@/components/ui/Field"; import { SectionHeader } from "@/components/ui/SectionHeader"; import { SegmentedControl } from "@/components/ui/SegmentedControl"; import { StatusNote } from "@/components/ui/StatusNote"; import { TextInput } from "@/components/ui/TextInput"; import type { CharacterCardView } from "@/lib/api/types"; import { clampCharacterCount, MAX_CHARACTER_COUNT, MIN_CHARACTER_COUNT, roleMixTotal, sanitizeRoleMix, updateCard, } from "@/lib/generation/cards"; import { useCharacterGen } from "@/lib/generation/useCharacterGen"; import { CharacterCardItem } from "./CharacterCardItem"; import { ConflictAdjudication } from "./ConflictAdjudication"; import { RoleMixEditor, type RoleMixRow } from "./RoleMixEditor"; type GenMode = "single" | "mix"; const MODE_OPTIONS: Array<{ value: GenMode; label: string }> = [ { value: "single", label: "单一定位" }, { value: "mix", label: "按定位配比" }, ]; const DEFAULT_MIX_ROWS: RoleMixRow[] = [ { role: "主角", count: 1 }, { role: "对手", count: 1 }, ]; interface CharacterGeneratorProps { projectId: string; // 入库成功后回调(如刷新 Codex 本地列表)。 onIngested?: (created: string[], cards: CharacterCardView[]) => void; } // 角色生成器(UX §6.6):一句话需求 + 数量 + 定位 → 预览卡 → 选/改 → 入库(处理 409 裁决)。 export function CharacterGenerator({ projectId, onIngested, }: CharacterGeneratorProps) { const gen = useCharacterGen(); const [brief, setBrief] = useState(""); const [count, setCount] = useState(MIN_CHARACTER_COUNT); const [role, setRole] = useState(""); const [mode, setMode] = useState("single"); const [mixRows, setMixRows] = useState(DEFAULT_MIX_ROWS); // 配比模式下的有效合计(sanitize 后,已按 MAX 截断)——门控生成按钮。 const mixTotal = roleMixTotal(sanitizeRoleMix(mixRows)); // 编辑态卡片(预览返回后拷贝进本地,允许就地改)。 const [drafts, setDrafts] = useState([]); const [selected, setSelected] = useState>(new Set()); // 预览返回 → 同步进本地编辑态 + 默认全选。 const syncDrafts = useCallback((cards: CharacterCardView[]) => { setDrafts(cards); setSelected(new Set(cards.map((_, i) => i))); }, []); const onGenerate = useCallback(async () => { if (mode === "mix") { await gen.generate({ projectId, brief, count, roleMix: sanitizeRoleMix(mixRows), }); return; } await gen.generate({ projectId, brief, count, role }); }, [gen, projectId, brief, count, role, mode, mixRows]); // gen.cards 变化(新预览)→ 重新同步本地草稿。 const previewKey = gen.cards.map((c) => c.name).join("|"); useEffect(() => { if (gen.genStatus === "preview") syncDrafts(gen.cards); // eslint-disable-next-line react-hooks/exhaustive-deps }, [previewKey, gen.genStatus]); const toggle = (i: number): void => { setSelected((prev) => { const next = new Set(prev); if (next.has(i)) next.delete(i); else next.add(i); return next; }); }; const selectedCards = useMemo( () => drafts.filter((_, i) => selected.has(i)), [drafts, selected], ); const doIngest = useCallback( async (acknowledge: boolean) => { const ok = await gen.ingest(projectId, selectedCards, acknowledge); if (ok) { onIngested?.(gen.created, selectedCards); setDrafts([]); setSelected(new Set()); } }, [gen, projectId, selectedCards, onIngested], ); const generating = gen.genStatus === "generating"; const ingesting = gen.ingestStatus === "ingesting"; return (
setBrief(e.target.value)} placeholder="如:一个亦正亦邪的剑修,背负灭门血仇" />
{mode === "mix" ? ( ) : (
setCount(clampCharacterCount(Number(e.target.value))) } /> setRole(e.target.value)} placeholder="主角 / CP / 对手 / 导师 / 工具人" />
)}
{generating ? (
) : null} {gen.genStatus === "preview" && drafts.length > 0 ? (

已生成 {drafts.length} 张,选 {selectedCards.length} 张入库(可就地编辑名/弧光)。

{drafts.map((card, i) => ( toggle(i)} onEdit={(patch) => setDrafts((prev) => prev.map((c, j) => (j === i ? updateCard(c, patch) : c)), ) } /> ))}
{gen.ingestStatus === "conflict" && gen.conflicts ? ( void doIngest(true)} onCancel={() => gen.reset()} /> ) : ( )}
) : null} {gen.ingestStatus === "done" && gen.created.length > 0 ? ( 已入库:{gen.created.join("、")} ) : null}
); }