M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。 M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变); 网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则; 前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。 K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径; coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。 本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400); GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化; 字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。 门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
"use client";
|
||
|
||
import { useCallback, useState } from "react";
|
||
|
||
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">
|
||
<h2 className="mb-3 font-serif text-base text-ink">世界观设计器</h2>
|
||
<label className="mb-3 block text-sm text-ink-soft">
|
||
设定方向
|
||
<textarea
|
||
value={brief}
|
||
onChange={(e) => setBrief(e.target.value)}
|
||
placeholder="如:东方修真世界,灵气复苏,宗门林立,强者寿元有限"
|
||
rows={3}
|
||
className="mt-1 w-full resize-y rounded border border-line bg-bg px-3 py-2 text-sm text-ink"
|
||
/>
|
||
</label>
|
||
<button
|
||
type="button"
|
||
onClick={() => void onGenerate()}
|
||
disabled={generating || brief.trim().length === 0}
|
||
className="rounded bg-cinnabar px-4 py-2 text-sm text-white disabled:opacity-50"
|
||
>
|
||
{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>
|
||
);
|
||
}
|