@goodandready/dsh-opencode-provider 0.0.0-stage → 0.5.1

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.
package/lib/client.js ADDED
@@ -0,0 +1,2644 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@goodandready/dsh-opencode-provider",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/locales.ts
10
+ const NS = "settings.opencode";
11
+ const dictionaries = {
12
+ en: {
13
+ nav: "OpenCode",
14
+ title: "OpenCode",
15
+ intro: "OpenCode Console and the OpenCode Go subscription share one adapter. The key is stored in the local credential service and never echoed back.",
16
+ key: "API key",
17
+ keyHint: "Paste a key from the OpenCode console, or import the key OpenCode already stored.",
18
+ keyConfigured: "Configured",
19
+ keyMissing: "Not configured",
20
+ keySource: "Source",
21
+ keyWritable: "Writable",
22
+ pasteKey: "Paste a key",
23
+ saveKey: "Save key",
24
+ clearKey: "Clear key",
25
+ importAuth: "Import from OpenCode",
26
+ imported: "Imported the OpenCode key",
27
+ testConnection: "Test connection",
28
+ testing: "Testing…",
29
+ testOk: "OK in {ms} ms",
30
+ testFailed: "Test failed",
31
+ markQuota: "Quota cooldown",
32
+ markRateLimit: "Rate limited",
33
+ markInvalid: "Invalid key",
34
+ markTransport: "Transport cooldown",
35
+ accounts: "Accounts",
36
+ accountsHint: "The first usable account serves requests; a 401/403/429 or an exhausted quota before the stream starts switches to the next one.",
37
+ accountLabel: "Name",
38
+ accountRef: "Credential ref",
39
+ accountSource: "Source",
40
+ addAccount: "Add account",
41
+ removeAccount: "Remove",
42
+ pinAccount: "Pin",
43
+ activeAccount: "Pinned account",
44
+ activeAccountNow: "Active account",
45
+ active: "active",
46
+ noActiveAccount: "No usable account",
47
+ modelAccountRules: "Model → account rules",
48
+ modelAccountRulesHint: "First matching glob pins that account for the request; the other accounts stay as fallbacks if it fails or is cooling. Use * and ?. An unknown account is ignored.",
49
+ rulePattern: "Model pattern (e.g. *free*)",
50
+ ruleAccount: "Account label",
51
+ addRule: "Add rule",
52
+ removeRule: "Remove",
53
+ auto: "Automatic",
54
+ quota: "Quota",
55
+ quotaTitle: "OpenCode Go quota",
56
+ quotaCardName: "OpenCode Go",
57
+ fiveHourWindow: "5-Hour Window",
58
+ weeklyWindow: "Weekly Window",
59
+ monthlyWindow: "Monthly Window",
60
+ remaining: "remaining",
61
+ quotaWarning: "Quota is above 80% in at least one window.",
62
+ quotaExhausted: "Quota is exhausted in at least one window; requests may be rejected until it resets.",
63
+ fiveHour: "5-hour",
64
+ weekly: "Weekly",
65
+ monthly: "Monthly",
66
+ noUsage: "Quota is unavailable.",
67
+ usageError: "Quota error",
68
+ refresh: "Refresh",
69
+ refreshed: "Refreshed",
70
+ loading: "Loading…",
71
+ failed: "Request failed",
72
+ save: "Save",
73
+ saving: "Saving…",
74
+ saved: "Saved",
75
+ saveFailed: "Save failed",
76
+ unsaved: "Unsaved changes",
77
+ models: "Models",
78
+ modelsHint: "All models are shown when the allowlist is empty. Unchecking a model keeps the rest.",
79
+ filterModelsByPlan: "Hide models outside the Go allowance",
80
+ filterModelsByPlanHint: "On the Go route, show only models covered by the Go table or free models. The allowlist and unfiltered settings views still show everything.",
81
+ limitsHint: "Request estimates come from the OpenCode Go pricing table (5h/week/month, Go and Go Plus); the gateway remains the authority.",
82
+ limitsTitle: "Subscription limits (Go / Go Plus)",
83
+ limitsWindow: "Each cell: monthly allowance, then estimated requests for 5h / week / month. ∞ = unlimited.",
84
+ model: "Model",
85
+ sessions: "Sessions",
86
+ usageAnalytics: "Usage analytics",
87
+ days: "Days",
88
+ date: "Date",
89
+ session: "Session",
90
+ requests: "Requests",
91
+ tokens: "Tokens",
92
+ successRate: "Success rate",
93
+ input: "Input",
94
+ output: "Output",
95
+ reasoning: "Reasoning",
96
+ cacheRead: "Cache read",
97
+ cacheWrite: "Cache write",
98
+ avgTtft: "Avg TTFT",
99
+ avgLatency: "Avg latency",
100
+ errors: "Errors",
101
+ requestHeatmap: "Request heatmap",
102
+ heatmapPeak: "Peak",
103
+ heatmapEmpty: "No requests recorded yet. Send a message or run Test connection.",
104
+ heatmapHint: "Local time. Counts requests made through this plugin; collection starts with this build.",
105
+ heatmapLess: "less",
106
+ heatmapMore: "more",
107
+ heatmapByHour: "By hour (all days)",
108
+ heatmapAllModels: "All models",
109
+ heatmapModel: "Model",
110
+ analyticsHint: "Aggregated from local session metadata by last activity. A session that switched models is counted under its last model; failures are not exposed by the projection.",
111
+ analyticsEmpty: "No OpenCode sessions in this window.",
112
+ showAll: "Show all",
113
+ modelSearch: "Search models…",
114
+ transport: "Transport",
115
+ transportAuto: "Auto",
116
+ free: "FREE",
117
+ vision: "Image",
118
+ sectionFree: "Free models",
119
+ sectionFreeHint: "No charge on any plan.",
120
+ sectionGo: "Go subscription",
121
+ sectionGoHint: "Covered by the Go / Go Plus allowance. N/5h is the estimated request limit; ∞ = unlimited.",
122
+ sectionZen: "Zen / pay-as-you-go",
123
+ sectionZenHint: "Available on the Console/Zen route; not included in the Go allowance.",
124
+ zenBadge: "Zen",
125
+ priceUnknown: "price n/a",
126
+ legendFree: "no charge",
127
+ legendPrice: "USD per 1M input/output tokens",
128
+ legendGo: "Go request estimate per 5h (∞ = unlimited)",
129
+ legendZen: "Console/Zen, not in the Go allowance",
130
+ legendImage: "image input",
131
+ legendUnknownPrice: "The catalog publishes no price for this model.",
132
+ modelsEmpty: "No models match the current search.",
133
+ advanced: "Connection",
134
+ apiKeyEnv: "Credential reference",
135
+ apiKeyEnvHint: "Environment / credential name resolved per request.",
136
+ openCodeAuthEntry: "OpenCode auth entry",
137
+ openCodeAuthEntryHint: "Entry in ~/.local/share/opencode/auth.json used as the last fallback.",
138
+ apiBase: "Console base",
139
+ goApiBase: "Go inference base",
140
+ goZenBase: "Go gateway base",
141
+ usageUrl: "Usage endpoint",
142
+ requestTimeoutMs: "Header timeout (ms)",
143
+ streamIdleTimeoutMs: "Stream idle timeout (ms)",
144
+ maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
145
+ contextWindow: "Context override",
146
+ showSidebarQuota: "Show the quota card in the sidebar",
147
+ pluginUpdate: "Plugin update",
148
+ currentVersion: "Current version",
149
+ latestVersion: "Latest version",
150
+ upToDate: "Up to date",
151
+ updateAvailable: "Update available",
152
+ updateNow: "Update now",
153
+ updating: "Updating…",
154
+ restartRequired: "Restart DSH to load the new version.",
155
+ updateUnavailable: "Automatic update is unavailable in this runtime; use the DSH plugin manager.",
156
+ updateCheckFailed: "Could not check the registry.",
157
+ updateFailed: "Plugin update failed.",
158
+ checking: "Checking…",
159
+ close: "Close",
160
+ openQuota: "OpenCode Go quota",
161
+ settings: "Settings",
162
+ accountCount: "accounts",
163
+ modelCount: "models",
164
+ configured: "configured",
165
+ notConfigured: "not configured",
166
+ resets: "resets",
167
+ perMonth: "monthly"
168
+ },
169
+ zh: {
170
+ nav: "OpenCode",
171
+ title: "OpenCode",
172
+ intro: "OpenCode Console 与 OpenCode Go 订阅共用同一个适配器。密钥保存在本地凭据服务中,永远不会回显。",
173
+ key: "API 密钥",
174
+ keyHint: "粘贴 OpenCode 控制台的密钥,或导入 OpenCode 已保存的密钥。",
175
+ keyConfigured: "已配置",
176
+ keyMissing: "未配置",
177
+ keySource: "来源",
178
+ keyWritable: "可写",
179
+ pasteKey: "粘贴密钥",
180
+ saveKey: "保存密钥",
181
+ clearKey: "清除密钥",
182
+ importAuth: "从 OpenCode 导入",
183
+ imported: "已导入 OpenCode 密钥",
184
+ testConnection: "测试连接",
185
+ testing: "测试中…",
186
+ testOk: "成功,耗时 {ms} ms",
187
+ testFailed: "测试失败",
188
+ markQuota: "配额冷却",
189
+ markRateLimit: "触发限流",
190
+ markInvalid: "密钥无效",
191
+ markTransport: "传输冷却",
192
+ accounts: "账户",
193
+ accountsHint: "第一个可用账户处理请求;流开始前遇到 401/403/429 或配额耗尽时切换到下一个账户。",
194
+ accountLabel: "名称",
195
+ accountRef: "凭据引用",
196
+ accountSource: "来源",
197
+ addAccount: "添加账户",
198
+ removeAccount: "移除",
199
+ pinAccount: "固定",
200
+ activeAccount: "固定账户",
201
+ activeAccountNow: "当前账户",
202
+ active: "活动",
203
+ noActiveAccount: "无可用账户",
204
+ modelAccountRules: "模型 → 账户规则",
205
+ modelAccountRulesHint: "首个匹配的 glob 会为该请求固定账户;失败或冷却时其余账户作为回退。支持 * 和 ?。未知账户会被忽略。",
206
+ rulePattern: "模型模式(例如 *free*)",
207
+ ruleAccount: "账户名称",
208
+ addRule: "添加规则",
209
+ removeRule: "移除",
210
+ auto: "自动",
211
+ quota: "配额",
212
+ quotaTitle: "OpenCode Go 配额",
213
+ quotaCardName: "OpenCode Go",
214
+ fiveHourWindow: "5 小时窗口",
215
+ weeklyWindow: "每周窗口",
216
+ monthlyWindow: "每月窗口",
217
+ remaining: "剩余",
218
+ quotaWarning: "至少一个窗口的配额已超过 80%。",
219
+ quotaExhausted: "至少一个窗口的配额已耗尽;重置前请求可能被拒绝。",
220
+ fiveHour: "5 小时",
221
+ weekly: "每周",
222
+ monthly: "每月",
223
+ noUsage: "配额不可用。",
224
+ usageError: "配额错误",
225
+ refresh: "刷新",
226
+ refreshed: "已刷新",
227
+ loading: "加载中…",
228
+ failed: "请求失败",
229
+ save: "保存",
230
+ saving: "保存中…",
231
+ saved: "已保存",
232
+ saveFailed: "保存失败",
233
+ unsaved: "未保存的更改",
234
+ models: "模型",
235
+ modelsHint: "允许列表为空时显示全部模型。取消勾选某个模型会保留其余模型。",
236
+ filterModelsByPlan: "隐藏 Go 额度之外的模型",
237
+ filterModelsByPlanHint: "在 Go 路由上仅显示 Go 表格覆盖的模型或免费模型。允许列表和未过滤的设置视图仍显示全部。",
238
+ limitsHint: "请求估算来自 OpenCode Go 价格表(5 小时/周/月,Go 与 Go Plus);网关仍是最终权威。",
239
+ limitsTitle: "订阅限制(Go / Go Plus)",
240
+ limitsWindow: "每个单元格:月度额度,然后是 5 小时/周/月的估算请求数。∞ = 无限制。",
241
+ model: "模型",
242
+ sessions: "会话",
243
+ usageAnalytics: "使用分析",
244
+ days: "天数",
245
+ date: "日期",
246
+ session: "会话",
247
+ requests: "请求",
248
+ tokens: "Token",
249
+ successRate: "成功率",
250
+ input: "输入",
251
+ output: "输出",
252
+ reasoning: "推理",
253
+ cacheRead: "缓存读取",
254
+ cacheWrite: "缓存写入",
255
+ avgTtft: "平均 TTFT",
256
+ avgLatency: "平均延迟",
257
+ errors: "错误",
258
+ requestHeatmap: "请求热力图",
259
+ heatmapPeak: "峰值",
260
+ heatmapEmpty: "尚无请求记录。发送消息或运行测试连接。",
261
+ heatmapHint: "本地时间。统计通过本插件发出的请求;从此版本开始收集。",
262
+ heatmapLess: "少",
263
+ heatmapMore: "多",
264
+ heatmapByHour: "按小时(全部日期)",
265
+ heatmapAllModels: "全部模型",
266
+ heatmapModel: "模型",
267
+ analyticsHint: "按最后活动时间聚合本地会话元数据。切换过模型的会话计入最后使用的模型;本地投影不暴露失败。",
268
+ analyticsEmpty: "此窗口内没有 OpenCode 会话。",
269
+ showAll: "显示全部",
270
+ modelSearch: "搜索模型…",
271
+ transport: "传输",
272
+ transportAuto: "自动",
273
+ free: "免费",
274
+ vision: "图片",
275
+ sectionFree: "免费模型",
276
+ sectionFreeHint: "任何计划均不收费。",
277
+ sectionGo: "Go 订阅",
278
+ sectionGoHint: "由 Go / Go Plus 额度覆盖。N/5h 是估算请求限制;∞ = 无限制。",
279
+ sectionZen: "Zen / 按量付费",
280
+ sectionZenHint: "在 Console/Zen 路由可用;不包含在 Go 额度中。",
281
+ zenBadge: "Zen",
282
+ priceUnknown: "价格不可用",
283
+ legendFree: "不收费",
284
+ legendPrice: "每 100 万输入/输出 Token 的美元价格",
285
+ legendGo: "每 5 小时 Go 请求估算(∞ = 无限制)",
286
+ legendZen: "Console/Zen,不在 Go 额度中",
287
+ legendImage: "图片输入",
288
+ legendUnknownPrice: "目录未发布此模型的价格。",
289
+ modelsEmpty: "没有匹配当前搜索的模型。",
290
+ advanced: "连接",
291
+ apiKeyEnv: "凭据引用",
292
+ apiKeyEnvHint: "按请求解析的环境变量 / 凭据名称。",
293
+ openCodeAuthEntry: "OpenCode 认证条目",
294
+ openCodeAuthEntryHint: "用作最后回退的 ~/.local/share/opencode/auth.json 条目。",
295
+ apiBase: "Console 基础 URL",
296
+ goApiBase: "Go 推理基础 URL",
297
+ goZenBase: "Go 网关基础 URL",
298
+ usageUrl: "用量端点",
299
+ requestTimeoutMs: "请求头超时(毫秒)",
300
+ streamIdleTimeoutMs: "流空闲超时(毫秒)",
301
+ maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
302
+ contextWindow: "上下文覆盖",
303
+ showSidebarQuota: "在侧边栏显示配额卡片",
304
+ pluginUpdate: "插件更新",
305
+ currentVersion: "当前版本",
306
+ latestVersion: "最新版本",
307
+ upToDate: "已是最新版本",
308
+ updateAvailable: "有可用更新",
309
+ updateNow: "立即更新",
310
+ updating: "更新中…",
311
+ restartRequired: "重启 DSH 以加载新版本。",
312
+ updateUnavailable: "当前运行环境不支持自动更新;请使用 DSH 插件管理器。",
313
+ updateCheckFailed: "无法检查 registry。",
314
+ updateFailed: "插件更新失败。",
315
+ checking: "检查中…",
316
+ close: "关闭",
317
+ openQuota: "OpenCode Go 配额",
318
+ settings: "设置",
319
+ accountCount: "个账户",
320
+ modelCount: "个模型",
321
+ configured: "已配置",
322
+ notConfigured: "未配置",
323
+ resets: "重置",
324
+ perMonth: "每月"
325
+ }
326
+ };
327
+ //#endregion
328
+ //#region src/client/api.ts
329
+ async function call(path, init) {
330
+ const response = await fetch(path, {
331
+ credentials: "same-origin",
332
+ ...init,
333
+ headers: {
334
+ accept: "application/json",
335
+ ...init?.body === void 0 ? {} : { "content-type": "application/json" },
336
+ ...init?.headers ?? {}
337
+ }
338
+ });
339
+ const body = await response.json().catch(() => ({}));
340
+ if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
341
+ return body;
342
+ }
343
+ function fetchState() {
344
+ return call("/dsh-opencode/state");
345
+ }
346
+ function refreshState() {
347
+ return call("/dsh-opencode/refresh", {
348
+ method: "POST",
349
+ body: "{}"
350
+ });
351
+ }
352
+ function refreshModels() {
353
+ return call("/dsh-opencode/models/refresh", {
354
+ method: "POST",
355
+ body: "{}"
356
+ });
357
+ }
358
+ function saveConfig(patch) {
359
+ return call("/dsh-opencode/config", {
360
+ method: "POST",
361
+ body: JSON.stringify({ patch })
362
+ });
363
+ }
364
+ function fetchUpdate() {
365
+ return call("/dsh-opencode/update");
366
+ }
367
+ function installUpdate() {
368
+ return call("/dsh-opencode/update", {
369
+ method: "POST",
370
+ headers: { "x-dsh-plugin-update": "1" },
371
+ body: "{}"
372
+ });
373
+ }
374
+ function saveKey(ref, key) {
375
+ return call("/dsh-opencode/key", {
376
+ method: "POST",
377
+ body: JSON.stringify({
378
+ ref,
379
+ key
380
+ })
381
+ });
382
+ }
383
+ function clearKey(ref) {
384
+ return call("/dsh-opencode/key/clear", {
385
+ method: "POST",
386
+ body: JSON.stringify({ ref })
387
+ });
388
+ }
389
+ function importAuth(entry) {
390
+ return call("/dsh-opencode/auth-import", {
391
+ method: "POST",
392
+ body: JSON.stringify({ entry })
393
+ });
394
+ }
395
+ function fetchAnalytics(days = 30) {
396
+ return call(`/dsh-opencode/analytics?days=${encodeURIComponent(String(days))}`);
397
+ }
398
+ function testConnection(model, provider = "opencode-go") {
399
+ return call("/dsh-opencode/test", {
400
+ method: "POST",
401
+ body: JSON.stringify({
402
+ model,
403
+ provider
404
+ })
405
+ });
406
+ }
407
+ //#endregion
408
+ //#region src/client/heatmap.tsx
409
+ const DAYS = [
410
+ "Mon",
411
+ "Tue",
412
+ "Wed",
413
+ "Thu",
414
+ "Fri",
415
+ "Sat",
416
+ "Sun"
417
+ ];
418
+ function cellColor(value, max) {
419
+ if (value === 0 || max <= 0) return "rgba(127,127,127,0.10)";
420
+ const ratio = Math.min(1, value / max);
421
+ return `rgba(79,191,134,${(.16 + .74 * Math.sqrt(ratio)).toFixed(3)})`;
422
+ }
423
+ function hourLabel(hour) {
424
+ return hour % 3 === 0 ? String(hour).padStart(2, "0") : "";
425
+ }
426
+ function pad(hour) {
427
+ return String(hour).padStart(2, "0");
428
+ }
429
+ function RequestHeatmapView(props) {
430
+ const { heatmap, t } = props;
431
+ const [selected, setSelected] = (0, react.useState)("all");
432
+ if (heatmap === void 0) return null;
433
+ const models = heatmap.models ?? [];
434
+ const selectedModel = models.find((entry) => `${entry.provider}/${entry.model}` === selected);
435
+ const view = selectedModel ?? heatmap;
436
+ const selectedKey = selectedModel === void 0 ? "all" : selected;
437
+ const max = Math.max(0, ...view.grid.flat());
438
+ const maxHours = Math.max(0, ...view.hours);
439
+ const peakHour = view.hours.reduce((best, value, hour) => value > (view.hours[best] ?? 0) ? hour : best, 0);
440
+ const peak = view.hours[peakHour] ?? 0;
441
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
442
+ className: "oc-heat-wrap",
443
+ children: [
444
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
445
+ className: "oc-row",
446
+ children: [
447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
448
+ className: "oc-badge",
449
+ children: [
450
+ view.total,
451
+ " ",
452
+ t("requests")
453
+ ]
454
+ }),
455
+ models.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
456
+ className: "oc-select",
457
+ value: selectedKey,
458
+ title: t("heatmapModel"),
459
+ onChange: (event) => setSelected(event.target.value),
460
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
461
+ value: "all",
462
+ children: [
463
+ t("heatmapAllModels"),
464
+ " (",
465
+ heatmap.total,
466
+ ")"
467
+ ]
468
+ }), models.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
469
+ value: `${entry.provider}/${entry.model}`,
470
+ children: [
471
+ entry.model,
472
+ " (",
473
+ entry.total,
474
+ ")"
475
+ ]
476
+ }, `${entry.provider}/${entry.model}`))]
477
+ }) : null,
478
+ peak === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
479
+ className: "oc-hint",
480
+ children: [
481
+ t("heatmapPeak"),
482
+ ": ",
483
+ pad(peakHour),
484
+ ":00 · ",
485
+ peak
486
+ ]
487
+ })
488
+ ]
489
+ }),
490
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
491
+ className: "oc-heat-scroll",
492
+ children: [
493
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
494
+ className: "oc-heat",
495
+ children: [
496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}),
497
+ view.hours.map((_, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
498
+ className: "oc-heat-hour",
499
+ children: hourLabel(hour)
500
+ }, `h${hour}`)),
501
+ view.grid.map((row, day) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
502
+ className: "oc-heat-day",
503
+ children: DAYS[day] ?? ""
504
+ }), row.map((value, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
505
+ className: "oc-heat-cell",
506
+ style: { background: cellColor(value, max) },
507
+ title: `${DAYS[day] ?? ""} ${pad(hour)}:00 — ${value}`
508
+ }, `c${day}-${hour}`))] }, `d${day}`))
509
+ ]
510
+ }),
511
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
512
+ className: "oc-hour-strip-head",
513
+ children: t("heatmapByHour")
514
+ }),
515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
516
+ className: "oc-hour-strip",
517
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), view.hours.map((value, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
518
+ className: "oc-hour-col",
519
+ title: `${pad(hour)}:00 — ${value}`,
520
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
521
+ className: "oc-hour-bar",
522
+ style: {
523
+ height: maxHours <= 0 ? "2px" : `${Math.max(2, Math.round(28 * value / maxHours))}px`,
524
+ background: cellColor(value, maxHours)
525
+ }
526
+ })
527
+ }, `s${hour}`))]
528
+ }),
529
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
530
+ className: "oc-hour-labels",
531
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), view.hours.map((_, hour) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hourLabel(hour) }, `l${hour}`))]
532
+ })
533
+ ]
534
+ }),
535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
536
+ className: "oc-heat-legend",
537
+ children: [
538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("heatmapLess") }),
539
+ [
540
+ .1,
541
+ .3,
542
+ .5,
543
+ .7,
544
+ .9
545
+ ].map((ratio) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
546
+ className: "oc-heat-swatch",
547
+ style: { background: `rgba(79,191,134,${(.16 + .74 * ratio).toFixed(3)})` }
548
+ }, ratio)),
549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("heatmapMore") })
550
+ ]
551
+ }),
552
+ view.total === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
553
+ className: "oc-hint",
554
+ children: t("heatmapEmpty")
555
+ }) : null,
556
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
557
+ className: "oc-hint",
558
+ children: t("heatmapHint")
559
+ })
560
+ ]
561
+ });
562
+ }
563
+ //#endregion
564
+ //#region src/client/usage-analytics.tsx
565
+ function compact(value) {
566
+ if (!Number.isFinite(value) || value <= 0) return "0";
567
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
568
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
569
+ return String(Math.round(value));
570
+ }
571
+ function ms(value) {
572
+ return value === null || !Number.isFinite(value) ? "—" : `${Math.round(value)} ms`;
573
+ }
574
+ function Kpi(props) {
575
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
576
+ className: "oc-kpi",
577
+ title: props.title,
578
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
579
+ className: "oc-kpi-value",
580
+ children: props.value
581
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
582
+ className: "oc-kpi-label",
583
+ children: props.label
584
+ })]
585
+ });
586
+ }
587
+ function modelRows(report, t) {
588
+ if (report.models.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
589
+ className: "oc-hint",
590
+ children: t("analyticsEmpty")
591
+ });
592
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
593
+ className: "oc-table-scroll",
594
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
595
+ className: "oc-table",
596
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("model") }),
598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("requests") }),
599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("tokens") }),
600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("avgTtft") })
601
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: report.models.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
602
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.model }),
603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.steps }),
604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: compact(row.inputTokens + row.outputTokens) }),
605
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ms(row.ttftSteps > 0 ? row.ttftMs / row.ttftSteps : null) })
606
+ ] }, `${row.provider}/${row.model}`)) })]
607
+ })
608
+ });
609
+ }
610
+ function dayRows(report, t) {
611
+ if (report.days.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
612
+ className: "oc-hint",
613
+ children: t("analyticsEmpty")
614
+ });
615
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
616
+ className: "oc-table-scroll",
617
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
618
+ className: "oc-table",
619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("date") }),
621
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("sessions") }),
622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("requests") }),
623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("tokens") }),
624
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("avgTtft") })
625
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: report.days.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.date }),
627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.sessions }),
628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.requests }),
629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: compact(row.tokens) }),
630
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ms(row.avgTtftMs) })
631
+ ] }, row.date)) })]
632
+ })
633
+ });
634
+ }
635
+ function sessionRows(report, t) {
636
+ if (report.sessions.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
637
+ className: "oc-hint",
638
+ children: t("analyticsEmpty")
639
+ });
640
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
641
+ className: "oc-table-scroll",
642
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
643
+ className: "oc-table",
644
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("session") }),
646
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("model") }),
647
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("requests") }),
648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("tokens") }),
649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("avgTtft") })
650
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: report.sessions.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
651
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
652
+ title: row.id,
653
+ children: row.id.replace(/^session-/, "").slice(0, 10) || "—"
654
+ }),
655
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.model }),
656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.requests }),
657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: compact(row.tokens) }),
658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: ms(row.avgTtftMs) })
659
+ ] }, `${row.id}-${row.at}`)) })]
660
+ })
661
+ });
662
+ }
663
+ function UsageAnalyticsView(props) {
664
+ const { analytics, t, days, onDays } = props;
665
+ const [tab, setTab] = (0, react.useState)("models");
666
+ const summary = analytics.summary;
667
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
669
+ className: "oc-analytics-head",
670
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
671
+ className: "oc-card-title",
672
+ children: t("usageAnalytics")
673
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
674
+ className: "oc-row",
675
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
676
+ className: "oc-select",
677
+ value: String(days),
678
+ onChange: (event) => onDays(Number(event.target.value)),
679
+ "aria-label": t("days"),
680
+ children: [
681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
682
+ value: "7",
683
+ children: "7 days"
684
+ }),
685
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
686
+ value: "30",
687
+ children: "30 days"
688
+ }),
689
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
690
+ value: "90",
691
+ children: "90 days"
692
+ })
693
+ ]
694
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
695
+ className: "oc-tabs",
696
+ role: "tablist",
697
+ children: [
698
+ "models",
699
+ "days",
700
+ "sessions"
701
+ ].map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
702
+ type: "button",
703
+ role: "tab",
704
+ "aria-selected": tab === entry,
705
+ className: `oc-tab${tab === entry ? " oc-tab-active" : ""}`,
706
+ onClick: () => setTab(entry),
707
+ children: t(entry)
708
+ }, entry))
709
+ })]
710
+ })]
711
+ }),
712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
713
+ className: "oc-kpi-grid",
714
+ children: [
715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
716
+ label: t("requests"),
717
+ value: String(summary.requests)
718
+ }),
719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
720
+ label: t("tokens"),
721
+ value: compact(summary.tokens)
722
+ }),
723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
724
+ label: t("successRate"),
725
+ value: summary.successRate === null ? "—" : `${Math.round(summary.successRate)}%`,
726
+ title: "Recorded session steps; failed attempts are not exposed by the local projection."
727
+ }),
728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
729
+ label: t("input"),
730
+ value: compact(summary.inputTokens)
731
+ }),
732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
733
+ label: t("output"),
734
+ value: compact(summary.outputTokens)
735
+ }),
736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
737
+ label: t("reasoning"),
738
+ value: summary.reasoningTokens === null ? "—" : compact(summary.reasoningTokens),
739
+ title: "Reasoning tokens are not exposed by the local session projection."
740
+ }),
741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
742
+ label: t("cacheRead"),
743
+ value: compact(summary.cacheReadTokens)
744
+ }),
745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
746
+ label: t("cacheWrite"),
747
+ value: compact(summary.cacheWriteTokens)
748
+ }),
749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
750
+ label: t("avgTtft"),
751
+ value: ms(summary.avgTtftMs)
752
+ }),
753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
754
+ label: t("avgLatency"),
755
+ value: ms(summary.avgLatencyMs)
756
+ }),
757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Kpi, {
758
+ label: t("errors"),
759
+ value: summary.errors === null ? "—" : String(summary.errors),
760
+ title: "The local projection does not expose failed attempts."
761
+ })
762
+ ]
763
+ }),
764
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
765
+ className: "oc-hint",
766
+ children: t("analyticsHint")
767
+ }),
768
+ tab === "models" ? modelRows(analytics, t) : null,
769
+ tab === "days" ? dayRows(analytics, t) : null,
770
+ tab === "sessions" ? sessionRows(analytics, t) : null
771
+ ] });
772
+ }
773
+ //#endregion
774
+ //#region src/client/usage-bars.tsx
775
+ function percentClass(percent) {
776
+ if (percent >= 90) return "oc-bar-fill oc-bar-fill-bad";
777
+ if (percent >= 75) return "oc-bar-fill oc-bar-fill-warn";
778
+ return "oc-bar-fill";
779
+ }
780
+ function reset(iso) {
781
+ if (iso === void 0) return "";
782
+ const date = new Date(iso);
783
+ if (Number.isNaN(date.getTime())) return "";
784
+ const delta = date.getTime() - Date.now();
785
+ const relative = delta <= 0 ? "now" : delta >= 864e5 ? `${Math.round(delta / 864e5)}d` : delta >= 36e5 ? `${Math.round(delta / 36e5)}h` : `${Math.max(1, Math.round(delta / 6e4))}m`;
786
+ return `${date.toLocaleString([], {
787
+ month: "2-digit",
788
+ day: "2-digit",
789
+ hour: "2-digit",
790
+ minute: "2-digit"
791
+ })} (${relative})`;
792
+ }
793
+ function Row(props) {
794
+ const { label, window } = props;
795
+ if (window === void 0) return null;
796
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
797
+ className: "oc-bar-row",
798
+ children: [
799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
801
+ className: "oc-bar",
802
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
803
+ className: percentClass(window.percentUsed),
804
+ style: { width: `${Math.max(0, Math.min(100, window.percentUsed))}%` }
805
+ })
806
+ }),
807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
808
+ title: window.resetsAt === void 0 ? void 0 : new Date(window.resetsAt).toLocaleString(),
809
+ children: [
810
+ window.percentUsed.toFixed(0),
811
+ "%",
812
+ window.resetsAt === void 0 ? "" : ` · ${reset(window.resetsAt)}`
813
+ ]
814
+ })
815
+ ]
816
+ });
817
+ }
818
+ function UsageBars(props) {
819
+ const { usage, t } = props;
820
+ if (usage === null || usage.report === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
821
+ className: "oc-hint",
822
+ children: usage?.error === void 0 ? t("noUsage") : `${t("usageError")}: ${usage.error}`
823
+ });
824
+ const windows = usage.report.windows;
825
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
826
+ className: "oc-bars",
827
+ children: [
828
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
829
+ label: t("fiveHour"),
830
+ window: windows.fiveHour
831
+ }),
832
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
833
+ label: t("weekly"),
834
+ window: windows.weekly
835
+ }),
836
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
837
+ label: t("monthly"),
838
+ window: windows.monthly
839
+ })
840
+ ]
841
+ });
842
+ }
843
+ //#endregion
844
+ //#region src/client/quota-card.tsx
845
+ const PANEL_EVENT = "dsh-opencode-panel-state";
846
+ function broadcastPanel(open) {
847
+ if (typeof window === "undefined") return;
848
+ window.dispatchEvent(new CustomEvent(PANEL_EVENT, { detail: open }));
849
+ }
850
+ function usePanelOpen() {
851
+ const [open, setOpen] = (0, react.useState)(false);
852
+ (0, react.useEffect)(() => {
853
+ const onChange = (event) => {
854
+ if (event instanceof CustomEvent) setOpen(event.detail === true);
855
+ };
856
+ window.addEventListener(PANEL_EVENT, onChange);
857
+ return () => window.removeEventListener(PANEL_EVENT, onChange);
858
+ }, []);
859
+ return open;
860
+ }
861
+ function useHostState(intervalMs) {
862
+ const [state, setState] = (0, react.useState)(null);
863
+ (0, react.useEffect)(() => {
864
+ let cancelled = false;
865
+ const load = () => {
866
+ fetchState().then((next) => {
867
+ if (!cancelled) setState(next);
868
+ }).catch(() => {});
869
+ };
870
+ load();
871
+ const timer = setInterval(load, intervalMs);
872
+ return () => {
873
+ cancelled = true;
874
+ clearInterval(timer);
875
+ };
876
+ }, [intervalMs]);
877
+ return state;
878
+ }
879
+ function percent(window) {
880
+ return window === void 0 ? 0 : Math.max(0, Math.min(100, window.percentUsed));
881
+ }
882
+ function remaining(window) {
883
+ return Math.max(0, 100 - Math.round(percent(window)));
884
+ }
885
+ function resetIn(iso) {
886
+ if (iso === void 0) return "";
887
+ const at = new Date(iso).getTime();
888
+ if (!Number.isFinite(at)) return "";
889
+ const delta = at - Date.now();
890
+ if (delta <= 0) return "now";
891
+ if (delta >= 864e5) return `${Math.round(delta / 864e5)}d`;
892
+ if (delta >= 36e5) return `${Math.round(delta / 36e5)}h`;
893
+ return `${Math.max(1, Math.round(delta / 6e4))}m`;
894
+ }
895
+ function usagePercent(window) {
896
+ return window === void 0 ? "—" : `${Math.round(window.percentUsed)}%`;
897
+ }
898
+ function accountStatus(account, t) {
899
+ if (account.mark !== "") {
900
+ const label = account.mark === "invalid-credential" ? t("markInvalid") : account.mark === "quota" ? t("markQuota") : account.mark === "rate-limit" ? t("markRateLimit") : t("markTransport");
901
+ return account.coolingUntil > 0 ? `${label} · ${resetIn(new Date(account.coolingUntil).toISOString())}` : label;
902
+ }
903
+ const windows = account.usage?.report?.windows;
904
+ return [
905
+ windows?.fiveHour,
906
+ windows?.weekly,
907
+ windows?.monthly
908
+ ].map((window) => window?.status).find((value) => value !== void 0 && value !== "ok");
909
+ }
910
+ function fillClass(value) {
911
+ if (value >= 90) return "oc-footFill oc-footFillBad";
912
+ if (value >= 75) return "oc-footFill oc-footFillWarn";
913
+ return "oc-footFill";
914
+ }
915
+ function MiniRing(props) {
916
+ const radius = 7;
917
+ const circumference = 2 * Math.PI * radius;
918
+ const offset = circumference - Math.max(0, Math.min(100, props.percent)) / 100 * circumference;
919
+ const size = props.size ?? 16;
920
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
921
+ className: "oc-ring",
922
+ width: size,
923
+ height: size,
924
+ viewBox: "0 0 20 20",
925
+ fill: "none",
926
+ "aria-hidden": "true",
927
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
928
+ cx: "10",
929
+ cy: "10",
930
+ r: radius,
931
+ stroke: "var(--dsw-alias-bg-layer-2, rgba(127,127,127,0.25))",
932
+ strokeWidth: "2.5"
933
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
934
+ cx: "10",
935
+ cy: "10",
936
+ r: radius,
937
+ stroke: props.warn ? "var(--dsw-alias-state-error-primary, #e07a7a)" : "var(--dsw-alias-brand-primary, #4fbf86)",
938
+ strokeWidth: "2.5",
939
+ strokeDasharray: circumference,
940
+ strokeDashoffset: offset,
941
+ strokeLinecap: "round",
942
+ transform: "rotate(-90 10 10)",
943
+ style: { transition: "stroke-dashoffset 0.3s ease" }
944
+ })]
945
+ });
946
+ }
947
+ function QuotaCard(props) {
948
+ const { t } = props;
949
+ const state = useHostState(6e4);
950
+ const expanded = usePanelOpen();
951
+ if (state === null || state.config.showSidebarQuota !== true) return null;
952
+ const windows = state.usage?.report?.windows;
953
+ const accountLabel = state.accounts.find((account) => account.active)?.label;
954
+ const rows = [
955
+ {
956
+ key: "fiveHour",
957
+ label: t("fiveHourWindow"),
958
+ window: windows?.fiveHour
959
+ },
960
+ {
961
+ key: "weekly",
962
+ label: t("weeklyWindow"),
963
+ window: windows?.weekly
964
+ },
965
+ {
966
+ key: "monthly",
967
+ label: t("monthlyWindow"),
968
+ window: windows?.monthly
969
+ }
970
+ ].filter((row) => row.window !== void 0);
971
+ const worst = rows.reduce((value, row) => Math.max(value, percent(row.window)), 0);
972
+ const warn = worst >= 90;
973
+ const caution = worst >= 75;
974
+ const name = accountLabel === void 0 || accountLabel === "default" ? t("quotaCardName") : `${t("quotaCardName")} · ${accountLabel}`;
975
+ const detail = rows.map((row) => {
976
+ const reset = resetIn(row.window.resetsAt);
977
+ const status = row.window.status === void 0 || row.window.status === "ok" ? "" : ` · ${row.window.status}`;
978
+ return `${row.label}: ${remaining(row.window)}% ${t("remaining")}${reset === "" ? "" : ` · ${reset}`}${status}`;
979
+ }).join(" · ");
980
+ const title = `${name} — ${detail === "" ? state.usage?.error ?? t("noUsage") : detail}`;
981
+ const open = () => {
982
+ broadcastPanel(true);
983
+ props.open?.();
984
+ };
985
+ if (props.wide === false) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
986
+ type: "button",
987
+ className: "oc-railButton",
988
+ title,
989
+ "aria-label": title,
990
+ "aria-haspopup": "dialog",
991
+ "aria-expanded": expanded,
992
+ onClick: open,
993
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniRing, {
994
+ percent: worst,
995
+ warn,
996
+ size: 20
997
+ })
998
+ });
999
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1000
+ type: "button",
1001
+ className: "oc-foot",
1002
+ title,
1003
+ "aria-label": title,
1004
+ "aria-haspopup": "dialog",
1005
+ "aria-expanded": expanded,
1006
+ onClick: open,
1007
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1008
+ className: "oc-footTop",
1009
+ children: [
1010
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniRing, {
1011
+ percent: worst,
1012
+ warn,
1013
+ size: 16
1014
+ }),
1015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1016
+ className: "oc-footName",
1017
+ children: name
1018
+ }),
1019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1020
+ className: `oc-footPct${warn ? " oc-footPctBad" : caution ? " oc-footPctWarn" : ""}`,
1021
+ children: rows.length === 0 ? "—" : `${Math.round(worst)}%`
1022
+ })
1023
+ ]
1024
+ }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1025
+ className: "oc-footMessage",
1026
+ children: state.usage?.error ?? t("noUsage")
1027
+ }) : rows.map((row) => {
1028
+ const used = percent(row.window);
1029
+ const reset = resetIn(row.window.resetsAt);
1030
+ const status = row.window.status === void 0 || row.window.status === "ok" ? "" : row.window.status;
1031
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1032
+ className: "oc-footRow",
1033
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1034
+ className: "oc-footHead",
1035
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1036
+ className: "oc-footLabel",
1037
+ children: row.label
1038
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1039
+ className: "oc-footAmount",
1040
+ children: [
1041
+ remaining(row.window),
1042
+ "% ",
1043
+ t("remaining"),
1044
+ reset === "" ? "" : ` · ${reset}`,
1045
+ status === "" ? "" : ` · ${status}`
1046
+ ]
1047
+ })]
1048
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1049
+ className: "oc-footBar",
1050
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1051
+ className: fillClass(used),
1052
+ style: { width: `${used === 0 ? 0 : Math.max(2, used)}%` }
1053
+ })
1054
+ })]
1055
+ }, row.key);
1056
+ })]
1057
+ });
1058
+ }
1059
+ function QuotaPanel(props) {
1060
+ const { t } = props;
1061
+ const [state, setState] = (0, react.useState)(null);
1062
+ const [stateError, setStateError] = (0, react.useState)(null);
1063
+ const [analytics, setAnalytics] = (0, react.useState)(null);
1064
+ const [analyticsError, setAnalyticsError] = (0, react.useState)(null);
1065
+ const [days, setDays] = (0, react.useState)(7);
1066
+ const [busy, setBusy] = (0, react.useState)(false);
1067
+ const analyticsSeq = (0, react.useRef)(0);
1068
+ const load = () => {
1069
+ fetchState().then((next) => {
1070
+ setState(next);
1071
+ setStateError(null);
1072
+ }).catch((caught) => {
1073
+ setStateError(caught instanceof Error ? caught.message : "state failed");
1074
+ });
1075
+ };
1076
+ const loadAnalytics = (windowDays) => {
1077
+ const seq = analyticsSeq.current + 1;
1078
+ analyticsSeq.current = seq;
1079
+ setAnalyticsError(null);
1080
+ fetchAnalytics(windowDays).then((next) => {
1081
+ if (seq !== analyticsSeq.current) return;
1082
+ setAnalytics(next);
1083
+ setAnalyticsError(null);
1084
+ }).catch((caught) => {
1085
+ if (seq !== analyticsSeq.current) return;
1086
+ setAnalytics(null);
1087
+ setAnalyticsError(caught instanceof Error ? caught.message : "analytics failed");
1088
+ });
1089
+ };
1090
+ (0, react.useEffect)(() => {
1091
+ load();
1092
+ loadAnalytics(days);
1093
+ const timer = setInterval(load, 6e4);
1094
+ return () => {
1095
+ clearInterval(timer);
1096
+ analyticsSeq.current += 1;
1097
+ };
1098
+ }, [days]);
1099
+ (0, react.useEffect)(() => () => broadcastPanel(false), []);
1100
+ const activeAccount = state?.accounts.find((account) => account.active);
1101
+ const refresh = () => {
1102
+ setBusy(true);
1103
+ refreshState().then((next) => {
1104
+ setState(next);
1105
+ setStateError(null);
1106
+ }).catch((caught) => {
1107
+ setStateError(caught instanceof Error ? caught.message : "state failed");
1108
+ }).finally(() => setBusy(false));
1109
+ loadAnalytics(days);
1110
+ };
1111
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1112
+ className: "oc-panel",
1113
+ children: [
1114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1115
+ className: "oc-panel-head",
1116
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1117
+ className: "oc-title",
1118
+ children: t("quotaTitle")
1119
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1120
+ className: "oc-row",
1121
+ children: [
1122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1123
+ className: "oc-select",
1124
+ value: String(days),
1125
+ onChange: (event) => setDays(Number(event.target.value)),
1126
+ children: [
1127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1128
+ value: "7",
1129
+ children: "7 days"
1130
+ }),
1131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1132
+ value: "30",
1133
+ children: "30 days"
1134
+ }),
1135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1136
+ value: "90",
1137
+ children: "90 days"
1138
+ })
1139
+ ]
1140
+ }),
1141
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1142
+ type: "button",
1143
+ className: "oc-button",
1144
+ disabled: busy,
1145
+ onClick: refresh,
1146
+ children: busy ? t("loading") : t("refresh")
1147
+ }),
1148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1149
+ type: "button",
1150
+ className: "oc-button",
1151
+ onClick: () => {
1152
+ broadcastPanel(false);
1153
+ props.close?.();
1154
+ },
1155
+ children: t("close")
1156
+ })
1157
+ ]
1158
+ })]
1159
+ }),
1160
+ stateError === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1161
+ className: "oc-hint",
1162
+ children: [
1163
+ t("failed"),
1164
+ ": ",
1165
+ stateError
1166
+ ]
1167
+ }),
1168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1169
+ className: "oc-card",
1170
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1171
+ className: "oc-row",
1172
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1173
+ className: "oc-badge",
1174
+ children: [
1175
+ t("activeAccountNow"),
1176
+ ": ",
1177
+ activeAccount?.label ?? t("noActiveAccount")
1178
+ ]
1179
+ })
1180
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBars, {
1181
+ usage: state?.usage ?? null,
1182
+ t
1183
+ })]
1184
+ }),
1185
+ state === null || state.accounts.length < 2 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1186
+ className: "oc-card",
1187
+ children: [
1188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1189
+ className: "oc-card-title",
1190
+ children: t("accounts")
1191
+ }),
1192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1193
+ className: "oc-table",
1194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("accountLabel") }),
1196
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("fiveHour") }),
1197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("weekly") }),
1198
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("monthly") })
1199
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: state.accounts.map((account) => {
1200
+ const accountWindows = account.usage?.report?.windows;
1201
+ const status = accountStatus(account, t);
1202
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
1204
+ account.label,
1205
+ account.active ? ` · ${t("active")}` : "",
1206
+ status === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1207
+ className: "oc-badge oc-badge-bad",
1208
+ style: { marginLeft: 6 },
1209
+ title: account.coolingUntil > 0 ? new Date(account.coolingUntil).toLocaleString() : void 0,
1210
+ children: status
1211
+ })
1212
+ ] }),
1213
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: usagePercent(accountWindows?.fiveHour) }),
1214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: usagePercent(accountWindows?.weekly) }),
1215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: usagePercent(accountWindows?.monthly) })
1216
+ ] }, account.label);
1217
+ }) })]
1218
+ }),
1219
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1220
+ className: "oc-hint",
1221
+ children: t("accountsHint")
1222
+ })
1223
+ ]
1224
+ }),
1225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1226
+ className: "oc-card",
1227
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1228
+ className: "oc-row",
1229
+ children: [
1230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1231
+ className: "oc-badge",
1232
+ children: [
1233
+ state?.accounts.length ?? 0,
1234
+ " ",
1235
+ t("accountCount")
1236
+ ]
1237
+ }),
1238
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1239
+ className: "oc-badge",
1240
+ children: [
1241
+ state?.models.length ?? 0,
1242
+ " ",
1243
+ t("modelCount")
1244
+ ]
1245
+ }),
1246
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1247
+ className: `oc-badge ${state?.key.configured === true ? "oc-badge-ok" : "oc-badge-warn"}`,
1248
+ children: state?.key.configured === true ? t("configured") : t("notConfigured")
1249
+ })
1250
+ ]
1251
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1252
+ className: "oc-hint",
1253
+ children: t("intro")
1254
+ })]
1255
+ }),
1256
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1257
+ className: "oc-card",
1258
+ children: analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1260
+ className: "oc-card-title",
1261
+ children: t("usageAnalytics")
1262
+ }),
1263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1264
+ className: "oc-hint",
1265
+ children: [
1266
+ t("failed"),
1267
+ ": ",
1268
+ analyticsError
1269
+ ]
1270
+ }),
1271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1272
+ type: "button",
1273
+ className: "oc-button",
1274
+ onClick: () => loadAnalytics(days),
1275
+ children: t("refresh")
1276
+ })
1277
+ ] }) : analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1278
+ className: "oc-card-title",
1279
+ children: t("usageAnalytics")
1280
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1281
+ className: "oc-hint",
1282
+ children: t("loading")
1283
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageAnalyticsView, {
1284
+ analytics,
1285
+ t,
1286
+ days,
1287
+ onDays: setDays
1288
+ })
1289
+ }),
1290
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1291
+ className: "oc-card",
1292
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1293
+ className: "oc-card-title",
1294
+ children: t("requestHeatmap")
1295
+ }), analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1296
+ className: "oc-hint",
1297
+ children: [
1298
+ t("failed"),
1299
+ ": ",
1300
+ analyticsError
1301
+ ]
1302
+ }) : analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1303
+ className: "oc-hint",
1304
+ children: t("loading")
1305
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequestHeatmapView, {
1306
+ heatmap: analytics.heatmap,
1307
+ t
1308
+ })]
1309
+ })
1310
+ ]
1311
+ });
1312
+ }
1313
+ //#endregion
1314
+ //#region src/client/settings-draft.ts
1315
+ function draftOf(state) {
1316
+ const allIds = state.models.map((model) => model.id);
1317
+ return {
1318
+ apiKeyEnv: state.config.apiKeyEnv,
1319
+ apiBase: state.config.apiBase,
1320
+ goApiBase: state.config.goApiBase,
1321
+ goZenBase: state.config.goZenBase,
1322
+ usageUrl: state.config.usageUrl,
1323
+ openCodeAuthEntry: state.config.openCodeAuthEntry,
1324
+ requestTimeoutMs: String(state.config.requestTimeoutMs),
1325
+ streamIdleTimeoutMs: String(state.config.streamIdleTimeoutMs),
1326
+ maxOutputTokens: state.config.maxOutputTokens === null ? "" : String(state.config.maxOutputTokens),
1327
+ contextWindow: state.config.contextWindow === null ? "" : String(state.config.contextWindow),
1328
+ showSidebarQuota: state.config.showSidebarQuota,
1329
+ activeAccount: state.config.activeAccount,
1330
+ accounts: state.config.accounts.map((account) => ({ ...account })),
1331
+ modelAccountRules: state.config.modelAccountRules.map((rule) => ({ ...rule })),
1332
+ visibleModels: state.config.visibleModels.length === 0 ? allIds : [...state.config.visibleModels],
1333
+ filterModelsByPlan: state.config.filterModelsByPlan,
1334
+ showAll: state.config.visibleModels.length === 0,
1335
+ modelTransports: { ...state.config.modelTransports }
1336
+ };
1337
+ }
1338
+ function numberOrNull(text) {
1339
+ const trimmed = text.trim();
1340
+ if (trimmed === "") return null;
1341
+ const value = Number(trimmed);
1342
+ if (!Number.isInteger(value) || value < 0) throw new Error(`Invalid number: ${text}`);
1343
+ return value;
1344
+ }
1345
+ function patchOf(draft) {
1346
+ return {
1347
+ apiKeyEnv: draft.apiKeyEnv.trim(),
1348
+ apiBase: draft.apiBase.trim(),
1349
+ goApiBase: draft.goApiBase.trim(),
1350
+ goZenBase: draft.goZenBase.trim(),
1351
+ usageUrl: draft.usageUrl.trim(),
1352
+ openCodeAuthEntry: draft.openCodeAuthEntry.trim(),
1353
+ requestTimeoutMs: numberOrNull(draft.requestTimeoutMs) ?? 3e5,
1354
+ streamIdleTimeoutMs: numberOrNull(draft.streamIdleTimeoutMs) ?? 3e5,
1355
+ maxOutputTokens: numberOrNull(draft.maxOutputTokens),
1356
+ contextWindow: numberOrNull(draft.contextWindow),
1357
+ showSidebarQuota: draft.showSidebarQuota,
1358
+ activeAccount: draft.activeAccount.trim(),
1359
+ accounts: draft.accounts.map((account) => ({
1360
+ label: account.label.trim(),
1361
+ apiKeyEnv: account.apiKeyEnv.trim()
1362
+ })).filter((account) => account.label !== "" || account.apiKeyEnv !== ""),
1363
+ modelAccountRules: draft.modelAccountRules.map((rule) => ({
1364
+ pattern: rule.pattern.trim(),
1365
+ account: rule.account.trim()
1366
+ })).filter((rule) => rule.pattern !== "" && rule.account !== ""),
1367
+ visibleModels: draft.showAll ? [] : draft.visibleModels,
1368
+ filterModelsByPlan: draft.filterModelsByPlan,
1369
+ modelTransports: Object.fromEntries(Object.entries(draft.modelTransports).filter(([, value]) => value !== ""))
1370
+ };
1371
+ }
1372
+ function same(a, b) {
1373
+ return JSON.stringify(patchOf(a)) === JSON.stringify(patchOf(b));
1374
+ }
1375
+ function safeSame(a, b) {
1376
+ try {
1377
+ return same(a, b);
1378
+ } catch {
1379
+ return false;
1380
+ }
1381
+ }
1382
+ /** Compact request estimate; `∞` for an unlimited window. */
1383
+ function formatRequests(value) {
1384
+ if (value === "unlimited") return "∞";
1385
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
1386
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
1387
+ return String(value);
1388
+ }
1389
+ function formatUsd(value) {
1390
+ return value === "unlimited" ? "unlimited" : `$${value}`;
1391
+ }
1392
+ function planLine(label, limits) {
1393
+ return `${label}: ${formatUsd(limits.monthlyUsd)}/mo · ${formatRequests(limits.fiveHour)}/${formatRequests(limits.weekly)}/${formatRequests(limits.monthly)} req (5h/wk/mo)`;
1394
+ }
1395
+ /** Full tooltip text for the settings row. */
1396
+ function describeLimitsFull(limits) {
1397
+ return `${planLine("Go", limits.go)} · ${planLine("Go Plus", limits.goPlus)}`;
1398
+ }
1399
+ //#endregion
1400
+ //#region src/client/settings-widgets.tsx
1401
+ function Field(props) {
1402
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1403
+ className: "oc-field",
1404
+ children: [
1405
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1406
+ className: "oc-label",
1407
+ children: props.label
1408
+ }),
1409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1410
+ className: "oc-input",
1411
+ type: props.type ?? "text",
1412
+ value: props.value,
1413
+ placeholder: props.placeholder,
1414
+ list: props.list,
1415
+ onChange: (event) => props.onChange(event.target.value)
1416
+ }),
1417
+ props.hint === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1418
+ className: "oc-hint",
1419
+ children: props.hint
1420
+ })
1421
+ ]
1422
+ });
1423
+ }
1424
+ function limitsCell(plan) {
1425
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1426
+ plan.monthlyUsd === "unlimited" ? "∞" : `$${plan.monthlyUsd}`,
1427
+ "/mo",
1428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
1429
+ formatRequests(plan.fiveHour),
1430
+ "/",
1431
+ formatRequests(plan.weekly),
1432
+ "/",
1433
+ formatRequests(plan.monthly),
1434
+ " req"
1435
+ ] });
1436
+ }
1437
+ function untilText(epoch) {
1438
+ const delta = Math.max(0, epoch - Date.now());
1439
+ if (delta >= 864e5) return `${Math.round(delta / 864e5)}d`;
1440
+ if (delta >= 36e5) return `${Math.round(delta / 36e5)}h`;
1441
+ return `${Math.max(1, Math.round(delta / 6e4))}m`;
1442
+ }
1443
+ function usageChip(label, window) {
1444
+ if (window === void 0) return null;
1445
+ const value = Math.round(window.percentUsed);
1446
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1447
+ className: `oc-badge ${value >= 90 ? "oc-badge-bad" : value >= 75 ? "oc-badge-warn" : "oc-badge-ok"}`,
1448
+ children: [
1449
+ label,
1450
+ " ",
1451
+ value,
1452
+ "%"
1453
+ ]
1454
+ });
1455
+ }
1456
+ function accountMark(account, t) {
1457
+ if (account === void 0 || account.mark === "") return void 0;
1458
+ if (account.mark === "invalid-credential") return t("markInvalid");
1459
+ const label = account.mark === "quota" ? t("markQuota") : account.mark === "rate-limit" ? t("markRateLimit") : t("markTransport");
1460
+ if (account.coolingUntil <= 0) return label;
1461
+ return `${label} · ${untilText(account.coolingUntil)}`;
1462
+ }
1463
+ //#endregion
1464
+ //#region src/client/update-card.tsx
1465
+ function PluginUpdateCard(props) {
1466
+ const { t } = props;
1467
+ const [status, setStatus] = (0, react.useState)(null);
1468
+ const [error, setError] = (0, react.useState)(null);
1469
+ const [busy, setBusy] = (0, react.useState)(false);
1470
+ const [notice, setNotice] = (0, react.useState)(null);
1471
+ const load = () => {
1472
+ fetchUpdate().then((next) => {
1473
+ setStatus(next);
1474
+ setError(null);
1475
+ }).catch((caught) => {
1476
+ setError(caught instanceof Error ? caught.message : t("updateCheckFailed"));
1477
+ });
1478
+ };
1479
+ (0, react.useEffect)(() => {
1480
+ load();
1481
+ }, []);
1482
+ const update = () => {
1483
+ setBusy(true);
1484
+ setError(null);
1485
+ setNotice(null);
1486
+ installUpdate().then((next) => {
1487
+ setStatus(next);
1488
+ setNotice(next.restartRequired === true ? t("restartRequired") : t("upToDate"));
1489
+ }).catch((caught) => {
1490
+ setError(caught instanceof Error ? caught.message : t("updateFailed"));
1491
+ }).finally(() => setBusy(false));
1492
+ };
1493
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1494
+ className: "oc-card",
1495
+ children: [
1496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1497
+ className: "oc-card-title",
1498
+ children: [t("pluginUpdate"), status === null ? null : status.updateAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1499
+ className: "oc-badge oc-badge-warn",
1500
+ children: t("updateAvailable")
1501
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1502
+ className: "oc-badge oc-badge-ok",
1503
+ children: t("upToDate")
1504
+ })]
1505
+ }),
1506
+ status === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1507
+ className: "oc-hint",
1508
+ children: error ?? t("checking")
1509
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1510
+ className: "oc-row",
1511
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1512
+ className: "oc-badge",
1513
+ children: [
1514
+ t("currentVersion"),
1515
+ ": ",
1516
+ status.currentVersion
1517
+ ]
1518
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1519
+ className: "oc-badge",
1520
+ children: [
1521
+ t("latestVersion"),
1522
+ ": ",
1523
+ status.latestVersion ?? "—"
1524
+ ]
1525
+ })]
1526
+ }), status.canAutoUpdate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1527
+ type: "button",
1528
+ className: "oc-button",
1529
+ disabled: busy || !status.updateAvailable || status.updatedVersion !== void 0,
1530
+ onClick: update,
1531
+ children: busy ? t("updating") : t("updateNow")
1532
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1533
+ className: "oc-hint",
1534
+ children: t("updateUnavailable")
1535
+ })] }),
1536
+ notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1537
+ className: "oc-notice",
1538
+ children: notice
1539
+ }),
1540
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1541
+ className: "oc-error",
1542
+ children: error
1543
+ })
1544
+ ]
1545
+ });
1546
+ }
1547
+ //#endregion
1548
+ //#region src/client/settings-page.tsx
1549
+ function OpenCodeSettingsPage(props) {
1550
+ const { t } = props;
1551
+ const [state, setState] = (0, react.useState)(null);
1552
+ const [draft, setDraft] = (0, react.useState)(null);
1553
+ const [loading, setLoading] = (0, react.useState)(true);
1554
+ const [saving, setSaving] = (0, react.useState)(false);
1555
+ const [error, setError] = (0, react.useState)(null);
1556
+ const [notice, setNotice] = (0, react.useState)(null);
1557
+ const [keyDraft, setKeyDraft] = (0, react.useState)("");
1558
+ const [keyDrafts, setKeyDrafts] = (0, react.useState)({});
1559
+ const [search, setSearch] = (0, react.useState)("");
1560
+ const [testModel, setTestModel] = (0, react.useState)("deepseek-v4.1-flash");
1561
+ const [testing, setTesting] = (0, react.useState)(false);
1562
+ const [testResult, setTestResult] = (0, react.useState)(null);
1563
+ const load = (0, react.useCallback)(async () => {
1564
+ setLoading(true);
1565
+ try {
1566
+ const next = await fetchState();
1567
+ setState(next);
1568
+ setDraft((current) => current ?? draftOf(next));
1569
+ setTestModel((current) => next.models.some((model) => model.id === current) ? current : next.models[0]?.id ?? current);
1570
+ setError(null);
1571
+ } catch (caught) {
1572
+ setError(caught instanceof Error ? caught.message : String(caught));
1573
+ } finally {
1574
+ setLoading(false);
1575
+ }
1576
+ }, []);
1577
+ (0, react.useEffect)(() => {
1578
+ load();
1579
+ }, [load]);
1580
+ const update = (next) => {
1581
+ setDraft((current) => current === null ? current : {
1582
+ ...current,
1583
+ ...next
1584
+ });
1585
+ setNotice(null);
1586
+ };
1587
+ const save = async () => {
1588
+ if (draft === null) return;
1589
+ setSaving(true);
1590
+ setError(null);
1591
+ try {
1592
+ await saveConfig(patchOf(draft));
1593
+ setNotice(t("saved"));
1594
+ setState(null);
1595
+ setDraft(null);
1596
+ await load();
1597
+ } catch (caught) {
1598
+ setError(caught instanceof Error ? caught.message : t("saveFailed"));
1599
+ } finally {
1600
+ setSaving(false);
1601
+ }
1602
+ };
1603
+ const refresh = async () => {
1604
+ try {
1605
+ const next = await refreshState();
1606
+ setState(next);
1607
+ setNotice(t("refreshed"));
1608
+ setError(null);
1609
+ } catch (caught) {
1610
+ setError(caught instanceof Error ? caught.message : t("failed"));
1611
+ }
1612
+ };
1613
+ if (loading && state === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1614
+ className: "oc-hint",
1615
+ children: t("loading")
1616
+ });
1617
+ if (state === null || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1618
+ className: "oc-page",
1619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1620
+ className: "oc-error",
1621
+ children: error ?? t("failed")
1622
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1623
+ type: "button",
1624
+ className: "oc-button",
1625
+ onClick: () => {
1626
+ load();
1627
+ },
1628
+ children: t("refresh")
1629
+ })]
1630
+ });
1631
+ const dirty = !safeSame(draft, draftOf(state));
1632
+ const activeAccount = state.accounts.find((account) => account.active);
1633
+ const defaultAccount = state.accounts.find((account) => account.default);
1634
+ const models = state.models.filter((model) => {
1635
+ const query = search.trim().toLowerCase();
1636
+ return query === "" || model.id.toLowerCase().includes(query) || model.name.toLowerCase().includes(query);
1637
+ });
1638
+ const freeModels = models.filter((model) => model.free);
1639
+ const goModels = models.filter((model) => !model.free && model.limits !== null);
1640
+ const zenModels = models.filter((model) => !model.free && model.limits === null);
1641
+ const toggleModel = (id) => {
1642
+ if (draft.showAll || draft.visibleModels.includes(id)) {
1643
+ const next = (draft.showAll ? state.models.map((model) => model.id) : draft.visibleModels).filter((modelId) => modelId !== id);
1644
+ if (next.length === 0) {
1645
+ update({
1646
+ showAll: true,
1647
+ visibleModels: state.models.map((model) => model.id)
1648
+ });
1649
+ return;
1650
+ }
1651
+ update({
1652
+ showAll: false,
1653
+ visibleModels: next
1654
+ });
1655
+ } else update({
1656
+ showAll: false,
1657
+ visibleModels: [...draft.visibleModels, id]
1658
+ });
1659
+ };
1660
+ const renderModel = (model) => {
1661
+ const checked = draft.showAll || draft.visibleModels.includes(model.id);
1662
+ const hasPrice = model.cost.input > 0 || model.cost.output > 0;
1663
+ const zen = !model.free && model.limits === null;
1664
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1665
+ className: "oc-model",
1666
+ children: [
1667
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1668
+ type: "checkbox",
1669
+ checked,
1670
+ onChange: () => toggleModel(model.id)
1671
+ }),
1672
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1673
+ className: "oc-model-name",
1674
+ title: model.name + " · " + model.description,
1675
+ children: [
1676
+ model.name,
1677
+ model.free ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1678
+ className: "oc-badge",
1679
+ title: t("legendFree"),
1680
+ children: [" ", t("free")]
1681
+ }) : null,
1682
+ !model.free && hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1683
+ className: "oc-badge",
1684
+ title: t("legendPrice"),
1685
+ children: [
1686
+ " ",
1687
+ "$",
1688
+ model.cost.input,
1689
+ "/",
1690
+ "$",
1691
+ model.cost.output
1692
+ ]
1693
+ }) : null,
1694
+ !model.free && !hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1695
+ className: "oc-badge",
1696
+ title: t("legendUnknownPrice"),
1697
+ children: t("priceUnknown")
1698
+ }) : null,
1699
+ model.limits === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1700
+ className: "oc-badge",
1701
+ title: describeLimitsFull(model.limits),
1702
+ children: [
1703
+ "Go ",
1704
+ formatRequests(model.limits.go.fiveHour),
1705
+ "/5h"
1706
+ ]
1707
+ }),
1708
+ zen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1709
+ className: "oc-badge",
1710
+ title: t("legendZen"),
1711
+ children: t("zenBadge")
1712
+ }) : null,
1713
+ model.input.includes("image") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1714
+ className: "oc-badge",
1715
+ title: t("legendImage"),
1716
+ children: [" ", t("vision")]
1717
+ }) : null
1718
+ ]
1719
+ }),
1720
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1721
+ className: "oc-select",
1722
+ value: draft.modelTransports[model.id] ?? "",
1723
+ onChange: (event) => {
1724
+ const value = event.target.value;
1725
+ const modelTransports = { ...draft.modelTransports };
1726
+ if (value === "") delete modelTransports[model.id];
1727
+ else modelTransports[model.id] = value;
1728
+ update({ modelTransports });
1729
+ },
1730
+ children: [
1731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1732
+ value: "",
1733
+ children: [
1734
+ t("transportAuto"),
1735
+ " (",
1736
+ model.transport,
1737
+ ")"
1738
+ ]
1739
+ }),
1740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1741
+ value: "openai-chat",
1742
+ children: "openai-chat"
1743
+ }),
1744
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1745
+ value: "openai-responses",
1746
+ children: "openai-responses"
1747
+ }),
1748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1749
+ value: "anthropic",
1750
+ children: "anthropic"
1751
+ }),
1752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1753
+ value: "google",
1754
+ children: "google"
1755
+ })
1756
+ ]
1757
+ })
1758
+ ]
1759
+ }, model.id);
1760
+ };
1761
+ const renderSection = (title, hint, rows) => {
1762
+ if (rows.length === 0) return null;
1763
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1764
+ className: "oc-model-section",
1765
+ children: [
1766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1767
+ className: "oc-model-section-head",
1768
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1769
+ className: "oc-badge",
1770
+ children: rows.length
1771
+ })]
1772
+ }),
1773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1774
+ className: "oc-hint",
1775
+ children: hint
1776
+ }),
1777
+ rows.map(renderModel)
1778
+ ]
1779
+ }, title);
1780
+ };
1781
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1782
+ className: "oc-page",
1783
+ children: [
1784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1785
+ className: "oc-head",
1786
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1787
+ className: "oc-title",
1788
+ children: t("title")
1789
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1790
+ className: "oc-hint",
1791
+ children: t("intro")
1792
+ })]
1793
+ }),
1794
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1795
+ className: "oc-error",
1796
+ children: error
1797
+ }),
1798
+ notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1799
+ className: "oc-notice",
1800
+ children: notice
1801
+ }),
1802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginUpdateCard, { t }),
1803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1804
+ className: "oc-card",
1805
+ children: [
1806
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1807
+ className: "oc-card-title",
1808
+ children: [
1809
+ t("key"),
1810
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1811
+ className: `oc-badge ${state.key.configured ? "oc-badge-ok" : "oc-badge-warn"}`,
1812
+ children: state.key.configured ? t("keyConfigured") : t("keyMissing")
1813
+ }),
1814
+ state.key.source === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1815
+ className: "oc-badge",
1816
+ children: state.key.source
1817
+ }),
1818
+ (() => {
1819
+ const mark = accountMark(state.accounts.find((account) => account.default), t);
1820
+ return mark === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1821
+ className: "oc-badge oc-badge-bad",
1822
+ children: mark
1823
+ });
1824
+ })()
1825
+ ]
1826
+ }),
1827
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1828
+ className: "oc-hint",
1829
+ children: t("keyHint")
1830
+ }),
1831
+ (() => {
1832
+ const windows = defaultAccount?.usage?.report?.windows;
1833
+ if (windows === void 0) return null;
1834
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1835
+ className: "oc-row",
1836
+ children: [
1837
+ usageChip(t("fiveHour"), windows.fiveHour),
1838
+ usageChip(t("weekly"), windows.weekly),
1839
+ usageChip(t("monthly"), windows.monthly)
1840
+ ]
1841
+ });
1842
+ })(),
1843
+ (() => {
1844
+ const usageError = defaultAccount?.usage?.error;
1845
+ return usageError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1846
+ className: "oc-hint",
1847
+ children: [
1848
+ t("usageError"),
1849
+ ": ",
1850
+ usageError
1851
+ ]
1852
+ });
1853
+ })(),
1854
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1855
+ className: "oc-row",
1856
+ children: [
1857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1858
+ className: "oc-input",
1859
+ style: { flex: "1 1 260px" },
1860
+ type: "password",
1861
+ autoComplete: "off",
1862
+ placeholder: t("pasteKey"),
1863
+ value: keyDraft,
1864
+ onChange: (event) => setKeyDraft(event.target.value)
1865
+ }),
1866
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1867
+ type: "button",
1868
+ className: "oc-button oc-button-primary",
1869
+ disabled: keyDraft.trim() === "",
1870
+ onClick: () => {
1871
+ saveKey(draft.apiKeyEnv.trim() === "" ? state.config.apiKeyEnv : draft.apiKeyEnv.trim(), keyDraft.trim()).then(() => {
1872
+ setKeyDraft("");
1873
+ setNotice(t("saved"));
1874
+ return load();
1875
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
1876
+ },
1877
+ children: t("saveKey")
1878
+ }),
1879
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1880
+ type: "button",
1881
+ className: "oc-button oc-button-danger",
1882
+ onClick: () => {
1883
+ clearKey(draft.apiKeyEnv.trim() === "" ? state.config.apiKeyEnv : draft.apiKeyEnv.trim()).then(() => {
1884
+ setNotice(t("saved"));
1885
+ return load();
1886
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
1887
+ },
1888
+ children: t("clearKey")
1889
+ }),
1890
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1891
+ type: "button",
1892
+ className: "oc-button",
1893
+ onClick: () => {
1894
+ importAuth(draft.openCodeAuthEntry.trim() === "" ? state.config.openCodeAuthEntry : draft.openCodeAuthEntry.trim()).then(() => {
1895
+ setNotice(t("imported"));
1896
+ return load();
1897
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
1898
+ },
1899
+ children: t("importAuth")
1900
+ })
1901
+ ]
1902
+ }),
1903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1904
+ className: "oc-row",
1905
+ children: [
1906
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1907
+ className: "oc-select",
1908
+ value: testModel,
1909
+ onChange: (event) => setTestModel(event.target.value),
1910
+ children: state.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1911
+ value: model.id,
1912
+ children: model.name
1913
+ }, model.id))
1914
+ }),
1915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1916
+ type: "button",
1917
+ className: "oc-button",
1918
+ disabled: testing || testModel === "",
1919
+ onClick: () => {
1920
+ setTesting(true);
1921
+ setTestResult(null);
1922
+ testConnection(testModel).then(setTestResult).catch((caught) => setTestResult({
1923
+ ok: false,
1924
+ model: testModel,
1925
+ provider: "opencode-go",
1926
+ latencyMs: 0,
1927
+ error: caught instanceof Error ? caught.message : t("failed")
1928
+ })).finally(() => setTesting(false));
1929
+ },
1930
+ children: testing ? t("testing") : t("testConnection")
1931
+ }),
1932
+ testResult === null ? null : testResult.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1933
+ className: "oc-notice",
1934
+ children: [t("testOk").replace("{ms}", String(testResult.latencyMs)), testResult.text === void 0 || testResult.text === "" ? "" : ` · ${testResult.text.slice(0, 60)}`]
1935
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1936
+ className: "oc-error",
1937
+ children: [
1938
+ t("testFailed"),
1939
+ ": ",
1940
+ testResult.error
1941
+ ]
1942
+ })
1943
+ ]
1944
+ })
1945
+ ]
1946
+ }),
1947
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1948
+ className: "oc-card",
1949
+ children: [
1950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1951
+ className: "oc-card-title",
1952
+ children: [
1953
+ t("quota"),
1954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1955
+ className: "oc-badge",
1956
+ children: activeAccount?.label ?? t("noActiveAccount")
1957
+ }),
1958
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1959
+ type: "button",
1960
+ className: "oc-button",
1961
+ onClick: () => {
1962
+ refresh();
1963
+ },
1964
+ children: t("refresh")
1965
+ })
1966
+ ]
1967
+ }),
1968
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBars, {
1969
+ usage: state.usage,
1970
+ t
1971
+ }),
1972
+ (() => {
1973
+ const windows = state.usage?.report?.windows;
1974
+ const worst = Math.max(windows?.fiveHour?.percentUsed ?? 0, windows?.weekly?.percentUsed ?? 0, windows?.monthly?.percentUsed ?? 0);
1975
+ if (worst >= 95) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1976
+ className: "oc-error",
1977
+ children: t("quotaExhausted")
1978
+ });
1979
+ if (worst >= 80) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1980
+ className: "oc-notice",
1981
+ children: t("quotaWarning")
1982
+ });
1983
+ return null;
1984
+ })()
1985
+ ]
1986
+ }),
1987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1988
+ className: "oc-card",
1989
+ children: [
1990
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
1991
+ className: "oc-card-title",
1992
+ children: [
1993
+ t("accounts"),
1994
+ " ",
1995
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1996
+ className: "oc-badge",
1997
+ children: [
1998
+ draft.accounts.length,
1999
+ " ",
2000
+ t("accountCount")
2001
+ ]
2002
+ })
2003
+ ]
2004
+ }),
2005
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2006
+ className: "oc-hint",
2007
+ children: t("accountsHint")
2008
+ }),
2009
+ draft.accounts.map((account, index) => {
2010
+ const known = state.accounts.find((row) => account.apiKeyEnv !== "" && row.ref === account.apiKeyEnv || account.label !== "" && row.label === account.label);
2011
+ const configured = known?.configured === true;
2012
+ const draftKey = keyDrafts[index] ?? "";
2013
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2014
+ className: "oc-account-block",
2015
+ children: [
2016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2017
+ className: "oc-account",
2018
+ children: [
2019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2020
+ className: "oc-input",
2021
+ placeholder: t("accountLabel"),
2022
+ value: account.label,
2023
+ onChange: (event) => {
2024
+ const accounts = draft.accounts.map((entry, i) => i === index ? {
2025
+ ...entry,
2026
+ label: event.target.value
2027
+ } : entry);
2028
+ update({ accounts });
2029
+ }
2030
+ }),
2031
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2032
+ className: "oc-input",
2033
+ placeholder: t("accountRef"),
2034
+ value: account.apiKeyEnv,
2035
+ onChange: (event) => {
2036
+ const accounts = draft.accounts.map((entry, i) => i === index ? {
2037
+ ...entry,
2038
+ apiKeyEnv: event.target.value
2039
+ } : entry);
2040
+ update({ accounts });
2041
+ }
2042
+ }),
2043
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2044
+ className: `oc-badge ${configured ? "oc-badge-ok" : "oc-badge-warn"}`,
2045
+ children: configured ? t("keyConfigured") : t("keyMissing")
2046
+ }),
2047
+ known === void 0 || known.mark === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2048
+ className: "oc-badge oc-badge-bad",
2049
+ children: accountMark(known, t)
2050
+ })
2051
+ ]
2052
+ }),
2053
+ known?.usage?.report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2054
+ className: "oc-row",
2055
+ children: [
2056
+ usageChip(t("fiveHour"), known.usage.report.windows.fiveHour),
2057
+ usageChip(t("weekly"), known.usage.report.windows.weekly),
2058
+ usageChip(t("monthly"), known.usage.report.windows.monthly)
2059
+ ]
2060
+ }),
2061
+ known?.usage?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2062
+ className: "oc-hint",
2063
+ children: [
2064
+ t("usageError"),
2065
+ ": ",
2066
+ known.usage.error
2067
+ ]
2068
+ }),
2069
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2070
+ className: "oc-row",
2071
+ children: [
2072
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2073
+ className: "oc-input",
2074
+ style: { flex: "1 1 200px" },
2075
+ type: "password",
2076
+ autoComplete: "off",
2077
+ placeholder: t("pasteKey"),
2078
+ value: draftKey,
2079
+ onChange: (event) => setKeyDrafts((current) => ({
2080
+ ...current,
2081
+ [index]: event.target.value
2082
+ }))
2083
+ }),
2084
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2085
+ type: "button",
2086
+ className: "oc-button oc-button-primary",
2087
+ disabled: draftKey.trim() === "" || account.apiKeyEnv.trim() === "",
2088
+ onClick: () => {
2089
+ saveKey(account.apiKeyEnv.trim(), draftKey.trim()).then(() => {
2090
+ setKeyDrafts((current) => ({
2091
+ ...current,
2092
+ [index]: ""
2093
+ }));
2094
+ setNotice(t("saved"));
2095
+ return load();
2096
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
2097
+ },
2098
+ children: t("saveKey")
2099
+ }),
2100
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2101
+ type: "button",
2102
+ className: "oc-button",
2103
+ onClick: () => update({ activeAccount: account.label !== "" ? account.label : account.apiKeyEnv }),
2104
+ children: t("pinAccount")
2105
+ }),
2106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2107
+ type: "button",
2108
+ className: "oc-button oc-button-danger",
2109
+ onClick: () => update({ accounts: draft.accounts.filter((_, i) => i !== index) }),
2110
+ children: t("removeAccount")
2111
+ })
2112
+ ]
2113
+ })
2114
+ ]
2115
+ }, index);
2116
+ }),
2117
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2118
+ className: "oc-row",
2119
+ children: [
2120
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2121
+ type: "button",
2122
+ className: "oc-button",
2123
+ onClick: () => update({ accounts: [...draft.accounts, {
2124
+ label: "",
2125
+ apiKeyEnv: ""
2126
+ }] }),
2127
+ children: t("addAccount")
2128
+ }),
2129
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2130
+ className: "oc-badge",
2131
+ children: [
2132
+ t("activeAccount"),
2133
+ ": ",
2134
+ draft.activeAccount === "" ? t("auto") : draft.activeAccount
2135
+ ]
2136
+ }),
2137
+ draft.activeAccount === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2138
+ type: "button",
2139
+ className: "oc-button",
2140
+ onClick: () => update({ activeAccount: "" }),
2141
+ children: t("auto")
2142
+ })
2143
+ ]
2144
+ })
2145
+ ]
2146
+ }),
2147
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2148
+ className: "oc-card",
2149
+ children: [
2150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2151
+ className: "oc-card-title",
2152
+ children: t("modelAccountRules")
2153
+ }),
2154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2155
+ className: "oc-hint",
2156
+ children: t("modelAccountRulesHint")
2157
+ }),
2158
+ draft.modelAccountRules.map((rule, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2159
+ className: "oc-row",
2160
+ children: [
2161
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2162
+ className: "oc-input",
2163
+ placeholder: t("rulePattern"),
2164
+ value: rule.pattern,
2165
+ onChange: (event) => {
2166
+ const modelAccountRules = draft.modelAccountRules.map((entry, i) => i === index ? {
2167
+ ...entry,
2168
+ pattern: event.target.value
2169
+ } : entry);
2170
+ update({ modelAccountRules });
2171
+ }
2172
+ }),
2173
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2174
+ className: "oc-input",
2175
+ list: "oc-account-labels",
2176
+ placeholder: t("ruleAccount"),
2177
+ value: rule.account,
2178
+ onChange: (event) => {
2179
+ const modelAccountRules = draft.modelAccountRules.map((entry, i) => i === index ? {
2180
+ ...entry,
2181
+ account: event.target.value
2182
+ } : entry);
2183
+ update({ modelAccountRules });
2184
+ }
2185
+ }),
2186
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2187
+ type: "button",
2188
+ className: "oc-button oc-button-danger",
2189
+ onClick: () => update({ modelAccountRules: draft.modelAccountRules.filter((_, i) => i !== index) }),
2190
+ children: t("removeRule")
2191
+ })
2192
+ ]
2193
+ }, index)),
2194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("datalist", {
2195
+ id: "oc-account-labels",
2196
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "default" }), draft.accounts.map((account, index) => account.label.trim() === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: account.label.trim() }, `label-${index}`))]
2197
+ }),
2198
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2199
+ type: "button",
2200
+ className: "oc-button",
2201
+ onClick: () => update({ modelAccountRules: [...draft.modelAccountRules, {
2202
+ pattern: "",
2203
+ account: ""
2204
+ }] }),
2205
+ children: t("addRule")
2206
+ })
2207
+ ]
2208
+ }),
2209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2210
+ className: "oc-card",
2211
+ children: [
2212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
2213
+ className: "oc-card-title",
2214
+ children: [
2215
+ t("models"),
2216
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2217
+ className: "oc-badge",
2218
+ children: [
2219
+ state.models.length,
2220
+ " ",
2221
+ t("modelCount")
2222
+ ]
2223
+ }),
2224
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2225
+ type: "button",
2226
+ className: "oc-button",
2227
+ onClick: () => update({
2228
+ showAll: true,
2229
+ visibleModels: state.models.map((model) => model.id)
2230
+ }),
2231
+ children: t("showAll")
2232
+ }),
2233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2234
+ type: "button",
2235
+ className: "oc-button",
2236
+ onClick: () => {
2237
+ refreshModels().then((next) => {
2238
+ setState(next);
2239
+ setNotice(t("refreshed"));
2240
+ }).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
2241
+ },
2242
+ children: t("refresh")
2243
+ })
2244
+ ]
2245
+ }),
2246
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2247
+ className: "oc-hint",
2248
+ children: t("modelsHint")
2249
+ }),
2250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2251
+ className: "oc-row",
2252
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2253
+ type: "checkbox",
2254
+ checked: draft.filterModelsByPlan,
2255
+ onChange: (event) => update({ filterModelsByPlan: event.target.checked })
2256
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("filterModelsByPlan") })]
2257
+ }),
2258
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2259
+ className: "oc-hint",
2260
+ children: t("filterModelsByPlanHint")
2261
+ }),
2262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2263
+ className: "oc-hint",
2264
+ children: t("limitsHint")
2265
+ }),
2266
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2267
+ className: "oc-model-legend",
2268
+ children: [
2269
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2270
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2271
+ className: "oc-badge",
2272
+ children: t("free")
2273
+ }),
2274
+ " ",
2275
+ t("legendFree")
2276
+ ] }),
2277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2278
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2279
+ className: "oc-badge",
2280
+ children: "$in/$out"
2281
+ }),
2282
+ " ",
2283
+ t("legendPrice")
2284
+ ] }),
2285
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2287
+ className: "oc-badge",
2288
+ children: "Go N/5h"
2289
+ }),
2290
+ " ",
2291
+ t("legendGo")
2292
+ ] }),
2293
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2295
+ className: "oc-badge",
2296
+ children: t("zenBadge")
2297
+ }),
2298
+ " ",
2299
+ t("legendZen")
2300
+ ] }),
2301
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2302
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2303
+ className: "oc-badge",
2304
+ children: t("vision")
2305
+ }),
2306
+ " ",
2307
+ t("legendImage")
2308
+ ] })
2309
+ ]
2310
+ }),
2311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2312
+ className: "oc-input",
2313
+ placeholder: t("modelSearch"),
2314
+ value: search,
2315
+ onChange: (event) => setSearch(event.target.value)
2316
+ }),
2317
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2318
+ className: "oc-models",
2319
+ children: models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2320
+ className: "oc-hint",
2321
+ children: t("modelsEmpty")
2322
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2323
+ renderSection(t("sectionFree"), t("sectionFreeHint"), freeModels),
2324
+ renderSection(t("sectionGo"), t("sectionGoHint"), goModels),
2325
+ renderSection(t("sectionZen"), t("sectionZenHint"), zenModels)
2326
+ ] })
2327
+ }),
2328
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
2329
+ className: "oc-limits",
2330
+ children: [
2331
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("limitsTitle") }),
2332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2333
+ className: "oc-hint",
2334
+ children: t("limitsWindow")
2335
+ }),
2336
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
2337
+ className: "oc-table",
2338
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2339
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("model") }),
2340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Go" }),
2341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Go Plus" })
2342
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: state.models.filter((model) => model.limits !== null).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2343
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: model.name }),
2344
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: limitsCell(model.limits.go) }),
2345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: limitsCell(model.limits.goPlus) })
2346
+ ] }, model.id)) })]
2347
+ })
2348
+ ]
2349
+ })
2350
+ ]
2351
+ }),
2352
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2353
+ className: "oc-card",
2354
+ children: [
2355
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2356
+ className: "oc-card-title",
2357
+ children: t("advanced")
2358
+ }),
2359
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2360
+ className: "oc-grid",
2361
+ children: [
2362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2363
+ label: t("apiKeyEnv"),
2364
+ hint: t("apiKeyEnvHint"),
2365
+ value: draft.apiKeyEnv,
2366
+ onChange: (value) => update({ apiKeyEnv: value })
2367
+ }),
2368
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2369
+ label: t("openCodeAuthEntry"),
2370
+ hint: t("openCodeAuthEntryHint"),
2371
+ value: draft.openCodeAuthEntry,
2372
+ onChange: (value) => update({ openCodeAuthEntry: value }),
2373
+ list: "oc-auth-entries"
2374
+ }),
2375
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("datalist", {
2376
+ id: "oc-auth-entries",
2377
+ children: state.auth.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: entry }, entry))
2378
+ }),
2379
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2380
+ label: t("apiBase"),
2381
+ value: draft.apiBase,
2382
+ onChange: (value) => update({ apiBase: value })
2383
+ }),
2384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2385
+ label: t("goApiBase"),
2386
+ value: draft.goApiBase,
2387
+ onChange: (value) => update({ goApiBase: value })
2388
+ }),
2389
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2390
+ label: t("goZenBase"),
2391
+ value: draft.goZenBase,
2392
+ onChange: (value) => update({ goZenBase: value })
2393
+ }),
2394
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2395
+ label: t("usageUrl"),
2396
+ value: draft.usageUrl,
2397
+ onChange: (value) => update({ usageUrl: value })
2398
+ }),
2399
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2400
+ label: t("requestTimeoutMs"),
2401
+ type: "number",
2402
+ value: draft.requestTimeoutMs,
2403
+ onChange: (value) => update({ requestTimeoutMs: value })
2404
+ }),
2405
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2406
+ label: t("streamIdleTimeoutMs"),
2407
+ type: "number",
2408
+ value: draft.streamIdleTimeoutMs,
2409
+ onChange: (value) => update({ streamIdleTimeoutMs: value })
2410
+ }),
2411
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2412
+ label: t("maxOutputTokens"),
2413
+ type: "number",
2414
+ value: draft.maxOutputTokens,
2415
+ onChange: (value) => update({ maxOutputTokens: value })
2416
+ }),
2417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2418
+ label: t("contextWindow"),
2419
+ type: "number",
2420
+ value: draft.contextWindow,
2421
+ onChange: (value) => update({ contextWindow: value })
2422
+ })
2423
+ ]
2424
+ }),
2425
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2426
+ className: "oc-row",
2427
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2428
+ type: "checkbox",
2429
+ checked: draft.showSidebarQuota,
2430
+ onChange: (event) => update({ showSidebarQuota: event.target.checked })
2431
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("showSidebarQuota") })]
2432
+ })
2433
+ ]
2434
+ }),
2435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2436
+ className: "oc-row",
2437
+ children: [
2438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2439
+ type: "button",
2440
+ className: "oc-button oc-button-primary",
2441
+ disabled: !dirty || saving,
2442
+ onClick: () => {
2443
+ save();
2444
+ },
2445
+ children: saving ? t("saving") : t("save")
2446
+ }),
2447
+ dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2448
+ className: "oc-badge oc-badge-warn",
2449
+ children: t("unsaved")
2450
+ }) : null,
2451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2452
+ type: "button",
2453
+ className: "oc-button",
2454
+ onClick: () => {
2455
+ if (props.close !== void 0) props.close();
2456
+ else {
2457
+ setDraft(draftOf(state));
2458
+ setNotice(null);
2459
+ setError(null);
2460
+ }
2461
+ },
2462
+ children: t("close")
2463
+ })
2464
+ ]
2465
+ })
2466
+ ]
2467
+ });
2468
+ }
2469
+ //#endregion
2470
+ //#region src/client/styles.ts
2471
+ const CSS_ID = "dsh-opencode-provider";
2472
+ const CSS = `
2473
+ :where(.oc-page, .oc-panel, .oc-card, .oc-foot, .oc-railButton) {
2474
+ --oc-border: var(--dsw-alias-border-l3, rgba(127,127,127,0.25));
2475
+ --oc-border-strong: var(--dsw-alias-border-l3, rgba(127,127,127,0.35));
2476
+ --oc-border-soft: var(--dsw-alias-border-l3, rgba(127,127,127,0.18));
2477
+ --oc-fill: var(--dsw-alias-settings-card-fill, rgba(127,127,127,0.06));
2478
+ --oc-fill-soft: var(--dsw-alias-bg-layer-2, rgba(127,127,127,0.08));
2479
+ --oc-fill-hover: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,0.12));
2480
+ --oc-text: var(--dsw-alias-label-primary, #e6e6e6);
2481
+ --oc-text-secondary: var(--dsw-alias-label-secondary, #a0a0a0);
2482
+ --oc-text-tertiary: var(--dsw-alias-label-tertiary, #7a7a7a);
2483
+ --oc-accent: var(--dsw-alias-state-business-primary, #508cff);
2484
+ --oc-accent-soft: color-mix(in srgb, var(--oc-accent) 16%, transparent);
2485
+ --oc-accent-border: color-mix(in srgb, var(--oc-accent) 55%, transparent);
2486
+ --oc-success: var(--dsw-alias-state-success-primary, #4fbf86);
2487
+ --oc-success-border: color-mix(in srgb, var(--oc-success) 50%, transparent);
2488
+ --oc-warning: var(--dsw-alias-state-warning-primary, #d9a24a);
2489
+ --oc-warning-border: color-mix(in srgb, var(--oc-warning) 50%, transparent);
2490
+ --oc-danger: var(--dsw-alias-state-error-primary, #e07a7a);
2491
+ --oc-danger-soft: color-mix(in srgb, var(--oc-danger) 12%, transparent);
2492
+ --oc-danger-border: color-mix(in srgb, var(--oc-danger) 35%, transparent);
2493
+ --oc-focus: var(--dsw-focus-ring-color, var(--oc-accent));
2494
+ }
2495
+ .oc-page { display: flex; flex-direction: column; gap: 14px; padding: 4px 2px 24px; color: inherit; }
2496
+ .oc-head { display: flex; flex-direction: column; gap: 4px; }
2497
+ .oc-title { font-size: 18px; font-weight: 650; margin: 0; }
2498
+ .oc-hint { font-size: 12px; opacity: 0.72; line-height: 1.5; margin: 0; }
2499
+ .oc-card { border: 1px solid var(--oc-border); border-radius: 10px; padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; background: var(--oc-fill); }
2500
+ .oc-card-title { font-size: 13px; font-weight: 650; margin: 0; display: flex; align-items: center; gap: 8px; }
2501
+ .oc-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
2502
+ .oc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
2503
+ .oc-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
2504
+ .oc-label { font-size: 12px; font-weight: 550; opacity: 0.86; }
2505
+ .oc-input, .oc-select { font: inherit; font-size: 13px; padding: 6px 8px; border-radius: 7px; border: 1px solid var(--oc-border-strong); background: var(--oc-fill-soft); color: inherit; min-width: 0; }
2506
+ .oc-input:focus, .oc-select:focus { outline: 2px solid var(--oc-focus); outline-offset: 1px; }
2507
+ .oc-button { font: inherit; font-size: 13px; padding: 6px 11px; border-radius: 7px; border: 1px solid var(--oc-border-strong); background: var(--oc-fill-hover); color: inherit; cursor: pointer; }
2508
+ .oc-button:hover:not(:disabled) { background: var(--oc-fill-hover); }
2509
+ .oc-button:disabled { opacity: 0.5; cursor: default; }
2510
+ .oc-button-primary { border-color: var(--oc-accent-border); background: var(--oc-accent-soft); }
2511
+ .oc-button-danger { border-color: var(--oc-danger-border); color: var(--oc-danger); }
2512
+ .oc-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; padding: 2px 7px; border-radius: 999px; border: 1px solid var(--oc-border-strong); background: var(--oc-fill-hover); }
2513
+ .oc-badge-ok { border-color: var(--oc-success-border); color: var(--oc-success); }
2514
+ .oc-badge-warn { border-color: var(--oc-warning-border); color: var(--oc-warning); }
2515
+ .oc-badge-bad { border-color: var(--oc-danger-border); color: var(--oc-danger); }
2516
+ .oc-heat-wrap { display: flex; flex-direction: column; gap: 8px; }
2517
+ .oc-analytics-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
2518
+ .oc-tabs { display: flex; gap: 4px; }
2519
+ .oc-tab { border: 1px solid transparent; border-radius: 999px; background: transparent; color: var(--oc-text-secondary); padding: 4px 11px; font: inherit; font-size: 12px; cursor: pointer; }
2520
+ .oc-tab:hover { background: var(--oc-fill-hover); color: var(--oc-text); }
2521
+ .oc-tab-active { border-color: var(--oc-border); background: var(--oc-fill-soft); color: var(--oc-text); }
2522
+ .oc-kpi-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-top: 12px; }
2523
+ .oc-kpi { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 10px 12px; border: 1px solid var(--oc-border); border-radius: var(--dsw-radius-md, 8px); background: var(--oc-fill); }
2524
+ .oc-kpi-value { overflow: hidden; color: var(--oc-text); font-size: 18px; font-weight: 600; line-height: 24px; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
2525
+ .oc-kpi-label { color: var(--oc-text-tertiary); font-size: 11px; line-height: 16px; }
2526
+ .oc-table-scroll { overflow-x: auto; }
2527
+ @media (max-width: 720px) { .oc-kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
2528
+ .oc-heat-scroll { overflow-x: auto; padding-bottom: 4px; }
2529
+ .oc-heat { display: grid; grid-template-columns: 32px repeat(24, minmax(12px, 1fr)); gap: 2px; align-items: center; min-width: 600px; }
2530
+ .oc-heat-hour { font-size: 9px; color: var(--oc-text-tertiary); text-align: center; }
2531
+ .oc-heat-day { font-size: 10px; color: var(--oc-text-secondary); }
2532
+ .oc-heat-cell { height: 14px; border-radius: 3px; }
2533
+ .oc-heat-legend { display: flex; align-items: center; gap: 4px; font-size: 10px; color: var(--oc-text-tertiary); }
2534
+ .oc-heat-swatch { width: 12px; height: 12px; border-radius: 2px; }
2535
+ .oc-hour-strip-head { margin: 10px 0 0; font-size: 10px; color: var(--oc-text-tertiary); }
2536
+ .oc-hour-strip { display: grid; grid-template-columns: 32px repeat(24, minmax(12px, 1fr)); gap: 2px; align-items: end; height: 30px; min-width: 600px; }
2537
+ .oc-hour-col { display: flex; align-items: flex-end; height: 100%; }
2538
+ .oc-hour-bar { display: block; width: 100%; min-height: 2px; border-radius: 2px 2px 0 0; }
2539
+ .oc-hour-labels { display: grid; grid-template-columns: 32px repeat(24, minmax(12px, 1fr)); gap: 2px; font-size: 9px; color: var(--oc-text-tertiary); text-align: center; min-width: 600px; }
2540
+ .oc-bars { display: flex; flex-direction: column; gap: 8px; }
2541
+ .oc-bar-row { display: grid; grid-template-columns: 72px 1fr auto; gap: 10px; align-items: center; font-size: 12px; }
2542
+ .oc-bar { height: 8px; border-radius: 999px; background: var(--oc-fill-hover); overflow: hidden; }
2543
+ .oc-bar-fill { height: 100%; border-radius: 999px; background: currentColor; opacity: 0.65; }
2544
+ .oc-bar-fill-warn { background: var(--oc-warning); opacity: 1; }
2545
+ .oc-bar-fill-bad { background: var(--oc-danger); opacity: 1; }
2546
+ .oc-models { display: flex; flex-direction: column; gap: 6px; max-height: 340px; overflow: auto; padding-right: 4px; }
2547
+ .oc-model { display: grid; grid-template-columns: 22px minmax(0,1fr) 120px; gap: 8px; align-items: center; font-size: 12px; padding: 3px 0; }
2548
+ .oc-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2549
+ .oc-model-legend { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; color: var(--oc-text-secondary); font-size: 11px; }
2550
+ .oc-model-legend > span { display: inline-flex; align-items: center; gap: 5px; }
2551
+ .oc-model-section { display: flex; flex-direction: column; gap: 4px; padding-top: 8px; border-top: 1px solid var(--oc-border-soft); }
2552
+ .oc-model-section:first-child { border-top: 0; padding-top: 0; }
2553
+ .oc-model-section-head { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; font-weight: 650; }
2554
+ .oc-model-section-head .oc-badge { padding: 0 6px; font-size: 10px; }
2555
+ .oc-model-section .oc-hint { margin: 0 0 4px; }
2556
+ .oc-account { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr) auto; gap: 8px; align-items: center; }
2557
+ .oc-account-block { display: flex; flex-direction: column; gap: 6px; padding: 9px; border: 1px solid var(--oc-border-soft); border-radius: 8px; background: var(--oc-fill); }
2558
+ .oc-notice { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-accent-soft); border: 1px solid var(--oc-accent-border); }
2559
+ .oc-error { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-danger-soft); border: 1px solid var(--oc-danger-border); color: var(--oc-danger); }
2560
+ [class*="_footerActions"]:has(.oc-foot), [class*="_footerActions"]:has(.oc-railButton) { flex-direction: column; }
2561
+ .oc-foot { box-sizing: border-box; flex: 1 1 auto; width: 100%; min-width: 0; display: flex; flex-direction: column; gap: 6px; margin: 0 0 4px; padding: 8px; border: 0.5px solid transparent; border-radius: var(--dsw-radius-md, 8px); background: transparent; color: var(--oc-text-secondary); font: inherit; text-align: left; cursor: pointer; }
2562
+ .oc-foot:hover { color: var(--oc-text); background: var(--oc-fill-hover); border-color: var(--oc-border); }
2563
+ .oc-foot:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--oc-focus); outline-offset: -2px; }
2564
+ .oc-footTop { display: flex; align-items: center; gap: 8px; min-width: 0; }
2565
+ .oc-footName { min-width: 0; overflow: hidden; color: var(--oc-text); font-size: 14px; font-weight: 400; line-height: 20px; white-space: nowrap; text-overflow: ellipsis; }
2566
+ .oc-footPct { flex: none; width: 34px; margin-left: auto; color: var(--oc-text-secondary); text-align: right; font-size: 12px; line-height: 16px; font-variant-numeric: tabular-nums; }
2567
+ .oc-footPctWarn { color: var(--oc-warning); }
2568
+ .oc-footPctBad { color: var(--oc-danger); }
2569
+ .oc-footRow { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
2570
+ .oc-footHead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-width: 0; }
2571
+ .oc-footLabel { flex: 1 1 auto; min-width: 0; overflow: hidden; color: var(--oc-text-tertiary); text-overflow: ellipsis; white-space: nowrap; font-size: 12px; line-height: 16px; }
2572
+ .oc-footAmount { flex: 0 0 auto; max-width: 68%; overflow: hidden; color: var(--oc-text-secondary); text-align: right; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; line-height: 16px; font-variant-numeric: tabular-nums; }
2573
+ .oc-footBar { display: block; height: 4px; border-radius: var(--dsw-radius-xs, 3px); background: var(--oc-fill-hover); overflow: hidden; }
2574
+ .oc-footFill { display: block; height: 100%; border-radius: var(--dsw-radius-xs, 3px); background: var(--oc-success); transition: width 0.3s ease; }
2575
+ .oc-footFillWarn { background: var(--oc-warning); }
2576
+ .oc-footFillBad { background: var(--oc-danger); }
2577
+ .oc-footMessage { color: var(--oc-text-tertiary); font-size: 11px; line-height: 16px; }
2578
+ .oc-railButton { box-sizing: border-box; flex: none; display: inline-flex; justify-content: center; align-items: center; width: 36px; height: 36px; margin: 0 0 4px; padding: 0; border: 0.5px solid transparent; border-radius: var(--dsw-radius-md, 8px); background: transparent; color: var(--oc-text-secondary); cursor: pointer; }
2579
+ .oc-railButton:hover { color: var(--oc-text); background: var(--oc-fill-hover); }
2580
+ .oc-railButton:focus-visible { outline: var(--dsw-focus-ring-width, 2px) solid var(--oc-focus); outline-offset: -2px; }
2581
+ .oc-ring { flex: none; display: inline-flex; }
2582
+ .oc-panel { display: flex; flex-direction: column; gap: 14px; padding: 18px; max-width: 760px; margin: 0 auto; height: 100%; max-height: calc(100vh - 56px); min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
2583
+ .oc-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
2584
+ .oc-limits { margin-top: 8px; }
2585
+ .oc-limits summary { cursor: pointer; font-size: 12px; font-weight: 600; padding: 4px 0; }
2586
+ .oc-table { width: 100%; border-collapse: collapse; font-size: 12px; }
2587
+ .oc-table th, .oc-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--oc-border-soft); vertical-align: top; }
2588
+ .oc-table th { font-weight: 600; opacity: 0.8; }
2589
+ `;
2590
+ //#endregion
2591
+ //#region src/client/index.tsx
2592
+ function injectCss() {
2593
+ if (typeof document === "undefined") return () => {};
2594
+ if (document.querySelector(`style[data-plugin-css="dsh-opencode-provider"]`) !== null) return () => {};
2595
+ const tag = document.createElement("style");
2596
+ tag.dataset.plugin = "dsh-opencode-provider";
2597
+ tag.dataset.pluginCss = CSS_ID;
2598
+ tag.textContent = CSS;
2599
+ document.head.appendChild(tag);
2600
+ return () => tag.remove();
2601
+ }
2602
+ const inject = ["slots", "locale"];
2603
+ /**
2604
+ * Browser half: the OpenCode settings section, a quota card at the bottom of
2605
+ * the sidebar, and the center panel it opens.
2606
+ */
2607
+ function apply(ctx) {
2608
+ ctx.effect(() => ctx.locale.register(NS, "en", dictionaries.en), "dsh-opencode-provider: en copy");
2609
+ ctx.effect(() => ctx.locale.register(NS, "zh", dictionaries.zh), "dsh-opencode-provider: zh copy");
2610
+ ctx.effect(() => injectCss(), "dsh-opencode-provider: styles");
2611
+ const t = ctx.locale.bind(NS);
2612
+ const slots = ctx.slots;
2613
+ ctx.slots.inject("settings.section", () => slots.register({
2614
+ name: "settings.section",
2615
+ id: "opencode",
2616
+ order: 20,
2617
+ label: () => t("nav"),
2618
+ locale: NS
2619
+ }, OpenCodeSettingsPage));
2620
+ ctx.slots.inject("main", () => slots.register({
2621
+ name: "main",
2622
+ key: "opencode-panel",
2623
+ locale: NS,
2624
+ inject: () => ({ close: () => ctx.get("layout")?.selectPanel(null) })
2625
+ }, QuotaPanel));
2626
+ ctx.inject(["layout"], (layoutCtx) => {
2627
+ const layoutSlots = layoutCtx.slots;
2628
+ layoutCtx.slots.inject("sidebar.footer.action", () => layoutSlots.register({
2629
+ name: "sidebar.footer.action",
2630
+ id: "opencode-quota",
2631
+ order: 1,
2632
+ locale: NS,
2633
+ inject: () => ({ open: () => ctx.get("layout")?.selectPanel("opencode-panel") })
2634
+ }, QuotaCard));
2635
+ });
2636
+ }
2637
+ //#endregion
2638
+ exports.apply = apply;
2639
+ exports.inject = inject;
2640
+ return module.exports;
2641
+ }
2642
+ });
2643
+
2644
+ //# sourceMappingURL=client.js.map