feat: M4 文风 + M5 生成/多provider/Skill + Kimi Code 订阅接入 + 本地联调修复
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>
This commit is contained in:
154
apps/web/lib/command/palette.ts
Normal file
154
apps/web/lib/command/palette.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
// 命令面板(⌘K)纯逻辑:命令清单组装 + 模糊过滤 + 键盘选择状态机。
|
||||
// 对齐 UX §7(快速导航/动作)。纯逻辑,便于 node 环境单测(不依赖 DOM)。
|
||||
|
||||
export type CommandKind = "navigate" | "action";
|
||||
|
||||
export interface Command {
|
||||
id: string;
|
||||
// 展示标题(如「写本章」「跳转伏笔」)。
|
||||
title: string;
|
||||
// 用于匹配的关键词(拼音/别名),与 title 一起参与过滤。
|
||||
keywords: string[];
|
||||
kind: CommandKind;
|
||||
// navigate:跳转路径;action:交由调用方按 id 分派。
|
||||
href?: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
// 项目内可用命令(projectId 已知)。导航项给 href;动作项(生成角色等)给 id。
|
||||
export function projectCommands(projectId: string): Command[] {
|
||||
const p = `/projects/${projectId}`;
|
||||
return [
|
||||
{
|
||||
id: "nav-write",
|
||||
title: "写本章",
|
||||
keywords: ["write", "写作", "工作台", "起草"],
|
||||
kind: "navigate",
|
||||
href: `${p}/write`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-review",
|
||||
title: "审稿",
|
||||
keywords: ["review", "质检", "冲突", "裁决"],
|
||||
kind: "navigate",
|
||||
href: `${p}/review`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-outline",
|
||||
title: "大纲",
|
||||
keywords: ["outline", "节拍", "beats"],
|
||||
kind: "navigate",
|
||||
href: `${p}/outline`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-foreshadow",
|
||||
title: "跳转伏笔",
|
||||
keywords: ["foreshadow", "伏笔", "看板", "线索"],
|
||||
kind: "navigate",
|
||||
href: `${p}/foreshadow`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-style",
|
||||
title: "文风",
|
||||
keywords: ["style", "文风", "指纹", "漂移"],
|
||||
kind: "navigate",
|
||||
href: `${p}/style`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-codex",
|
||||
title: "搜设定",
|
||||
keywords: ["codex", "设定库", "人物", "世界观", "角色"],
|
||||
kind: "navigate",
|
||||
href: `${p}/codex`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-rules",
|
||||
title: "规则",
|
||||
keywords: ["rules", "规则", "硬规则"],
|
||||
kind: "navigate",
|
||||
href: `${p}/rules`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-skills",
|
||||
title: "技能库",
|
||||
keywords: ["skills", "技能", "注册表"],
|
||||
kind: "navigate",
|
||||
href: `${p}/skills`,
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "action-gen-character",
|
||||
title: "生成角色",
|
||||
keywords: ["character", "角色", "生成器", "群像"],
|
||||
kind: "action",
|
||||
href: `${p}/codex?gen=character`,
|
||||
group: "动作",
|
||||
},
|
||||
{
|
||||
id: "action-gen-world",
|
||||
title: "生成世界观",
|
||||
keywords: ["world", "世界观", "设定", "设计器"],
|
||||
kind: "action",
|
||||
href: `${p}/codex?gen=world`,
|
||||
group: "动作",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 全局命令(无 projectId 时;如作品库/设置)。
|
||||
export function globalCommands(): Command[] {
|
||||
return [
|
||||
{
|
||||
id: "nav-home",
|
||||
title: "作品库",
|
||||
keywords: ["home", "作品", "library", "projects"],
|
||||
kind: "navigate",
|
||||
href: "/",
|
||||
group: "导航",
|
||||
},
|
||||
{
|
||||
id: "nav-settings",
|
||||
title: "设置",
|
||||
keywords: ["settings", "设置", "凭据", "provider", "档位"],
|
||||
kind: "navigate",
|
||||
href: "/settings/providers",
|
||||
group: "导航",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 模糊过滤:空查询返回全部;否则在 title + keywords 上做大小写无关子串匹配(保持原顺序)。
|
||||
export function filterCommands(
|
||||
commands: readonly Command[],
|
||||
query: string,
|
||||
): Command[] {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return [...commands];
|
||||
return commands.filter((cmd) => {
|
||||
if (cmd.title.toLowerCase().includes(q)) return true;
|
||||
return cmd.keywords.some((kw) => kw.toLowerCase().includes(q));
|
||||
});
|
||||
}
|
||||
|
||||
// 键盘高亮索引在 [0, len) 内循环(空列表归 0)。
|
||||
export function clampHighlight(index: number, length: number): number {
|
||||
if (length <= 0) return 0;
|
||||
const wrapped = index % length;
|
||||
return wrapped < 0 ? wrapped + length : wrapped;
|
||||
}
|
||||
|
||||
// 上/下方向移动高亮(循环)。
|
||||
export function moveHighlight(
|
||||
index: number,
|
||||
delta: number,
|
||||
length: number,
|
||||
): number {
|
||||
return clampHighlight(index + delta, length);
|
||||
}
|
||||
Reference in New Issue
Block a user