/** * @file settingsSearch — the Settings page's search: `SETTINGS_INDEX` names the * rows each section holds (as i18n keys, resolved at render), and * `rankSettings` scores sections against a query. Per field, exact beats * prefix beats substring: section title 900/850/800, entry title 700/650/600, * description 500/450/400, keywords 300/250/200; a section scores its best hit. * `rankSettingsEntries` ranks the rows themselves, for the result list that * deep-links a row's sub-tab and anchor. * @author Nguyễn Ngọc Trí Vĩ */ export interface SettingsEntry { section: string; title: string; description?: string; keywords?: string[]; /** The section's own title (scored in the top tier). */ sectionTitle?: boolean; /** Appended to `/settings/
`: a sub-tab (`?tab=skills`) and/or an * anchor (`#cleanup`) the hub scrolls to and highlights. */ to?: string; } /** One searchable row per section, by i18n key (`settings` namespace unless * the key names another, `ccConfig:tabs.skills`). `to` targets a sub-tab and/or * a row anchor — an element with that `id` or `data-settings-anchor`. A * section whose id starts with `agents/` and is not listed here is a catalog * tool's page and takes the `agents/*` entries. */ export const SETTINGS_INDEX: { section: string; titleKey: string; descKey?: string; keywords?: string[]; to?: string; }[] = [ { section: "appearance", titleKey: "language.title", to: "#language", keywords: ["english", "tiếng việt", "vietnamese", "ngôn ngữ"], }, { section: "appearance", titleKey: "terminalFont.title", descKey: "terminalFont.description", keywords: ["font", "phông", "maple", "jetbrains", "monospace", "rounded", "bo tròn"], to: "#terminal-font", }, { section: "data", titleKey: "pushIngest.title", descKey: "pushIngest.description", keywords: ["token", "push", "remote", "batch"], to: "#push-ingest", }, { section: "data", titleKey: "privacy.title", descKey: "privacy.description", keywords: ["redact", "secrets", "ignore", "folder", "cwd"], to: "#privacy", }, { section: "data", titleKey: "lanesSection.title", descKey: "lanesSection.description", to: "#lanes", }, { section: "data", titleKey: "lanesSection.runHousekeeping", keywords: ["gc", "logs", "processes"], to: "#lanes", }, { section: "data", titleKey: "import.title", keywords: ["history", "transcripts"], to: "#import", }, { section: "data", titleKey: "remoteSources.title", keywords: ["ssh", "machines"], to: "#remote-sources", }, { section: "data", titleKey: "data.title", descKey: "data.description", to: "#data" }, { section: "data", titleKey: "exportData", keywords: ["backup", "download"], to: "#data" }, { section: "data", titleKey: "data.sessionCleanup", descKey: "data.cleanupDesc", keywords: ["purge", "abandon", "retention"], to: "#cleanup", }, { section: "data", titleKey: "prefsBackup.title", descKey: "prefsBackup.description", keywords: ["export", "import", "browser", "preferences"], to: "#prefs", }, { section: "data", titleKey: "danger.title", keywords: ["clear", "delete", "wipe"], to: "#danger", }, { section: "system", titleKey: "about.title", keywords: ["about", "version", "server", "database", "uptime", "memory", "cpu", "install"], }, { section: "system", titleKey: "autostart.label", keywords: ["autostart", "boot", "login", "systemd", "launchd", "service"], to: "#autostart", }, { section: "system", titleKey: "quit.label", keywords: ["quit", "stop", "kill", "shutdown", "exit", "background"], to: "#autostart", }, { section: "system", titleKey: "tray.label", keywords: ["tray", "icon", "taskbar", "menu bar", "status area", "notification area"], to: "#autostart", }, { section: "appearance", titleKey: "sagi.title", descKey: "sagi.description", keywords: ["sagi", "mascot", "pet"], to: "#pet", }, { section: "appearance", titleKey: "sagi.bubbles", descKey: "sagi.bubblesDesc", to: "#pet" }, { section: "appearance", titleKey: "sagi.pet.size", to: "#pet" }, { section: "appearance", titleKey: "sagi.pet.choose", keywords: ["codex-pets", "upload", "import"], to: "#pet", }, { section: "reports", titleKey: "snapshots.title", descKey: "snapshots.description", keywords: ["share", "freeze", "link", "redact"], to: "#snapshots", }, { section: "reports", titleKey: "reports.title", descKey: "reports.description", keywords: ["schedule", "report", "email", "summary", "html", "json", "export"], }, { section: "appearance", titleKey: "appearance.mode", keywords: ["dark", "light", "system"] }, { section: "appearance", titleKey: "appearance.darkThemes", keywords: ["theme", "colors"] }, { section: "appearance", titleKey: "appearance.lightThemes" }, { section: "notifications", titleKey: "notifications.enable", keywords: ["push", "browser"], to: "#notifications", }, { section: "notifications", titleKey: "notifications.awaitingInput", to: "#notifications" }, { section: "notifications", titleKey: "notifications.turnComplete", to: "#notifications" }, { section: "notifications", titleKey: "notifications.sound.title", keywords: ["chime", "audio"], to: "#notifications", }, { section: "notifications", titleKey: "notifications.sendTest", keywords: ["test"], to: "#notifications", }, { section: "notifications", titleKey: "alertsHub.title", descKey: "alertsHub.description", to: "#alerts", }, { section: "notifications", titleKey: "webhooks.title", keywords: ["slack", "discord", "telegram", "channels"], to: "#alerts", }, { section: "agents", titleKey: "aiTools.title", descKey: "aiTools.description", keywords: ["install", "update", "default"], }, { section: "agents/claude-code", titleKey: "hooks.title" }, { section: "agents/claude-code", titleKey: "claudeHome.title" }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.skills", to: "?tab=skills" }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.agents", to: "?tab=agents" }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.commands", to: "?tab=commands" }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.memory", keywords: ["claude.md"], to: "?tab=memory", }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.plugins", to: "?tab=plugins" }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.marketplaces", to: "?tab=marketplaces", }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.mcp", keywords: ["mcp"], to: "?tab=mcp", }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.hooks", keywords: ["hooks"], to: "?tab=hooks", }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.keybindings", keywords: ["shortcuts"], to: "?tab=keybindings", }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.outputStyles", to: "?tab=outputStyles", }, { section: "agents/claude-code", titleKey: "ccConfig:tabs.settings", keywords: ["settings.json", "statusline", "permissions"], to: "?tab=settings", }, { section: "agents/claude-code", titleKey: "ccConfig:statusline.title", keywords: ["statusline"], to: "?tab=settings", }, { section: "agents/claude-code", titleKey: "ccConfig:profiles.title", keywords: ["account", "login", "quota", "switch"], }, { section: "agents/claude-code", titleKey: "ccConfig:backups.title", keywords: ["restore"] }, { section: "agents/codex", titleKey: "codexHooks.title" }, { section: "agents/codex", titleKey: "codexHome.title" }, { section: "agents/codex", titleKey: "codexConfig:tabs.skills", to: "?tab=skills" }, { section: "agents/codex", titleKey: "codexConfig:tabs.agents", to: "?tab=agents" }, { section: "agents/codex", titleKey: "codexConfig:tabs.rules", to: "?tab=rules" }, { section: "agents/codex", titleKey: "codexConfig:tabs.memory", keywords: ["agents.md"], to: "?tab=memory", }, { section: "agents/codex", titleKey: "codexConfig:tabs.config", keywords: ["config.toml"], to: "?tab=config", }, { section: "agents/codex", titleKey: "codexConfig:tabs.mcp", keywords: ["mcp"], to: "?tab=mcp" }, { section: "agents/codex", titleKey: "codexConfig:tabs.plugins", to: "?tab=plugins" }, { section: "agents/antigravity", titleKey: "antigravityHooks.title", keywords: ["agy", "hooks"] }, { section: "agents/*", titleKey: "toolTab.defaultModel", keywords: ["model", "config"] }, { section: "ai-proxy", titleKey: "aiProxy.title", keywords: ["router", "endpoint", "key"] }, { section: "pricing", titleKey: "pricing.title", keywords: ["cost", "price", "tokens"] }, { section: "git", titleKey: "gitCreds.title", keywords: ["login", "token"] }, { section: "git", titleKey: "worktrees.showUntracked", keywords: ["hide", "suggestions"], to: "#worktrees", }, { section: "git", titleKey: "worktrees.claude", to: "#worktrees" }, { section: "git", titleKey: "worktrees.agenthub", to: "#worktrees" }, ]; function tier(hay: string | undefined, q: string, exact: number): number { if (!hay) return 0; const h = hay.toLowerCase(); if (h === q) return exact; if (h.startsWith(q)) return exact - 50; if (h.includes(q)) return exact - 100; return 0; } /** Score of one entry against an already-lowercased query (0 = no match). */ function scoreEntry(e: SettingsEntry, q: string): number { return Math.max( tier(e.title, q, e.sectionTitle ? 900 : 700), tier(e.description, q, 500), ...(e.keywords ?? []).map((k) => tier(k, q, 300)) ); } /** Entries matching `query`, best first (ties keep order), at most `limit`. */ export function rankSettingsEntries( query: string, entries: SettingsEntry[], limit = 8 ): (SettingsEntry & { score: number })[] { const q = query.trim().toLowerCase(); if (!q) return []; return entries .map((e) => ({ ...e, score: scoreEntry(e, q) })) .filter((e) => e.score > 0) .sort((a, b) => b.score - a.score) .slice(0, limit); } /** Sections matching `query`, best first; ties keep the entries' order. */ export function rankSettings( query: string, entries: SettingsEntry[] ): { section: string; score: number }[] { const q = query.trim().toLowerCase(); if (!q) return []; const best = new Map(); for (const e of entries) { const score = scoreEntry(e, q); if (score > (best.get(e.section) ?? 0)) best.set(e.section, score); } return [...best] .map(([section, score]) => ({ section, score })) .sort((a, b) => b.score - a.score); }