Files
writer-work-flow/apps/web/components/generation/WorldGenerator.tsx
2026-06-28 07:31:20 +02:00

82 lines
2.9 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, useState } from "react";
import { Sparkles } from "lucide-react";
import { Button } from "@/components/ui/Button";
import { Field } from "@/components/ui/Field";
import { SectionHeader } from "@/components/ui/SectionHeader";
import { TextArea } from "@/components/ui/TextArea";
import { worldEntityRules } from "@/lib/generation/cards";
import { useWorldGen } from "@/lib/generation/useWorldGen";
interface WorldGeneratorProps {
projectId: string;
}
// 世界观设计器UX §6.5):一句话需求 → 预览实体卡type/name/rules
// 本期世界观入库端点未建(仅预览);预览结果供作者参考/复制。
export function WorldGenerator({ projectId }: WorldGeneratorProps) {
const gen = useWorldGen();
const [brief, setBrief] = useState("");
const onGenerate = useCallback(async () => {
await gen.generate(projectId, brief);
}, [gen, projectId, brief]);
const generating = gen.status === "generating";
return (
<section className="flex flex-col gap-4" aria-label="世界观设计器">
<div className="rounded border border-line bg-panel p-4">
<SectionHeader title="世界观设计器" className="mb-3" />
<Field label="设定方向" className="mb-3">
<TextArea
value={brief}
onChange={(e) => setBrief(e.target.value)}
placeholder="如:东方修真世界,灵气复苏,宗门林立,强者寿元有限"
rows={3}
/>
</Field>
<Button
onClick={() => void onGenerate()}
disabled={generating || brief.trim().length === 0}
variant="primary"
>
<Sparkles className="h-4 w-4" aria-hidden="true" />
{generating ? "生成中…" : "生成世界观"}
</Button>
</div>
{gen.status === "done" && gen.entities.length > 0 ? (
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
{gen.entities.map((entity, i) => (
<article
key={`${entity.name}-${i}`}
className="rounded border border-line bg-panel p-3 text-sm"
>
<header className="mb-2 flex items-center gap-2">
<span className="font-serif text-base text-ink">
{entity.name}
</span>
<span className="rounded bg-bg px-2 py-0.5 text-xs text-ink-soft">
{entity.type}
</span>
</header>
{worldEntityRules(entity).length > 0 ? (
<ul className="flex list-disc flex-col gap-1 pl-5 text-ink-soft">
{worldEntityRules(entity).map((rule, j) => (
<li key={j}>{rule}</li>
))}
</ul>
) : (
<p className="text-ink-soft"></p>
)}
</article>
))}
</div>
) : null}
</section>
);
}