import type { OutlineChapterView, WorldEntityCardView } from "@/lib/api/types"; // 速查抽屉(ContextDrawer)的 Tab 配置与纯整形逻辑。 // 视图无关、无副作用,供组件与单测复用(组件本身不进 vitest 范围)。 export type ContextTabKey = | "codex" | "outline" | "foreshadow" | "rules" | "style"; export interface ContextTab { key: ContextTabKey; label: string; // 完整编辑页路由后缀(拼在 /projects/{id}/ 之后)。 routeSegment: string; } // Tab 顺序即抽屉里的呈现顺序:读为主的设定入口,设定库/大纲在前(本期做完整)。 export const CONTEXT_TABS: readonly ContextTab[] = [ { key: "codex", label: "设定库", routeSegment: "codex" }, { key: "outline", label: "大纲", routeSegment: "outline" }, { key: "foreshadow", label: "伏笔", routeSegment: "foreshadow" }, { key: "rules", label: "规则", routeSegment: "rules" }, { key: "style", label: "文风", routeSegment: "style" }, ]; // 抽屉只读速查 →「去完整页编辑」链接。未知 key 回退用 key 作后缀(防御)。 export function contextTabHref(projectId: string, key: ContextTabKey): string { const tab = CONTEXT_TABS.find((t) => t.key === key); const segment = tab?.routeSegment ?? key; return `/projects/${projectId}/${segment}`; } export interface CodexGroup { type: string; entities: WorldEntityCardView[]; } // 设定库实体按 type 归组,保持首次出现顺序(紧凑分组展示)。空 type 归「未分类」。 // 不可变:不改入参,返回全新分组数组。 export function toCodexGroups( entities: readonly WorldEntityCardView[], ): CodexGroup[] { const groups: CodexGroup[] = []; const indexByType = new Map(); for (const entity of entities) { const type = entity.type.trim() || "未分类"; const idx = indexByType.get(type); if (idx === undefined) { indexByType.set(type, groups.length); groups.push({ type, entities: [entity] }); } else { const group = groups.at(idx); if (group) { groups[idx] = { type: group.type, entities: [...group.entities, entity] }; } } } return groups; } export interface OutlineRow { no: number; volume: number; beats: string[]; foreshadowCodes: string[]; } // 大纲章节整形为紧凑速查行:按章号升序,抽出节拍与该章伏笔窗口代号。 // 不可变:先复制再排序,不改入参。 export function toOutlineRows( chapters: readonly OutlineChapterView[], ): OutlineRow[] { return [...chapters] .sort((a, b) => a.no - b.no) .map((chapter) => ({ no: chapter.no, volume: chapter.volume, beats: chapter.beats ?? [], foreshadowCodes: (chapter.foreshadow_windows ?? []).map((w) => w.code), })); }