feat(toolbox): T6 创作工具箱通用生成器框架 — 8 新生成器 + 声明驱动落地页 + P2 收尾
通用执行路径驱动全部生成器("加生成器=加一份声明"):
- @llm: ww_agents +7 输出 schema + 7 spec(book-title/blurb/name/golden-finger/
glossary/opening/fine-outline,只声明 tier)+ build_outline_chapter_context
- @backend: ww_skills GeneratorTool 描述符 + TOOLBOX(11) + get_tool;3 通用端点
GET /skills/toolbox · POST .../skills/{tool_key}/generate(预览不写库,仅记账) ·
POST .../ingest(复用 continuity 409 + partition_writes 白名单);纯 context 派发
- @frontend: 工具箱落地页 RSC + 声明驱动 GeneratorRunner + lib/toolbox 纯函数
+ LeftNav「工具箱」+ ⌘K nav-toolbox/action-gen-*;legacy 3 跳现页
- @qa: tests/test_t6_toolbox_e2e.py 5 用例真 pg + mock 网关零 token,无端点 bug
- P2 收尾: 限流→decisions.md 记延后(单用户原型);noopener/Committable 早已修
守不变量 #2(只声明 tier)/#3(预览不写库,入库经验收 gate)/#9(缓存前缀)。无 DB 迁移。
门禁绿: 后端 ruff/format/mypy 195/alembic 无漂移/pytest 583;前端 lint/tsc/vitest 279/build。
spec 回写 PRODUCT_SPEC §7 + ARCHITECTURE §7.2 端点表。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
92
apps/web/components/toolbox/ToolboxPage.tsx
Normal file
92
apps/web/components/toolbox/ToolboxPage.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
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">
|
||||
<header>
|
||||
<h1 className="font-serif text-lg text-ink">创作工具箱</h1>
|
||||
<p className="mt-1 text-xs text-ink-soft">
|
||||
声明驱动的生成器集合:脑洞 / 书名 / 简介 / 名字 / 金手指 / 词条 / 黄金开篇 / 细纲…
|
||||
选一个开始,结构化产物可一键入库(经一致性预检)。
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{active ? (
|
||||
<GeneratorRunner
|
||||
projectId={project.id}
|
||||
tool={active}
|
||||
onClose={() => setActive(null)}
|
||||
/>
|
||||
) : sorted.length === 0 ? (
|
||||
<p className="text-sm text-ink-soft">(暂无可用生成器)</p>
|
||||
) : (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user