feat(web): 世界观预览补「仅预览」说明与按卡复制
This commit is contained in:
@@ -1,14 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { Sparkles } from "lucide-react";
|
import { Copy, Sparkles } from "lucide-react";
|
||||||
|
|
||||||
|
import { useToast } from "@/components/Toast";
|
||||||
import { Button } from "@/components/ui/Button";
|
import { Button } from "@/components/ui/Button";
|
||||||
import { Field } from "@/components/ui/Field";
|
import { Field } from "@/components/ui/Field";
|
||||||
import { SectionHeader } from "@/components/ui/SectionHeader";
|
import { SectionHeader } from "@/components/ui/SectionHeader";
|
||||||
|
import { StatusNote } from "@/components/ui/StatusNote";
|
||||||
import { TextArea } from "@/components/ui/TextArea";
|
import { TextArea } from "@/components/ui/TextArea";
|
||||||
import { worldEntityRules } from "@/lib/generation/cards";
|
import { worldEntityRules } from "@/lib/generation/cards";
|
||||||
|
import type { WorldEntityCardView } from "@/lib/api/types";
|
||||||
import { useWorldGen } from "@/lib/generation/useWorldGen";
|
import { useWorldGen } from "@/lib/generation/useWorldGen";
|
||||||
|
import { cardClass } from "@/lib/ui/variants";
|
||||||
|
|
||||||
interface WorldGeneratorProps {
|
interface WorldGeneratorProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -16,14 +20,34 @@ interface WorldGeneratorProps {
|
|||||||
|
|
||||||
// 世界观设计器(UX §6.5):一句话需求 → 预览实体卡(type/name/rules)。
|
// 世界观设计器(UX §6.5):一句话需求 → 预览实体卡(type/name/rules)。
|
||||||
// 本期世界观入库端点未建(仅预览);预览结果供作者参考/复制。
|
// 本期世界观入库端点未建(仅预览);预览结果供作者参考/复制。
|
||||||
|
// 复制文案:名称 + 硬规则正文(供作者粘贴到「规则」或「设定库」)。
|
||||||
|
function entityClipboardText(entity: WorldEntityCardView): string {
|
||||||
|
const rules = worldEntityRules(entity);
|
||||||
|
const body = rules.length > 0 ? rules.join("\n") : "(无显式硬规则)";
|
||||||
|
return `${entity.name}(${entity.type})\n${body}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function WorldGenerator({ projectId }: WorldGeneratorProps) {
|
export function WorldGenerator({ projectId }: WorldGeneratorProps) {
|
||||||
const gen = useWorldGen();
|
const gen = useWorldGen();
|
||||||
|
const toast = useToast();
|
||||||
const [brief, setBrief] = useState("");
|
const [brief, setBrief] = useState("");
|
||||||
|
|
||||||
const onGenerate = useCallback(async () => {
|
const onGenerate = useCallback(async () => {
|
||||||
await gen.generate(projectId, brief);
|
await gen.generate(projectId, brief);
|
||||||
}, [gen, projectId, brief]);
|
}, [gen, projectId, brief]);
|
||||||
|
|
||||||
|
const onCopy = useCallback(
|
||||||
|
async (entity: WorldEntityCardView): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(entityClipboardText(entity));
|
||||||
|
toast("已复制到剪贴板。", "success");
|
||||||
|
} catch {
|
||||||
|
toast("复制失败,请手动选择文本。", "error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[toast],
|
||||||
|
);
|
||||||
|
|
||||||
const generating = gen.status === "generating";
|
const generating = gen.status === "generating";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,11 +73,15 @@ export function WorldGenerator({ projectId }: WorldGeneratorProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{gen.status === "done" && gen.entities.length > 0 ? (
|
{gen.status === "done" && gen.entities.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
|
<div className="flex flex-col gap-3">
|
||||||
{gen.entities.map((entity, i) => (
|
<StatusNote variant="info">
|
||||||
|
当前为预览,世界观入库尚未开放;可复制后粘到「规则」或「设定库」。
|
||||||
|
</StatusNote>
|
||||||
|
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
|
||||||
|
{gen.entities.map((entity, i) => (
|
||||||
<article
|
<article
|
||||||
key={`${entity.name}-${i}`}
|
key={`${entity.name}-${i}`}
|
||||||
className="rounded border border-line bg-panel p-3 text-sm"
|
className={cardClass("p-3 text-sm")}
|
||||||
>
|
>
|
||||||
<header className="mb-2 flex items-center gap-2">
|
<header className="mb-2 flex items-center gap-2">
|
||||||
<span className="font-serif text-base text-ink">
|
<span className="font-serif text-base text-ink">
|
||||||
@@ -62,6 +90,17 @@ export function WorldGenerator({ projectId }: WorldGeneratorProps) {
|
|||||||
<span className="rounded bg-bg px-2 py-0.5 text-xs text-ink-soft">
|
<span className="rounded bg-bg px-2 py-0.5 text-xs text-ink-soft">
|
||||||
{entity.type}
|
{entity.type}
|
||||||
</span>
|
</span>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => void onCopy(entity)}
|
||||||
|
className="ml-auto"
|
||||||
|
aria-label={`复制 ${entity.name}`}
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4" aria-hidden="true" />
|
||||||
|
复制
|
||||||
|
</Button>
|
||||||
</header>
|
</header>
|
||||||
{worldEntityRules(entity).length > 0 ? (
|
{worldEntityRules(entity).length > 0 ? (
|
||||||
<ul className="flex list-disc flex-col gap-1 pl-5 text-ink-soft">
|
<ul className="flex list-disc flex-col gap-1 pl-5 text-ink-soft">
|
||||||
@@ -73,7 +112,8 @@ export function WorldGenerator({ projectId }: WorldGeneratorProps) {
|
|||||||
<p className="text-ink-soft">(无显式硬规则)</p>
|
<p className="text-ink-soft">(无显式硬规则)</p>
|
||||||
)}
|
)}
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user