Files
writer-work-flow/apps/web/lib/command/palette.ts
Yaojia Wang 765dbdfbd4 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>
2026-06-20 10:39:58 +02:00

155 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 命令面板⌘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);
}