@konbakuyomu/smart-search-linux-x64 0.0.0 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +18 -3
  3. package/runtime/smart-search/_internal/base_library.zip +0 -0
  4. package/runtime/smart-search/_internal/certifi/cacert.pem +3959 -0
  5. package/runtime/smart-search/_internal/certifi/py.typed +0 -0
  6. package/runtime/smart-search/_internal/libpython3.13.so.1.0 +0 -0
  7. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/INSTALLER +1 -0
  8. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/METADATA +171 -0
  9. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/RECORD +298 -0
  10. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/WHEEL +5 -0
  11. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/licenses/AUTHORS.rst +11 -0
  12. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/licenses/LICENSE +27 -0
  13. package/runtime/smart-search/_internal/prompt_toolkit-3.0.53.dist-info/top_level.txt +1 -0
  14. package/runtime/smart-search/_internal/smart_search/__init__.py +1 -0
  15. package/runtime/smart-search/_internal/smart_search/activity.py +279 -0
  16. package/runtime/smart-search/_internal/smart_search/assets/i18n/messages.json +17858 -0
  17. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/SKILL.md +69 -0
  18. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/agents/openai.yaml +3 -0
  19. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/cli-contract.md +21 -0
  20. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/cli-core.md +99 -0
  21. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/command-patterns.md +166 -0
  22. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/deep-research-mode.md +204 -0
  23. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/provider-routing.md +178 -0
  24. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/regression-release.md +46 -0
  25. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/setup-config.md +133 -0
  26. package/runtime/smart-search/_internal/smart_search/assets/ui/index.html +955 -0
  27. package/runtime/smart-search/_internal/smart_search/cli.py +3651 -0
  28. package/runtime/smart-search/_internal/smart_search/config.py +1302 -0
  29. package/runtime/smart-search/_internal/smart_search/desktop_backend.py +709 -0
  30. package/runtime/smart-search/_internal/smart_search/desktop_catalog.py +166 -0
  31. package/runtime/smart-search/_internal/smart_search/desktop_cli.py +209 -0
  32. package/runtime/smart-search/_internal/smart_search/desktop_entry.py +19 -0
  33. package/runtime/smart-search/_internal/smart_search/desktop_environment.py +629 -0
  34. package/runtime/smart-search/_internal/smart_search/desktop_skills.py +129 -0
  35. package/runtime/smart-search/_internal/smart_search/desktop_updates.py +160 -0
  36. package/runtime/smart-search/_internal/smart_search/desktop_worker.py +117 -0
  37. package/runtime/smart-search/_internal/smart_search/i18n.py +135 -0
  38. package/runtime/smart-search/_internal/smart_search/intent_router.py +253 -0
  39. package/runtime/smart-search/_internal/smart_search/jev.py +473 -0
  40. package/runtime/smart-search/_internal/smart_search/jev_search.py +719 -0
  41. package/runtime/smart-search/_internal/smart_search/logger.py +43 -0
  42. package/runtime/smart-search/_internal/smart_search/provider_descriptions.py +140 -0
  43. package/runtime/smart-search/_internal/smart_search/provider_errors.py +137 -0
  44. package/runtime/smart-search/_internal/smart_search/provider_health.py +277 -0
  45. package/runtime/smart-search/_internal/smart_search/providers/__init__.py +27 -0
  46. package/runtime/smart-search/_internal/smart_search/providers/anysearch.py +336 -0
  47. package/runtime/smart-search/_internal/smart_search/providers/base.py +41 -0
  48. package/runtime/smart-search/_internal/smart_search/providers/context7.py +153 -0
  49. package/runtime/smart-search/_internal/smart_search/providers/exa.py +191 -0
  50. package/runtime/smart-search/_internal/smart_search/providers/jina.py +124 -0
  51. package/runtime/smart-search/_internal/smart_search/providers/openai_compatible.py +977 -0
  52. package/runtime/smart-search/_internal/smart_search/providers/sciverse.py +351 -0
  53. package/runtime/smart-search/_internal/smart_search/providers/tinyfish.py +213 -0
  54. package/runtime/smart-search/_internal/smart_search/providers/xai_responses.py +242 -0
  55. package/runtime/smart-search/_internal/smart_search/providers/zhipu.py +172 -0
  56. package/runtime/smart-search/_internal/smart_search/providers/zhipu_mcp.py +287 -0
  57. package/runtime/smart-search/_internal/smart_search/sciverse_schema.py +437 -0
  58. package/runtime/smart-search/_internal/smart_search/service.py +4302 -0
  59. package/runtime/smart-search/_internal/smart_search/skill_installer.py +549 -0
  60. package/runtime/smart-search/_internal/smart_search/skill_maintenance.py +181 -0
  61. package/runtime/smart-search/_internal/smart_search/sources.py +429 -0
  62. package/runtime/smart-search/_internal/smart_search/state_files.py +53 -0
  63. package/runtime/smart-search/_internal/smart_search/ui_api.py +246 -0
  64. package/runtime/smart-search/_internal/smart_search/ui_metadata.py +698 -0
  65. package/runtime/smart-search/_internal/smart_search/ui_server.py +506 -0
  66. package/runtime/smart-search/_internal/smart_search/utils.py +220 -0
  67. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/INSTALLER +1 -0
  68. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/METADATA +97 -0
  69. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/RECORD +105 -0
  70. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/REQUESTED +0 -0
  71. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/WHEEL +5 -0
  72. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/direct_url.json +1 -0
  73. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/entry_points.txt +2 -0
  74. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/licenses/LICENSE +21 -0
  75. package/runtime/smart-search/_internal/smart_search-0.1.26.dist-info/top_level.txt +1 -0
  76. package/runtime/smart-search/_internal/wcwidth/_wcwidth_c.abi3.so +0 -0
  77. package/runtime/smart-search/_internal/wcwidth/_wcwidth_c.pyi +77 -0
  78. package/runtime/smart-search/_internal/wcwidth/py.typed +0 -0
  79. package/runtime/smart-search/package.json +49 -0
  80. package/runtime/smart-search/smart-search +0 -0
  81. package/README.md +0 -3
@@ -0,0 +1,955 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>smart-search</title>
7
+ <link rel="icon" type="image/png" data-smart-search-icon href="data:image/png;base64,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">
8
+ <style>
9
+ :root {
10
+ --bg: #f6f7f9; --panel: #ffffff; --ink: #1b1f24; --muted: #646e7a;
11
+ --line: #e2e6ea; --accent: #2f6feb; --ok: #1a7f46; --warn: #a3671a; --bad: #c0392b;
12
+ --chip: #eef1f5; --shadow: 0 1px 2px rgba(16,22,26,.06), 0 4px 16px rgba(16,22,26,.05);
13
+ }
14
+ @media (prefers-color-scheme: dark) {
15
+ :root {
16
+ --bg: #14171a; --panel: #1c2025; --ink: #e7eaee; --muted: #98a2ad;
17
+ --line: #2a3039; --accent: #6ea0ff; --ok: #4ec27f; --warn: #d7a34a; --bad: #f0776a;
18
+ --chip: #252b33; --shadow: none;
19
+ }
20
+ }
21
+ * { box-sizing: border-box; }
22
+ body {
23
+ margin: 0; background: var(--bg); color: var(--ink);
24
+ font: 15px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
25
+ "Hiragino Sans GB", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
26
+ }
27
+ code, pre, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
28
+ .layout { display: grid; grid-template-columns: 232px 1fr; min-height: 100vh; }
29
+ nav {
30
+ background: var(--panel); border-right: 1px solid var(--line);
31
+ padding: 20px 12px; position: sticky; top: 0; height: 100vh; overflow-y: auto;
32
+ }
33
+ nav h1 { font-size: 15px; margin: 0 0 4px 8px; letter-spacing: .2px; display: flex; align-items: center; gap: 8px; }
34
+ nav h1 img { width: 36px; height: 36px; padding: 2px; background: white; border-radius: 6px; }
35
+ nav .sub { font-size: 12px; color: var(--muted); margin: 0 0 18px 8px; }
36
+ nav button {
37
+ display: block; width: 100%; text-align: left; background: none; border: 0;
38
+ padding: 8px 10px; border-radius: 7px; color: var(--ink); cursor: pointer;
39
+ font: inherit; font-size: 14px; margin-bottom: 2px;
40
+ }
41
+ nav button:hover { background: var(--chip); }
42
+ nav button[aria-current="true"] { background: var(--accent); color: #fff; }
43
+ main { padding: 28px 32px 80px; max-width: 900px; }
44
+ h2 { font-size: 20px; margin: 0 0 6px; }
45
+ .blurb { color: var(--muted); margin: 0 0 22px; font-size: 14px; }
46
+ .card {
47
+ background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
48
+ padding: 16px 18px; margin-bottom: 14px; box-shadow: var(--shadow);
49
+ }
50
+ .card h3 { margin: 0 0 2px; font-size: 15px; display: flex; align-items: center; gap: 8px; }
51
+ .card .note { color: var(--muted); font-size: 13px; margin: 0 0 12px; }
52
+ .card .provider-guidance {
53
+ color: var(--muted); font-size: 12px; margin: 0 0 12px; padding: 8px 10px;
54
+ border-left: 2px solid var(--accent); background: var(--bg); border-radius: 4px;
55
+ }
56
+ .banner { border-left: 3px solid var(--accent); }
57
+ .banner.ok { border-left-color: var(--ok); }
58
+ .banner.bad { border-left-color: var(--bad); }
59
+ .pill {
60
+ display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px;
61
+ background: var(--chip); color: var(--muted); white-space: nowrap;
62
+ }
63
+ .pill:empty { display: none; }
64
+ .pill.ok { background: rgba(26,127,70,.14); color: var(--ok); }
65
+ .pill.bad { background: rgba(192,57,43,.14); color: var(--bad); }
66
+ .pill.warn { background: rgba(163,103,26,.16); color: var(--warn); }
67
+ .row {
68
+ display: grid; grid-template-columns: 200px 1fr auto; gap: 10px;
69
+ align-items: start; padding: 9px 0; border-top: 1px solid var(--line);
70
+ }
71
+ .row:first-of-type { border-top: 0; }
72
+ .row label { font-size: 14px; }
73
+ .row .help { display: block; color: var(--muted); font-size: 12px; margin-top: 2px; }
74
+ .row .val { font-size: 13px; word-break: break-all; }
75
+ .row .val .unset { color: var(--muted); }
76
+ button.act {
77
+ background: var(--accent); color: #fff; border: 0; border-radius: 7px;
78
+ padding: 6px 13px; font: inherit; font-size: 13px; cursor: pointer;
79
+ }
80
+ button.act.ghost { background: var(--chip); color: var(--ink); }
81
+ button.act[disabled] { opacity: .5; cursor: not-allowed; }
82
+ table { border-collapse: collapse; width: 100%; font-size: 13px; }
83
+ th, td { text-align: left; padding: 7px 10px 7px 0; border-top: 1px solid var(--line); vertical-align: top; }
84
+ th { color: var(--muted); font-weight: 600; border-top: 0; }
85
+ input[type="text"], input[type="password"], select {
86
+ width: 100%; padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px;
87
+ background: var(--bg); color: var(--ink); font: inherit; font-size: 13px;
88
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
89
+ }
90
+ input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
91
+ input[disabled], select[disabled] { opacity: .6; cursor: not-allowed; }
92
+ input.dirty, select.dirty { border-color: var(--accent); }
93
+ .savebar {
94
+ position: sticky; bottom: 0; margin: 20px -32px -80px; padding: 12px 32px;
95
+ background: var(--panel); border-top: 1px solid var(--line);
96
+ display: flex; align-items: center; gap: 12px; z-index: 5;
97
+ }
98
+ .savebar .grow { flex: 1; font-size: 13px; color: var(--muted); }
99
+ .steps { counter-reset: step; }
100
+ .step > h3::before {
101
+ counter-increment: step; content: counter(step);
102
+ display: inline-flex; align-items: center; justify-content: center;
103
+ width: 20px; height: 20px; border-radius: 50%; background: var(--accent);
104
+ color: #fff; font-size: 12px; margin-right: 2px;
105
+ }
106
+ .step.done > h3::before { background: var(--ok); content: "✓"; }
107
+ details.more { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
108
+ details.more > summary {
109
+ cursor: pointer; color: var(--muted); font-size: 13px; list-style: none;
110
+ padding: 2px 0; user-select: none;
111
+ }
112
+ details.more > summary::-webkit-details-marker { display: none; }
113
+ details.more > summary::before { content: "▸ "; }
114
+ details.more[open] > summary::before { content: "▾ "; }
115
+ .links { display: flex; gap: 10px; margin-top: 4px; flex-wrap: wrap; }
116
+ .links a {
117
+ color: var(--accent); font-size: 12px; text-decoration: none;
118
+ border-bottom: 1px dotted var(--accent);
119
+ }
120
+ .links a:hover { border-bottom-style: solid; }
121
+ .chips { display: flex; flex-wrap: wrap; gap: 6px; }
122
+ .chip {
123
+ border: 1px solid var(--line); background: var(--bg); color: var(--muted);
124
+ border-radius: 999px; padding: 3px 11px; font: inherit; font-size: 12px; cursor: pointer;
125
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
126
+ }
127
+ .chip.on { background: var(--accent); border-color: var(--accent); color: #fff; }
128
+ .chip[disabled] { cursor: not-allowed; opacity: .5; }
129
+ .chips.dirty .chip.on { outline: 1px solid var(--accent); }
130
+ .chain { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
131
+ .chain .arrow { color: var(--muted); font-size: 12px; }
132
+ .cost {
133
+ font-size: 12px; color: var(--warn); background: rgba(163,103,26,.1);
134
+ border-radius: 6px; padding: 7px 10px; margin-bottom: 12px;
135
+ }
136
+ .toast {
137
+ position: fixed; right: 20px; bottom: 20px; background: var(--panel);
138
+ border: 1px solid var(--line); border-left: 3px solid var(--accent);
139
+ border-radius: 8px; padding: 10px 14px; box-shadow: var(--shadow);
140
+ font-size: 13px; max-width: 380px; opacity: 0; transition: opacity .15s; pointer-events: none;
141
+ }
142
+ .toast.show { opacity: 1; }
143
+ .toast.bad { border-left-color: var(--bad); }
144
+ .lang { float: right; }
145
+ .lang button { display: inline; width: auto; padding: 2px 8px; font-size: 12px; }
146
+ @media (max-width: 720px) {
147
+ .layout { grid-template-columns: 1fr; }
148
+ nav { position: static; height: auto; }
149
+ main { padding: 20px 16px 60px; }
150
+ .row { grid-template-columns: 1fr; }
151
+ .savebar { margin: 20px -16px -60px; padding: 12px 16px; flex-wrap: wrap; }
152
+ }
153
+ </style>
154
+ </head>
155
+ <body>
156
+ <div class="layout">
157
+ <nav>
158
+ <h1><img data-smart-search-icon src="data:image/png;base64,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" alt="">smart-search</h1>
159
+ <p class="sub" id="subtitle"></p>
160
+ <div id="nav"></div>
161
+ <p class="sub" style="margin-top:18px">
162
+ <span class="lang">
163
+ <button class="act ghost" id="langToggle" type="button">EN / 中文</button>
164
+ </span>
165
+ </p>
166
+ </nav>
167
+ <main id="main"></main>
168
+ </div>
169
+ <div class="toast" id="toast"></div>
170
+
171
+ <script nonce="__SS_UI_NONCE__">
172
+ (function () {
173
+ "use strict";
174
+ var TOKEN = "__SS_UI_TOKEN__";
175
+ var lang = "__SS_UI_LANG__" === "en" ? "en" : "zh";
176
+ var state = null;
177
+ var current = "status";
178
+ var testing = {};
179
+
180
+ var T = {
181
+ zh: {
182
+ sub: "本地配置页", ready: "最低配置已满足", notReady: "最低配置还差一点",
183
+ missing: "还缺", configFile: "配置文件", source: "来源",
184
+ env: "环境变量", file: "配置文件", def: "默认值",
185
+ unset: "未配置", test: "测试", testing: "测试中…", testAll: "全部测试",
186
+ cost: "每点一次「测试」都会向对应服务商发一次真实请求,可能计费。",
187
+ capabilities: "能力", chain: "兜底顺序", configured: "已配置",
188
+ cooldown: "冷却中", provider: "数据源", status: "状态", message: "说明",
189
+ remaining: "剩余", reset: "解除冷却", resetAll: "全部解除",
190
+ noCooldown: "当前没有数据源在冷却。", envLocked: "由环境变量接管,改这里不生效",
191
+ doctor: "运行完整体检", doctorCost: "体检会一次性调用所有已配置的服务商。",
192
+ confirmAll: "这会对所有已配置的数据源各发一次真实请求,继续?",
193
+ skills: "已安装", skillsNot: "未安装", refresh: "刷新",
194
+ skillsCurrent: "已是当前版本", skillsStale: "可更新", skillsExtra: "已安装(含额外文件)",
195
+ required: "必需", optional: "可选", routingConnection: "路由与连接", jevMode: "Jev 自动选路", localMode: "本地选路",
196
+ missingOne: "缺", optionalOne: "未配置", more: "更多设置",
197
+ save: "保存", discard: "放弃修改", clear: "清除", saved: "已保存",
198
+ unsaved: "有 {n} 处未保存", envHint: "想改的话先在终端执行",
199
+ keepSecret: "留空表示不改动", getKey: "去申请 key", readDocs: "文档",
200
+ stepMain: "模型联网检索",
201
+ stepDocs: "文档与网页检索", stepFetch: "网页读取(深入搜索推荐)",
202
+ pickOne: "按用途选择渠道,不要求三类全部配置。", installSkill: "安装 / 更新",
203
+ runRoute: "看它走哪些能力(免费)", runSearch: "跑一次真实搜索",
204
+ queryPlaceholder: "比如:React useEffect 清理函数怎么写",
205
+ running: "运行中…", searchCost: "真实搜索会调用选中的检索渠道,配置 Jev 时还会进行一次选路判断,可能计费。确定继续?",
206
+ needQuery: "先输入一个问题。", result: "结果", chain: "兜底顺序",
207
+ chainNote: "Jev 按问题选择渠道;这里列出各类能力的本地回退顺序,绿色表示已配置。",
208
+ },
209
+ en: {
210
+ sub: "local config", ready: "Minimum profile met", notReady: "Minimum profile not met",
211
+ missing: "Missing", configFile: "Config file", source: "Source",
212
+ env: "environment", file: "config file", def: "default",
213
+ unset: "not set", test: "Test", testing: "Testing…", testAll: "Test all",
214
+ cost: "Every Test makes one real request to that provider and may cost money.",
215
+ capabilities: "Capabilities", chain: "Fallback order", configured: "Configured",
216
+ cooldown: "Cooling down", provider: "Provider", status: "Status", message: "Message",
217
+ remaining: "Remaining", reset: "Clear cooldown", resetAll: "Clear all",
218
+ noCooldown: "No provider is cooling down.", envLocked: "Taken over by an environment variable",
219
+ doctor: "Run full doctor", doctorCost: "Doctor calls every configured provider at once.",
220
+ confirmAll: "This makes one real request to every configured provider. Continue?",
221
+ skills: "installed", skillsNot: "not installed", refresh: "Refresh",
222
+ skillsCurrent: "up to date", skillsStale: "update available", skillsExtra: "installed (extra files)",
223
+ required: "required", optional: "optional", routingConnection: "Routing and connection", jevMode: "Jev routing", localMode: "Local routing",
224
+ missingOne: "missing", optionalOne: "not set", more: "More settings",
225
+ save: "Save", discard: "Discard", clear: "Clear", saved: "Saved",
226
+ unsaved: "{n} unsaved change(s)", envHint: "To change it, first run",
227
+ keepSecret: "Leave empty to keep the current value", getKey: "Get a key", readDocs: "Docs",
228
+ stepMain: "Model-backed retrieval",
229
+ stepDocs: "Documentation and web retrieval", stepFetch: "Page reading (recommended for research)",
230
+ pickOne: "Choose channels for your task; all three groups are not required.", installSkill: "Install / update",
231
+ runRoute: "Show which capabilities (free)", runSearch: "Run a real search",
232
+ queryPlaceholder: "e.g. how do I write a React useEffect cleanup function",
233
+ running: "Running…", searchCost: "A real search calls the selected channel and, when configured, Jev once for routing. Charges may apply. Continue?",
234
+ needQuery: "Type a question first.", result: "Result", chain: "Fallback order",
235
+ chainNote: "Jev selects for the question. These are local fallback orders; green channels are configured.",
236
+ }
237
+ };
238
+ function t(key) { return T[lang][key] || key; }
239
+ function L(obj, base) { return obj[base + "_" + lang] || obj[base + "_en"] || ""; }
240
+
241
+ function el(tag, attrs, kids) {
242
+ var node = document.createElement(tag);
243
+ attrs = attrs || {};
244
+ Object.keys(attrs).forEach(function (name) {
245
+ if (name === "class") node.className = attrs[name];
246
+ else if (name === "text") node.textContent = attrs[name];
247
+ else if (name.indexOf("on") === 0) node.addEventListener(name.slice(2), attrs[name]);
248
+ else if (attrs[name] !== null && attrs[name] !== undefined) node.setAttribute(name, attrs[name]);
249
+ });
250
+ (kids || []).forEach(function (kid) { if (kid) node.appendChild(kid); });
251
+ return node;
252
+ }
253
+
254
+ var toastTimer = null;
255
+ function toast(message, bad) {
256
+ var node = document.getElementById("toast");
257
+ node.textContent = message;
258
+ node.className = "toast show" + (bad ? " bad" : "");
259
+ clearTimeout(toastTimer);
260
+ toastTimer = setTimeout(function () { node.className = "toast"; }, 4200);
261
+ }
262
+
263
+ function api(method, path, body) {
264
+ return fetch(path, {
265
+ method: method,
266
+ headers: body
267
+ ? { "X-Smart-Search-Token": TOKEN, "Content-Type": "application/json" }
268
+ : { "X-Smart-Search-Token": TOKEN },
269
+ body: body ? JSON.stringify(body) : undefined
270
+ }).then(function (response) {
271
+ return response.json().catch(function () { return { ok: false, error: "HTTP " + response.status }; });
272
+ });
273
+ }
274
+
275
+ function sourceLabel(source) {
276
+ if (source === "environment") return t("env");
277
+ if (source === "config_file") return t("file");
278
+ return t("def");
279
+ }
280
+
281
+ function seconds(value) {
282
+ var n = Number(value || 0);
283
+ if (n <= 0) return "-";
284
+ if (n < 60) return Math.round(n) + "s";
285
+ if (n < 3600) return Math.round(n / 60) + "m";
286
+ return (n / 3600).toFixed(1) + "h";
287
+ }
288
+
289
+ // ---- views ------------------------------------------------------------
290
+ function viewStatus() {
291
+ var main = document.getElementById("main");
292
+ var minimum = state.minimum_profile || {};
293
+ var ok = !!minimum.ok;
294
+ main.appendChild(el("h2", { text: L(sectionById("status"), "label") }));
295
+ main.appendChild(el("p", { class: "blurb", text: L(sectionById("status"), "blurb") }));
296
+
297
+ var missing = (minimum.missing || []).join(", ");
298
+ main.appendChild(el("div", { class: "card banner " + (ok ? "ok" : "bad") }, [
299
+ el("h3", {}, [
300
+ el("span", { text: ok ? t("ready") : t("notReady") }),
301
+ el("span", { class: "pill " + (ok ? "ok" : "bad"), text: ok ? "OK" : "FAIL" })
302
+ ]),
303
+ missing ? el("p", { class: "note", text: t("missing") + ": " + missing }) : null,
304
+ el("p", { class: "note mono", text: t("configFile") + ": " + (state.config_path || {}).config_file })
305
+ ]));
306
+
307
+ var caps = state.capability_status || {};
308
+ var requiredCaps = minimum.required || [];
309
+ var rows = Object.keys(caps).map(function (name) {
310
+ var item = caps[name] || {};
311
+ var configured = (item.configured || []).join(", ") || "-";
312
+ var needed = requiredCaps.indexOf(name) >= 0;
313
+ var cls = item.ok ? "ok" : (needed ? "bad" : "");
314
+ var label = item.ok ? "OK" : (needed ? t("missingOne") : t("optionalOne"));
315
+ return el("tr", {}, [
316
+ el("td", {}, [
317
+ el("span", { class: "mono", text: name }),
318
+ needed ? el("span", { class: "pill", text: t("required") }) : null
319
+ ]),
320
+ el("td", {}, [el("span", { class: "pill " + cls, text: label })]),
321
+ el("td", { class: "mono", text: configured }),
322
+ el("td", { class: "mono", text: (item.fallback_chain || []).join(" → ") })
323
+ ]);
324
+ });
325
+ main.appendChild(el("div", { class: "card" }, [
326
+ el("h3", { text: t("capabilities") }),
327
+ el("table", {}, [
328
+ el("thead", {}, [el("tr", {}, [
329
+ el("th", { text: t("capabilities") }), el("th", { text: t("status") }),
330
+ el("th", { text: t("configured") }), el("th", { text: t("chain") })
331
+ ])]),
332
+ el("tbody", {}, rows)
333
+ ])
334
+ ]));
335
+
336
+ main.appendChild(chainCard());
337
+ main.appendChild(cooldownCard());
338
+ }
339
+
340
+ function cooldownCard() {
341
+ var health = state.provider_health || {};
342
+ var cooling = (health.providers || []).filter(function (item) { return item.state === "cooldown"; });
343
+ var kids = [el("h3", { text: t("cooldown") })];
344
+ if (!cooling.length) {
345
+ kids.push(el("p", { class: "note", text: t("noCooldown") }));
346
+ return el("div", { class: "card" }, kids);
347
+ }
348
+ kids.push(el("table", {}, [
349
+ el("thead", {}, [el("tr", {}, [
350
+ el("th", { text: t("provider") }), el("th", { text: t("remaining") }),
351
+ el("th", { text: t("message") }), el("th", {})
352
+ ])]),
353
+ el("tbody", {}, cooling.map(function (item) {
354
+ return el("tr", {}, [
355
+ el("td", { class: "mono", text: item.provider }),
356
+ el("td", { text: seconds(item.cooldown_remaining_seconds) }),
357
+ el("td", { text: item.error || item.error_type || "-" }),
358
+ el("td", {}, [el("button", {
359
+ class: "act ghost", type: "button", text: t("reset"),
360
+ onclick: function () { resetCooldown([item.provider]); }
361
+ })])
362
+ ]);
363
+ }))
364
+ ]));
365
+ kids.push(el("p", { class: "note", style: "margin-top:12px" }, [
366
+ el("button", {
367
+ class: "act ghost", type: "button", text: t("resetAll"),
368
+ onclick: function () { resetCooldown(null); }
369
+ })
370
+ ]));
371
+ return el("div", { class: "card" }, kids);
372
+ }
373
+
374
+ function resetCooldown(providers) {
375
+ api("POST", "/api/providers/reset", { providers: providers }).then(function (data) {
376
+ if (!data.ok) { toast(data.error || "failed", true); return; }
377
+ toast((data.cleared || []).join(", ") || t("noCooldown"));
378
+ refresh();
379
+ });
380
+ }
381
+
382
+ function viewProviders() {
383
+ var main = document.getElementById("main");
384
+ var section = sectionById("providers");
385
+ main.appendChild(el("h2", { text: L(section, "label") }));
386
+ main.appendChild(el("p", { class: "blurb", text: L(section, "blurb") }));
387
+ main.appendChild(el("div", { class: "cost", text: t("cost") }));
388
+
389
+ var groups = {};
390
+ var order = [];
391
+ state.metadata.fields.forEach(function (field) {
392
+ if (field.section !== "providers") return;
393
+ var name = field.provider || "other";
394
+ if (!groups[name]) { groups[name] = []; order.push(name); }
395
+ groups[name].push(field);
396
+ });
397
+ order.forEach(function (provider) {
398
+ main.appendChild(providerCard(provider, groups[provider]));
399
+ });
400
+ main.appendChild(el("div", { class: "card" }, [
401
+ el("h3", { text: t("doctor") }),
402
+ el("p", { class: "note", text: t("doctorCost") }),
403
+ el("button", { class: "act ghost", type: "button", text: t("doctor"), onclick: runDoctor })
404
+ ]));
405
+ main.appendChild(saveBar());
406
+ updateSaveBar();
407
+ }
408
+
409
+ function providerCard(provider, fields) {
410
+ var health = ((state.provider_health || {}).providers || []).filter(function (item) {
411
+ return item.provider === provider;
412
+ })[0];
413
+ var probe = (state.probe_kinds || {})[provider] || "";
414
+ var testable = !!probe;
415
+ var badge = null;
416
+ if (health && health.state === "cooldown") badge = el("span", { class: "pill bad", text: t("cooldown") });
417
+ else if (probe === "presence") badge = el("span", { class: "pill warn", text: "presence" });
418
+
419
+ var kids = [
420
+ el("h3", {}, [
421
+ el("span", { class: "mono", text: provider }),
422
+ badge,
423
+ el("span", { class: "pill", id: "res-" + provider, text: "" })
424
+ ])
425
+ ];
426
+ var profile = (state.provider_profiles || {})[provider] || {};
427
+ var descriptions = profile.descriptions || {};
428
+ function localizedDescriptions(prefix) {
429
+ return ["search", "fetch", "site_map", "synthesis"].map(function (operation) {
430
+ var translations = descriptions[operation] || {};
431
+ return translations[prefix + lang] || translations[prefix + "en"] || "";
432
+ }).filter(Boolean).join(" ");
433
+ }
434
+ var intro = localizedDescriptions("");
435
+ var guidance = localizedDescriptions("guidance_");
436
+ if (intro) kids.push(el("p", { class: "note", text: intro }));
437
+ if (guidance) kids.push(el("p", { class: "provider-guidance", text: guidance }));
438
+ var upfront = fields.filter(function (f) { return f.tier !== "advanced"; });
439
+ var advanced = fields.filter(function (f) { return f.tier === "advanced"; });
440
+ if (!upfront.length) { upfront = advanced; advanced = []; }
441
+ upfront.forEach(function (field) { kids.push(editableRow(field)); });
442
+ if (advanced.length) {
443
+ kids.push(el("details", { class: "more" }, [
444
+ el("summary", { text: t("more") + " (" + advanced.length + ")" })
445
+ ].concat(advanced.map(editableRow))));
446
+ }
447
+ if (testable) {
448
+ kids.push(el("p", { class: "note", style: "margin-top:12px;margin-bottom:0" }, [
449
+ el("button", {
450
+ class: "act", type: "button", id: "btn-" + provider, text: t("test"),
451
+ onclick: function () { runTest(provider); }
452
+ })
453
+ ]));
454
+ }
455
+ return el("div", { class: "card" }, kids);
456
+ }
457
+
458
+ var pending = {}; // KEY -> new string value ("" means clear)
459
+ var previewTimer = null;
460
+
461
+ function isDirty() { return Object.keys(pending).length > 0; }
462
+
463
+ function effectiveValues() {
464
+ // Truthiness is all capability_status_from_values looks at, so a masked
465
+ // placeholder stands in fine for a secret we are not allowed to read back.
466
+ var merged = {};
467
+ Object.keys(state.values || {}).forEach(function (key) { merged[key] = state.values[key]; });
468
+ Object.keys(pending).forEach(function (key) { merged[key] = pending[key]; });
469
+ return merged;
470
+ }
471
+
472
+ function markDirty(field, input, value) {
473
+ var saved = (state.values || {})[field.key] || "";
474
+ if (value === saved || (value === "" && (!saved || field.kind === "secret"))) delete pending[field.key];
475
+ else pending[field.key] = value;
476
+ input.className = pending.hasOwnProperty(field.key) ? "dirty" : "";
477
+ updateSaveBar();
478
+ clearTimeout(previewTimer);
479
+ previewTimer = setTimeout(runPreview, 350);
480
+ }
481
+
482
+ function runPreview() {
483
+ api("POST", "/api/preview", { values: effectiveValues() }).then(function (data) {
484
+ if (!data || !data.ok) return;
485
+ var node = document.getElementById("previewMeter");
486
+ if (!node) return;
487
+ node.className = "pill " + (data.minimum_profile_ok ? "ok" : "bad");
488
+ node.textContent = data.minimum_profile_ok
489
+ ? t("ready")
490
+ : t("notReady") + (data.missing.length ? " · " + data.missing.join(", ") : "");
491
+ document.querySelectorAll("[data-step]").forEach(function (card) {
492
+ var name = card.getAttribute("data-step");
493
+ var ok = (data.capability_status[name] || {}).ok;
494
+ card.className = "card step" + (ok ? " done" : "");
495
+ });
496
+ });
497
+ }
498
+
499
+ function updateSaveBar() {
500
+ var bar = document.getElementById("saveBar");
501
+ if (!bar) return;
502
+ var count = Object.keys(pending).length;
503
+ bar.style.display = count ? "flex" : "none";
504
+ var label = document.getElementById("saveCount");
505
+ if (label) label.textContent = count ? t("unsaved").replace("{n}", count) : "";
506
+ }
507
+
508
+ function saveChanges() {
509
+ if (!isDirty()) return;
510
+ var setValues = {}, unsetKeys = [];
511
+ Object.keys(pending).forEach(function (key) {
512
+ if (pending[key] === "") unsetKeys.push(key);
513
+ else setValues[key] = pending[key];
514
+ });
515
+ var button = document.getElementById("saveBtn");
516
+ if (button) button.disabled = true;
517
+ api("POST", "/api/config", { set: setValues, unset: unsetKeys, revision: state.revision }).then(function (data) {
518
+ if (!data.ok) { toast(data.error || "failed", true); return; }
519
+ pending = {};
520
+ if (data.status) { Object.keys(data.status).forEach(function (k) { state[k] = data.status[k]; }); }
521
+ toast(t("saved"));
522
+ render();
523
+ }).catch(function (e) {
524
+ toast(String(e), true);
525
+ }).then(function () {
526
+ if (button) button.disabled = false;
527
+ });
528
+ }
529
+
530
+ function saveBar() {
531
+ return el("div", { class: "savebar", id: "saveBar", style: "display:none" }, [
532
+ el("span", { class: "grow", id: "saveCount" }),
533
+ el("button", {
534
+ class: "act ghost", type: "button", text: t("discard"),
535
+ onclick: function () { pending = {}; render(); }
536
+ }),
537
+ el("button", { class: "act", type: "button", id: "saveBtn", text: t("save"), onclick: saveChanges })
538
+ ]);
539
+ }
540
+
541
+ function fieldInput(field) {
542
+ var source = (state.sources || {})[field.key] || "default";
543
+ var saved = (state.values || {})[field.key] || "";
544
+ var locked = source === "environment";
545
+ var value = pending.hasOwnProperty(field.key) ? pending[field.key] : saved;
546
+
547
+ if (field.kind === "enum" || field.kind === "bool") {
548
+ var options = field.kind === "bool" ? ["true", "false"] : field.choices;
549
+ var select = el("select", locked ? { disabled: "disabled" } : {});
550
+ select.appendChild(el("option", { value: "", text: "(" + t("def") + (field.default ? " " + field.default : "") + ")" }));
551
+ options.forEach(function (option) {
552
+ var label = field.key === "SMART_SEARCH_INTENT_ROUTER" ? t(option === "jev" ? "jevMode" : "localMode") : option;
553
+ select.appendChild(el("option", { value: option, text: label }));
554
+ });
555
+ select.value = value;
556
+ if (!locked) select.addEventListener("change", function () {
557
+ markDirty(field, select, select.value);
558
+ if (field.key === "SMART_SEARCH_INTENT_ROUTER") render();
559
+ });
560
+ if (pending.hasOwnProperty(field.key)) select.className = "dirty";
561
+ return select;
562
+ }
563
+
564
+ if (field.kind === "csv" && field.key.indexOf("RESEARCH_") >= 0) {
565
+ var chosen = {};
566
+ value.split(",").forEach(function (item) {
567
+ item = item.trim();
568
+ if (item) chosen[item] = true;
569
+ });
570
+ var wrap = el("div", { class: "chips" });
571
+ Object.keys(state.provider_profiles || {}).sort().forEach(function (provider) {
572
+ var on = !!chosen[provider];
573
+ var chip = el("button", {
574
+ type: "button", class: "chip" + (on ? " on" : ""), text: provider,
575
+ disabled: locked ? "disabled" : null
576
+ });
577
+ chip.addEventListener("click", function () {
578
+ if (locked) return;
579
+ if (chosen[provider]) delete chosen[provider]; else chosen[provider] = true;
580
+ chip.className = "chip" + (chosen[provider] ? " on" : "");
581
+ markDirty(field, wrap, Object.keys(chosen).sort().join(","));
582
+ });
583
+ wrap.appendChild(chip);
584
+ });
585
+ return wrap;
586
+ }
587
+
588
+ var secret = field.kind === "secret";
589
+ var input = el("input", {
590
+ type: secret ? "password" : "text",
591
+ autocomplete: secret ? "new-password" : "off",
592
+ spellcheck: "false",
593
+ placeholder: secret && saved ? saved : (field.placeholder || field.default || "")
594
+ });
595
+ // A saved secret is only ever shown masked; leaving the box empty keeps it.
596
+ input.value = secret ? (pending.hasOwnProperty(field.key) ? pending[field.key] : "") : value;
597
+ if (locked) { input.disabled = true; input.value = secret ? "" : saved; }
598
+ else input.addEventListener("input", function () { markDirty(field, input, input.value); });
599
+ if (pending.hasOwnProperty(field.key)) input.className = "dirty";
600
+ return input;
601
+ }
602
+
603
+ function editableRow(field) {
604
+ var source = (state.sources || {})[field.key] || "default";
605
+ var saved = (state.values || {})[field.key] || "";
606
+ var locked = source === "environment";
607
+ var input = fieldInput(field);
608
+ input.setAttribute("aria-label", L(field, "label"));
609
+ var side = [el("span", { class: "pill", text: sourceLabel(source) })];
610
+ if (locked) side.push(el("span", { class: "pill warn", text: t("envLocked") }));
611
+ else if (saved) side.push(el("button", {
612
+ class: "act ghost", type: "button", text: t("clear"),
613
+ onclick: function () {
614
+ pending[field.key] = "";
615
+ render();
616
+ clearTimeout(previewTimer);
617
+ previewTimer = setTimeout(runPreview, 50);
618
+ }
619
+ }));
620
+ var links = null;
621
+ if (field.key_url || field.docs_url) {
622
+ links = el("div", { class: "links" }, [
623
+ field.key_url ? el("a", {
624
+ href: field.key_url, target: "_blank", rel: "noopener noreferrer",
625
+ text: t("getKey") + " ↗"
626
+ }) : null,
627
+ field.docs_url ? el("a", {
628
+ href: field.docs_url, target: "_blank", rel: "noopener noreferrer",
629
+ text: t("readDocs") + " ↗"
630
+ }) : null
631
+ ]);
632
+ }
633
+ return el("div", { class: "row" }, [
634
+ el("div", {}, [
635
+ el("label", { text: L(field, "label") }),
636
+ el("span", { class: "help mono", text: field.key }),
637
+ L(field, "help") ? el("span", { class: "help", text: L(field, "help") }) : null,
638
+ locked ? el("span", { class: "help", text: t("envHint") + " unset " + field.key }) : null,
639
+ links
640
+ ]),
641
+ el("div", {}, [input]),
642
+ el("div", {}, side)
643
+ ]);
644
+ }
645
+
646
+ function runTest(provider) {
647
+ if (testing[provider]) return;
648
+ testing[provider] = true;
649
+ var button = document.getElementById("btn-" + provider);
650
+ var result = document.getElementById("res-" + provider);
651
+ if (button) { button.disabled = true; button.textContent = t("testing"); }
652
+ if (result) { result.className = "pill"; result.textContent = "…"; }
653
+ var shared = (state.probe_kinds[provider] || "").replace(/^shared:/, "");
654
+ var overrides = {};
655
+ state.metadata.fields.forEach(function (field) {
656
+ if ((field.provider === provider || field.provider === shared) && pending.hasOwnProperty(field.key)) {
657
+ overrides[field.key] = pending[field.key];
658
+ }
659
+ });
660
+ api("POST", "/api/test", { provider: provider, overrides: overrides }).then(function (data) {
661
+ var good = data.status === "ok";
662
+ var neutral = data.status === "configured" || data.status === "not_configured";
663
+ if (result) {
664
+ result.className = "pill " + (good ? "ok" : (neutral ? "warn" : "bad"));
665
+ result.textContent = data.status + (data.response_time_ms ? " · " + Math.round(data.response_time_ms) + "ms" : "");
666
+ }
667
+ toast(provider + ": " + (data.message || data.status), !good && !neutral);
668
+ return api("GET", "/api/status");
669
+ }).then(function (fresh) {
670
+ if (fresh && fresh.ok) { Object.keys(fresh).forEach(function (k) { state[k] = fresh[k]; }); }
671
+ }).catch(function (e) {
672
+ toast(String(e), true);
673
+ }).then(function () {
674
+ testing[provider] = false;
675
+ if (button) { button.disabled = false; button.textContent = t("test"); }
676
+ });
677
+ }
678
+
679
+ function runDoctor() {
680
+ if (!window.confirm(t("confirmAll"))) return;
681
+ toast(t("testing"));
682
+ api("POST", "/api/doctor").then(function (data) {
683
+ toast(data.ok ? "doctor: OK" : "doctor: " + (data.error || "FAIL"), !data.ok);
684
+ refresh();
685
+ });
686
+ }
687
+
688
+ var STEPS = [
689
+ { capability: "main_search", title: "stepMain",
690
+ providers: ["xai-responses", "openai-compatible"] },
691
+ { capability: "docs_search", title: "stepDocs",
692
+ providers: ["context7", "exa"] },
693
+ { capability: "web_fetch", title: "stepFetch",
694
+ providers: ["tavily", "jina", "zhipu-mcp-reader", "zhipu-mcp", "firecrawl"] }
695
+ ];
696
+
697
+ function viewGettingStarted() {
698
+ var main = document.getElementById("main");
699
+ var section = sectionById("getting_started");
700
+ main.appendChild(el("h2", {}, [
701
+ el("span", { text: L(section, "label") + " " }),
702
+ el("span", { class: "pill", id: "previewMeter", text: "" })
703
+ ]));
704
+ main.appendChild(el("p", { class: "blurb", text: L(section, "blurb") }));
705
+
706
+ var wrap = el("div", { class: "steps" });
707
+ STEPS.forEach(function (step) {
708
+ var fields = state.metadata.fields.filter(function (field) {
709
+ return step.providers.indexOf(field.provider) >= 0
710
+ && field.capabilities.indexOf(step.capability) >= 0;
711
+ }).sort(function (a, b) {
712
+ return step.providers.indexOf(a.provider) - step.providers.indexOf(b.provider);
713
+ });
714
+ var essential = fields.filter(function (f) { return f.tier !== "advanced"; });
715
+ var advanced = fields.filter(function (f) { return f.tier === "advanced"; });
716
+ var ok = ((state.capability_status || {})[step.capability] || {}).ok;
717
+ var kids = [
718
+ el("h3", {}, [
719
+ el("span", { text: " " + t(step.title) }),
720
+ el("span", { class: "mono pill", text: step.capability })
721
+ ]),
722
+ el("p", { class: "note", text: t("pickOne") })
723
+ ];
724
+ essential.forEach(function (field) { kids.push(editableRow(field)); });
725
+ if (advanced.length) {
726
+ kids.push(el("details", { class: "more" }, [
727
+ el("summary", { text: t("more") + " (" + advanced.length + ")" })
728
+ ].concat(advanced.map(editableRow))));
729
+ }
730
+ step.providers.filter(function (provider) { return state.probe_kinds[provider]; }).forEach(function (provider) {
731
+ kids.push(el("p", { class: "note" }, [
732
+ el("button", { class: "act ghost", type: "button", id: "btn-" + provider,
733
+ text: t("test") + " " + provider, onclick: function () { runTest(provider); } }),
734
+ el("span", { class: "pill", id: "res-" + provider })
735
+ ]));
736
+ });
737
+ wrap.appendChild(el("div", { class: "card step" + (ok ? " done" : ""), "data-step": step.capability }, kids));
738
+ });
739
+ main.appendChild(wrap);
740
+ main.appendChild(saveBar());
741
+ updateSaveBar();
742
+ runPreview();
743
+ }
744
+
745
+ function viewSection(id) {
746
+ var main = document.getElementById("main");
747
+ var section = sectionById(id);
748
+ main.appendChild(el("h2", { text: L(section, "label") }));
749
+ main.appendChild(el("p", { class: "blurb", text: L(section, "blurb") }));
750
+ var fields = state.metadata.fields.filter(function (field) { return field.section === id; });
751
+ if (id === "routing" && effectiveValues().SMART_SEARCH_INTENT_ROUTER === "local") {
752
+ fields = fields.filter(function (field) {
753
+ return field.key.indexOf("TYPESAFE_") !== 0 && field.key.indexOf("SMART_SEARCH_JEV_") !== 0;
754
+ });
755
+ }
756
+ var essential = fields.filter(function (f) { return f.tier === "essential"; });
757
+ var rest = fields.filter(function (f) { return f.tier !== "essential"; });
758
+ if (essential.length) {
759
+ main.appendChild(el("div", { class: "card" }, [el("h3", { text: t(id === "routing" ? "routingConnection" : "required") })].concat(
760
+ essential.map(editableRow))));
761
+ }
762
+ if (rest.length) {
763
+ main.appendChild(el("div", { class: "card" }, [
764
+ el("h3", { text: t("optional") }),
765
+ el("details", { class: "more", open: essential.length ? null : "open" }, [
766
+ el("summary", { text: t("more") + " (" + rest.length + ")" })
767
+ ].concat(rest.map(editableRow)))
768
+ ]));
769
+ }
770
+ main.appendChild(saveBar());
771
+ updateSaveBar();
772
+ }
773
+
774
+ function viewSkills() {
775
+ var main = document.getElementById("main");
776
+ var section = sectionById("skills");
777
+ main.appendChild(el("h2", { text: L(section, "label") }));
778
+ main.appendChild(el("p", { class: "blurb", text: L(section, "blurb") }));
779
+ var card = el("div", { class: "card" }, [el("p", { class: "note", text: "…" })]);
780
+ main.appendChild(card);
781
+ api("GET", "/api/skills?targets=" + encodeURIComponent(
782
+ state.skill_targets.map(function (item) { return item.id; }).join(",")
783
+ )).then(function (data) {
784
+ card.textContent = "";
785
+ if (!data.ok) { card.appendChild(el("p", { class: "note", text: data.error || "failed" })); return; }
786
+ card.appendChild(el("table", {}, [
787
+ el("thead", {}, [el("tr", {}, [
788
+ el("th", { text: t("provider") }), el("th", { text: t("status") }), el("th", { text: t("message") })
789
+ ])]),
790
+ el("tbody", {}, (data.targets || []).map(function (item) {
791
+ var installed = ["up_to_date", "stale", "extra_files"].indexOf(item.status) >= 0;
792
+ var statusLabel = { up_to_date: "skillsCurrent", stale: "skillsStale", extra_files: "skillsExtra" }[item.status];
793
+ return el("tr", {}, [
794
+ el("td", { text: item.label || item.target }),
795
+ el("td", {}, [el("span", {
796
+ class: "pill " + (installed ? "ok" : ""), text: statusLabel ? t(statusLabel) : t("skillsNot")
797
+ })]),
798
+ el("td", { class: "mono", text: item.status || "" })
799
+ ]);
800
+ }))
801
+ ]));
802
+ card.appendChild(el("p", { class: "note", style: "margin-top:12px;margin-bottom:0" }, [
803
+ el("button", {
804
+ class: "act", type: "button", text: t("installSkill"),
805
+ onclick: function (event) {
806
+ var button = event.target;
807
+ button.disabled = true;
808
+ api("POST", "/api/skills/install", {
809
+ targets: (data.targets || []).map(function (item) { return item.target; })
810
+ }).then(function (result) {
811
+ toast(result.ok ? t("saved") + " (" + (result.installed_count || 0) + ")"
812
+ : (result.error || "failed"), !result.ok);
813
+ render();
814
+ }).then(function () { button.disabled = false; });
815
+ }
816
+ })
817
+ ]));
818
+ });
819
+ }
820
+
821
+ function viewTryIt() {
822
+ var main = document.getElementById("main");
823
+ var section = sectionById("tryit");
824
+ main.appendChild(el("h2", { text: L(section, "label") }));
825
+ main.appendChild(el("p", { class: "blurb", text: L(section, "blurb") }));
826
+
827
+ var box = el("input", { type: "text", placeholder: t("queryPlaceholder"), spellcheck: "false" });
828
+ var output = el("pre", {
829
+ class: "mono", style: "white-space:pre-wrap;word-break:break-word;margin:0;font-size:12px"
830
+ });
831
+ var outputCard = el("div", { class: "card", style: "display:none" }, [
832
+ el("h3", { text: t("result") }), output
833
+ ]);
834
+
835
+ function run(command) {
836
+ var query = box.value.trim();
837
+ if (!query) { toast(t("needQuery"), true); return; }
838
+ if (command === "search" && !window.confirm(t("searchCost"))) return;
839
+ outputCard.style.display = "";
840
+ output.textContent = t("running");
841
+ api("POST", "/api/run", { command: command, query: query }).then(function (data) {
842
+ output.textContent = JSON.stringify(data, null, 2);
843
+ if (!data.ok) toast(data.error || "failed", true);
844
+ }).catch(function (e) { output.textContent = String(e); });
845
+ }
846
+
847
+ main.appendChild(el("div", { class: "card" }, [
848
+ el("div", { class: "row", style: "grid-template-columns:1fr" }, [el("div", {}, [box])]),
849
+ el("p", { class: "note", style: "margin:12px 0 0;display:flex;gap:8px;flex-wrap:wrap" }, [
850
+ el("button", { class: "act", type: "button", text: t("runRoute"), onclick: function () { run("route"); } }),
851
+ el("button", { class: "act ghost", type: "button", text: t("runSearch"), onclick: function () { run("search"); } })
852
+ ])
853
+ ]));
854
+ main.appendChild(outputCard);
855
+ box.addEventListener("keydown", function (event) { if (event.key === "Enter") run("route"); });
856
+ }
857
+
858
+ function chainCard() {
859
+ var chains = state.capability_chains || {};
860
+ var caps = state.capability_status || {};
861
+ var cooling = {};
862
+ ((state.provider_health || {}).providers || []).forEach(function (item) {
863
+ if (item.state === "cooldown") cooling[item.provider] = true;
864
+ });
865
+ var rows = Object.keys(chains).filter(function (name) { return caps[name]; }).map(function (name) {
866
+ var configured = (caps[name] || {}).configured || [];
867
+ var steps = [];
868
+ chains[name].forEach(function (provider, index) {
869
+ if (index) steps.push(el("span", { class: "arrow", text: "→" }));
870
+ var cls = cooling[provider] ? "bad" : (configured.indexOf(provider) >= 0 ? "ok" : "");
871
+ steps.push(el("span", {
872
+ class: "pill " + cls,
873
+ title: cooling[provider] ? t("cooldown") : "",
874
+ text: provider + (cooling[provider] ? " ✕" : "")
875
+ }));
876
+ });
877
+ return el("tr", {}, [
878
+ el("td", { class: "mono", text: name }),
879
+ el("td", { class: "chain" }, steps)
880
+ ]);
881
+ });
882
+ return el("div", { class: "card" }, [
883
+ el("h3", { text: t("chain") }),
884
+ el("p", { class: "note", text: t("chainNote") }),
885
+ el("table", {}, [el("tbody", {}, rows)])
886
+ ]);
887
+ }
888
+
889
+ function sectionById(id) {
890
+ var found = state.metadata.sections.filter(function (item) { return item.id === id; })[0];
891
+ return found || { label_zh: id, label_en: id, blurb_zh: "", blurb_en: "" };
892
+ }
893
+
894
+ // ---- shell ------------------------------------------------------------
895
+ function renderNav() {
896
+ var nav = document.getElementById("nav");
897
+ nav.textContent = "";
898
+ document.getElementById("subtitle").textContent = t("sub");
899
+ state.metadata.sections.forEach(function (section) {
900
+ nav.appendChild(el("button", {
901
+ type: "button", text: L(section, "label"),
902
+ "aria-current": section.id === current ? "true" : "false",
903
+ onclick: function () {
904
+ if (isDirty() && !window.confirm(t("discard") + "?")) return;
905
+ pending = {};
906
+ current = section.id;
907
+ render();
908
+ }
909
+ }));
910
+ });
911
+ }
912
+
913
+ function render() {
914
+ renderNav();
915
+ var main = document.getElementById("main");
916
+ main.textContent = "";
917
+ if (current === "status") viewStatus();
918
+ else if (current === "getting_started") viewGettingStarted();
919
+ else if (current === "providers") viewProviders();
920
+ else if (current === "skills") viewSkills();
921
+ else if (current === "tryit") viewTryIt();
922
+ else viewSection(current);
923
+ window.scrollTo(0, 0);
924
+ }
925
+
926
+ function refresh() {
927
+ return api("GET", "/api/status").then(function (data) {
928
+ if (data && data.ok) {
929
+ Object.keys(data).forEach(function (key) { state[key] = data[key]; });
930
+ render();
931
+ }
932
+ });
933
+ }
934
+
935
+ document.getElementById("langToggle").addEventListener("click", function () {
936
+ lang = lang === "zh" ? "en" : "zh";
937
+ document.documentElement.lang = lang === "zh" ? "zh-CN" : "en";
938
+ render();
939
+ });
940
+
941
+ window.setInterval(function () { api("POST", "/api/heartbeat", {}); }, 30000);
942
+
943
+ api("GET", "/api/state").then(function (data) {
944
+ if (!data || !data.ok) {
945
+ document.getElementById("main").textContent = (data && data.error) || "failed to load";
946
+ return;
947
+ }
948
+ state = data;
949
+ current = "status";
950
+ render();
951
+ });
952
+ })();
953
+ </script>
954
+ </body>
955
+ </html>