98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
"use client";
|
||
|
||
import { useCallback, useMemo, useState } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import { Sparkles } from "lucide-react";
|
||
|
||
import { AppShell } from "@/components/AppShell";
|
||
import { EmptyState } from "@/components/ui/EmptyState";
|
||
import { PageHeader } from "@/components/ui/PageHeader";
|
||
import type { ProjectResponse, ToolDescriptorView } from "@/lib/api/types";
|
||
import { isLegacyTool, resolveLegacyRoute } from "@/lib/toolbox/toolbox";
|
||
import { ToolCard } from "./ToolCard";
|
||
import { GeneratorRunner } from "./GeneratorRunner";
|
||
|
||
interface ToolboxPageProps {
|
||
project: ProjectResponse;
|
||
tools: ToolDescriptorView[];
|
||
// 命令面板 action-gen-<key> 深链:进页直接打开该工具的 runner(仅新工具)。
|
||
initialOpenKey?: string;
|
||
}
|
||
|
||
// 创作工具箱(通用生成器框架):声明驱动的卡片栅格。
|
||
// legacy 工具(世界观/人设/大纲)跳现有页面保其更丰富的入库流;新工具开 GeneratorRunner。
|
||
export function ToolboxPage({
|
||
project,
|
||
tools,
|
||
initialOpenKey,
|
||
}: ToolboxPageProps) {
|
||
const router = useRouter();
|
||
|
||
const findOpenable = useCallback(
|
||
(key: string | undefined): ToolDescriptorView | null => {
|
||
if (!key) return null;
|
||
const tool = tools.find((t) => t.key === key);
|
||
return tool && !isLegacyTool(tool) ? tool : null;
|
||
},
|
||
[tools],
|
||
);
|
||
|
||
const [active, setActive] = useState<ToolDescriptorView | null>(() =>
|
||
findOpenable(initialOpenKey),
|
||
);
|
||
|
||
const onOpen = useCallback(
|
||
(tool: ToolDescriptorView): void => {
|
||
if (isLegacyTool(tool) && tool.legacy_route) {
|
||
router.push(resolveLegacyRoute(tool.legacy_route, project.id));
|
||
return;
|
||
}
|
||
setActive(tool);
|
||
},
|
||
[router, project.id],
|
||
);
|
||
|
||
const sorted = useMemo(
|
||
// 新工具靠前(更想被发现),legacy 收后;同组保持后端顺序。
|
||
() => [...tools].sort((a, b) => Number(a.is_legacy) - Number(b.is_legacy)),
|
||
[tools],
|
||
);
|
||
|
||
return (
|
||
<AppShell
|
||
title={`〈${project.title}〉`}
|
||
subtitle="工具箱"
|
||
projectId={project.id}
|
||
activeNav="toolbox"
|
||
>
|
||
<div className="mx-auto flex max-w-5xl flex-col gap-6 p-6">
|
||
<PageHeader
|
||
title="创作工具箱"
|
||
eyebrow="generator toolbox"
|
||
description="脑洞、书名、简介、名字、金手指、词条、黄金开篇与细纲。每个工具都先生成结构化预览,可入库内容会经过一致性预检。"
|
||
/>
|
||
|
||
{active ? (
|
||
<GeneratorRunner
|
||
projectId={project.id}
|
||
tool={active}
|
||
onClose={() => setActive(null)}
|
||
/>
|
||
) : sorted.length === 0 ? (
|
||
<EmptyState
|
||
icon={Sparkles}
|
||
title="暂无可用生成器"
|
||
description="后端暂未返回工具描述符。工具箱会在描述符可用后自动渲染卡片与输入表单。"
|
||
/>
|
||
) : (
|
||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||
{sorted.map((tool) => (
|
||
<ToolCard key={tool.key} tool={tool} onOpen={onOpen} />
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</AppShell>
|
||
);
|
||
}
|