dsh-subscriptions 0.1.4 → 0.2.0
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/README.md +31 -5
- package/lib/auth/device-flow.js +6 -3
- package/lib/auth/oauth-flow.js +41 -8
- package/lib/auth/rpc.d.ts +73 -2
- package/lib/auth/rpc.js +53 -3
- package/lib/client/ExternalUsageCards.js +1 -1
- package/lib/client/OllamaCard.d.ts +13 -0
- package/lib/client/OllamaCard.js +76 -0
- package/lib/client/OllamaSettingsManager.d.ts +24 -0
- package/lib/client/OllamaSettingsManager.js +246 -0
- package/lib/client/ProviderModelEditor.js +4 -2
- package/lib/client/SubscriptionUsageBadge.d.ts +1 -1
- package/lib/client/SubscriptionUsageBadge.js +3 -2
- package/lib/client/SubscriptionsSection.d.ts +2 -74
- package/lib/client/SubscriptionsSection.js +4 -67
- package/lib/client/UsageMeter.js +11 -7
- package/lib/client/locales.d.ts +68 -0
- package/lib/client/locales.js +77 -0
- package/lib/client/ollama-draft.d.ts +229 -0
- package/lib/client/ollama-draft.js +246 -0
- package/lib/client/usage-pace.d.ts +7 -0
- package/lib/client/usage-pace.js +15 -0
- package/lib/client.js +1385 -244
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +25 -0
- package/lib/index.js +11060 -9733
- package/lib/model-defaults.d.ts +4 -5
- package/lib/model-defaults.js +18 -14
- package/lib/provider-settings.d.ts +2 -1
- package/lib/provider-settings.js +6 -4
- package/lib/providers/antigravity.d.ts +0 -2
- package/lib/providers/antigravity.js +5 -9
- package/lib/providers/claude.d.ts +14 -1
- package/lib/providers/claude.js +25 -13
- package/lib/providers/codex-search.js +5 -2
- package/lib/providers/codex.js +7 -7
- package/lib/providers/common.d.ts +27 -0
- package/lib/providers/common.js +70 -0
- package/lib/providers/copilot.js +3 -3
- package/lib/providers/cursor-adapter.d.ts +35 -0
- package/lib/providers/cursor-adapter.js +245 -4
- package/lib/providers/cursor-auth.js +11 -4
- package/lib/providers/cursor-usage.js +2 -1
- package/lib/providers/external-usage-controller.d.ts +9 -2
- package/lib/providers/external-usage-controller.js +19 -5
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +93 -8
- package/lib/providers/grok.js +14 -9
- package/lib/providers/minimax-usage.js +2 -1
- package/lib/providers/ollama-settings.d.ts +90 -0
- package/lib/providers/ollama-settings.js +197 -0
- package/lib/providers/ollama-web.d.ts +46 -0
- package/lib/providers/ollama-web.js +213 -0
- package/lib/providers/ollama.d.ts +107 -0
- package/lib/providers/ollama.js +545 -0
- package/lib/tools/image-generate.js +2 -1
- package/lib/tools/video-generate.d.ts +0 -5
- package/lib/tools/video-generate.js +25 -14
- package/lib/tools/x-search.js +2 -2
- package/lib/translate/resolved.d.ts +5 -2
- package/lib/translate/resolved.js +5 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -108,6 +108,7 @@ const en = {
|
|
|
108
108
|
usageRefresh: "Refresh",
|
|
109
109
|
usageLoading: "Loading usage…",
|
|
110
110
|
usageEmpty: "No usage windows reported.",
|
|
111
|
+
usageUnsupported: "This endpoint has no usage surface.",
|
|
111
112
|
usageError: "Usage lookup failed: {message}",
|
|
112
113
|
usageSession: "5-hour window",
|
|
113
114
|
usageWeekly: "Weekly",
|
|
@@ -233,7 +234,40 @@ const en = {
|
|
|
233
234
|
speedFast: "Fast",
|
|
234
235
|
speedFastDescription: "1.5x speed, more usage",
|
|
235
236
|
commandFast: "Switch the Codex speed tier (Standard/Fast)",
|
|
236
|
-
commandFastUnavailable: "The current model has no fast tier; /fast only works on Codex models whose catalog advertises one"
|
|
237
|
+
commandFastUnavailable: "The current model has no fast tier; /fast only works on Codex models whose catalog advertises one",
|
|
238
|
+
ollamaTitle: "Ollama Cloud",
|
|
239
|
+
ollamaStatus: "{count} model(s) in the picker",
|
|
240
|
+
ollamaStatusNone: "No models configured",
|
|
241
|
+
ollamaWebOn: "Web search/fetch enabled",
|
|
242
|
+
ollamaWebOff: "Web search/fetch off",
|
|
243
|
+
ollamaManageTitle: "Manage Ollama Cloud",
|
|
244
|
+
ollamaManageHint: "Fetch the Cloud list, tick the models this route serves, then Save. The picker shows the ticked models in order; the API key itself stays in DSH credentials.",
|
|
245
|
+
ollamaFetch: "Fetch cloud models",
|
|
246
|
+
ollamaFetching: "Fetching…",
|
|
247
|
+
ollamaKeyPlaceholder: "One-time API key (optional)",
|
|
248
|
+
ollamaKeyHint: "A key typed here is used for this fetch only and is never saved. Leave it blank to use the stored credential.",
|
|
249
|
+
ollamaFetchFailed: "Could not fetch cloud models: {message}",
|
|
250
|
+
ollamaCandidatesCount: "{count} returned",
|
|
251
|
+
ollamaCandidatesEmpty: "No cloud models returned.",
|
|
252
|
+
ollamaModelsEmpty: "No models configured. Fetch the cloud list and tick the ones to keep.",
|
|
253
|
+
ollamaModelsLoadFailed: "Could not load Ollama settings: {message}",
|
|
254
|
+
ollamaModelsSaveFailed: "Could not save Ollama settings: {message}",
|
|
255
|
+
ollamaConfigureHint: "No API key found at {ref}. Add it to the credentials file (~/.dsh/.credentials.yaml).",
|
|
256
|
+
ollamaDisplayName: "Display name",
|
|
257
|
+
ollamaRowDetails: "Details",
|
|
258
|
+
ollamaEffortsUnknown: "This model reasons; its level set will come from live discovery.",
|
|
259
|
+
ollamaMoveUp: "Move up",
|
|
260
|
+
ollamaMoveDown: "Move down",
|
|
261
|
+
ollamaBaseURL: "Base URL",
|
|
262
|
+
ollamaApiKeyRef: "API key reference",
|
|
263
|
+
ollamaConnectionHint: "The base URL receives this route's requests and the credential lookup; the reference names the DSH credential, not the key itself. Requests send the resolved key to that address, so point it only at a host you trust.",
|
|
264
|
+
ollamaWebHint: "Enables the Ollama Cloud web search and fetch providers. The host still has to select them before the web tools use them.",
|
|
265
|
+
ollamaCatalogOverride: "A profile pins this route's model list; the picker ignores store models until that override is removed.",
|
|
266
|
+
ollamaResetDefaults: "Restore deployment defaults",
|
|
267
|
+
ollamaResetConfirm: "Clear every saved Ollama override? The profile values and built-in defaults apply again.",
|
|
268
|
+
ollamaCapVision: "vision",
|
|
269
|
+
ollamaCapThinking: "thinking",
|
|
270
|
+
ollamaContext: "context {tokens}"
|
|
237
271
|
};
|
|
238
272
|
/** zh strings, one per {@link en} key. */
|
|
239
273
|
const zh = {
|
|
@@ -309,6 +343,7 @@ const zh = {
|
|
|
309
343
|
usageRefresh: "刷新",
|
|
310
344
|
usageLoading: "用量加载中…",
|
|
311
345
|
usageEmpty: "服务商未返回任何用量窗口。",
|
|
346
|
+
usageUnsupported: "该端点不提供用量查询。",
|
|
312
347
|
usageError: "用量查询失败:{message}",
|
|
313
348
|
usageSession: "5 小时窗口",
|
|
314
349
|
usageWeekly: "每周",
|
|
@@ -434,7 +469,40 @@ const zh = {
|
|
|
434
469
|
speedFast: "快速",
|
|
435
470
|
speedFastDescription: "约 1.5 倍速度,消耗更多用量",
|
|
436
471
|
commandFast: "切换 Codex 速度档(标准/快速)",
|
|
437
|
-
commandFastUnavailable: "当前模型不支持快速档;/fast 仅对目录声明了 fast tier 的 Codex 模型可用"
|
|
472
|
+
commandFastUnavailable: "当前模型不支持快速档;/fast 仅对目录声明了 fast tier 的 Codex 模型可用",
|
|
473
|
+
ollamaTitle: "Ollama Cloud",
|
|
474
|
+
ollamaStatus: "选择器中 {count} 个模型",
|
|
475
|
+
ollamaStatusNone: "尚未配置模型",
|
|
476
|
+
ollamaWebOn: "网页搜索/抓取已启用",
|
|
477
|
+
ollamaWebOff: "网页搜索/抓取已关闭",
|
|
478
|
+
ollamaManageTitle: "管理 Ollama Cloud",
|
|
479
|
+
ollamaManageHint: "选择此路由提供的云端模型后保存。模型选择器按此顺序显示这份列表;API 密钥本身仍保存在 DSH 凭据中。",
|
|
480
|
+
ollamaFetch: "拉取云端模型",
|
|
481
|
+
ollamaFetching: "拉取中…",
|
|
482
|
+
ollamaKeyPlaceholder: "一次性 API 密钥(可选)",
|
|
483
|
+
ollamaKeyHint: "在此输入的密钥仅用于本次拉取,不会保存。留空则使用已存凭据。",
|
|
484
|
+
ollamaFetchFailed: "拉取云端模型失败:{message}",
|
|
485
|
+
ollamaCandidatesCount: "返回 {count} 个",
|
|
486
|
+
ollamaCandidatesEmpty: "未返回任何云端模型。",
|
|
487
|
+
ollamaModelsEmpty: "尚未配置模型。请拉取云端列表并勾选需要保留的模型。",
|
|
488
|
+
ollamaModelsLoadFailed: "Ollama 设置加载失败:{message}",
|
|
489
|
+
ollamaModelsSaveFailed: "Ollama 设置保存失败:{message}",
|
|
490
|
+
ollamaConfigureHint: "未在 {ref} 找到 API 密钥。请在凭据文件(~/.dsh/.credentials.yaml)中添加。",
|
|
491
|
+
ollamaDisplayName: "显示名",
|
|
492
|
+
ollamaRowDetails: "详情",
|
|
493
|
+
ollamaEffortsUnknown: "该模型支持思考,推理档位将由实时发现补充。",
|
|
494
|
+
ollamaMoveUp: "上移",
|
|
495
|
+
ollamaMoveDown: "下移",
|
|
496
|
+
ollamaBaseURL: "基础 URL",
|
|
497
|
+
ollamaApiKeyRef: "API 密钥引用",
|
|
498
|
+
ollamaConnectionHint: "基础 URL 用于此路由的请求与凭据查询;引用是 DSH 凭据的名称,不是密钥本身。请求会把解析出的密钥发往该地址,请只填写可信主机。",
|
|
499
|
+
ollamaWebHint: "启用 Ollama Cloud 的网页搜索与抓取能力。宿主仍需选择它们,网页工具才会使用。",
|
|
500
|
+
ollamaCatalogOverride: "配置档案固定了此路由的模型列表;在移除该覆盖前,选择器会忽略存储中的模型。",
|
|
501
|
+
ollamaResetDefaults: "恢复部署默认值",
|
|
502
|
+
ollamaResetConfirm: "清除全部已保存的 Ollama 覆盖项?将重新使用配置档案值与内置默认值。",
|
|
503
|
+
ollamaCapVision: "视觉",
|
|
504
|
+
ollamaCapThinking: "思考",
|
|
505
|
+
ollamaContext: "上下文 {tokens}"
|
|
438
506
|
};
|
|
439
507
|
|
|
440
508
|
//#endregion
|
|
@@ -521,8 +589,10 @@ function mergeAccountChanges(latest, changes) {
|
|
|
521
589
|
|
|
522
590
|
//#endregion
|
|
523
591
|
//#region src/client/ProviderModelEditor.tsx
|
|
524
|
-
|
|
525
|
-
const
|
|
592
|
+
/** Providers whose runtime honours a local context-window override. */
|
|
593
|
+
const supportsContext = (provider) => provider === "codex" || provider === "claude";
|
|
594
|
+
const border$2 = "0.5px solid var(--dsw-alias-border-l2, #ddd)";
|
|
595
|
+
const actions$2 = {
|
|
526
596
|
display: "flex",
|
|
527
597
|
flexWrap: "wrap",
|
|
528
598
|
gap: 8,
|
|
@@ -595,7 +665,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
595
665
|
setError("");
|
|
596
666
|
const { accounts: _accounts,...settings } = {
|
|
597
667
|
...draft,
|
|
598
|
-
...provider
|
|
668
|
+
...supportsContext(provider) ? { contextWindows: windows } : {}
|
|
599
669
|
};
|
|
600
670
|
return {
|
|
601
671
|
settings,
|
|
@@ -625,7 +695,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
625
695
|
const selected = new Set(draft.visibleModels ?? allModels.map((model) => model.id));
|
|
626
696
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
627
697
|
style: {
|
|
628
|
-
borderTop: border$
|
|
698
|
+
borderTop: border$2,
|
|
629
699
|
marginTop: 12,
|
|
630
700
|
paddingTop: 12
|
|
631
701
|
},
|
|
@@ -648,7 +718,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
648
718
|
children: error
|
|
649
719
|
}),
|
|
650
720
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
651
|
-
style: actions$
|
|
721
|
+
style: actions$2,
|
|
652
722
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
653
723
|
type: "button",
|
|
654
724
|
className: "dsh-subscription-quiet",
|
|
@@ -688,7 +758,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
688
758
|
t("modelsAutomatic")
|
|
689
759
|
] }),
|
|
690
760
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
691
|
-
style: actions$
|
|
761
|
+
style: actions$2,
|
|
692
762
|
children: [
|
|
693
763
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
694
764
|
style: { flex: "1 1 180px" },
|
|
@@ -724,7 +794,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
724
794
|
},
|
|
725
795
|
children: [models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
726
796
|
style: {
|
|
727
|
-
borderBottom: border$
|
|
797
|
+
borderBottom: border$2,
|
|
728
798
|
padding: "8px 2px",
|
|
729
799
|
display: "grid",
|
|
730
800
|
gap: 8
|
|
@@ -750,9 +820,9 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
750
820
|
!known.has(model.id) && ` (${t("modelsUnavailable")})`
|
|
751
821
|
]
|
|
752
822
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
753
|
-
style: actions$
|
|
823
|
+
style: actions$2,
|
|
754
824
|
children: [(model.efforts?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
755
|
-
style: actions$
|
|
825
|
+
style: actions$2,
|
|
756
826
|
children: [t("modelDefaultsTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
757
827
|
"aria-label": `${model.name} ${t("modelDefaultsTitle")}`,
|
|
758
828
|
value: efforts[model.id] ?? "",
|
|
@@ -784,10 +854,10 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
784
854
|
}, effort.id))
|
|
785
855
|
]
|
|
786
856
|
})]
|
|
787
|
-
}), provider
|
|
788
|
-
style: actions$
|
|
857
|
+
}), supportsContext(provider) && model.maxContextWindow !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
858
|
+
style: actions$2,
|
|
789
859
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
790
|
-
style: actions$
|
|
860
|
+
style: actions$2,
|
|
791
861
|
children: [t("modelsContext"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
792
862
|
style: { width: 140 },
|
|
793
863
|
inputMode: "numeric",
|
|
@@ -810,7 +880,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
810
880
|
})]
|
|
811
881
|
}, model.id)), models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelDefaultsFilterEmpty", { query }) })]
|
|
812
882
|
}),
|
|
813
|
-
provider
|
|
883
|
+
supportsContext(provider) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("modelsContextHint") }),
|
|
814
884
|
catalog.tools.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
815
885
|
style: {
|
|
816
886
|
display: "grid",
|
|
@@ -1054,13 +1124,13 @@ const subscriptionChromeCss = `
|
|
|
1054
1124
|
|
|
1055
1125
|
//#endregion
|
|
1056
1126
|
//#region src/client/ProviderAccountManager.tsx
|
|
1057
|
-
const border = "0.5px solid var(--dsw-alias-border-l2)";
|
|
1058
|
-
const stack = {
|
|
1127
|
+
const border$1 = "0.5px solid var(--dsw-alias-border-l2)";
|
|
1128
|
+
const stack$1 = {
|
|
1059
1129
|
display: "grid",
|
|
1060
1130
|
gap: 12,
|
|
1061
1131
|
minWidth: 0
|
|
1062
1132
|
};
|
|
1063
|
-
const actions = {
|
|
1133
|
+
const actions$1 = {
|
|
1064
1134
|
display: "flex",
|
|
1065
1135
|
flexWrap: "wrap",
|
|
1066
1136
|
gap: 8,
|
|
@@ -1151,8 +1221,8 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1151
1221
|
if (alive.current) onClose();
|
|
1152
1222
|
} catch (error$1) {
|
|
1153
1223
|
if (alive.current) {
|
|
1154
|
-
const message$
|
|
1155
|
-
setSaveError((savedEfforts ? t("modelsPartialSave") + " " : "") + t("accountsSaveFailed", { message: message$
|
|
1224
|
+
const message$3 = error$1 instanceof Error ? error$1.message : String(error$1);
|
|
1225
|
+
setSaveError((savedEfforts ? t("modelsPartialSave") + " " : "") + t("accountsSaveFailed", { message: message$3 }));
|
|
1156
1226
|
}
|
|
1157
1227
|
} finally {
|
|
1158
1228
|
saveLock.current = false;
|
|
@@ -1176,13 +1246,13 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1176
1246
|
},
|
|
1177
1247
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: providerSettingsCss }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1178
1248
|
style: {
|
|
1179
|
-
...stack,
|
|
1249
|
+
...stack$1,
|
|
1180
1250
|
padding: 20
|
|
1181
1251
|
},
|
|
1182
1252
|
children: [
|
|
1183
1253
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1184
1254
|
style: {
|
|
1185
|
-
...actions,
|
|
1255
|
+
...actions$1,
|
|
1186
1256
|
justifyContent: "space-between"
|
|
1187
1257
|
},
|
|
1188
1258
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
@@ -1219,7 +1289,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1219
1289
|
catalog && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1220
1290
|
disabled: saving || loading,
|
|
1221
1291
|
style: {
|
|
1222
|
-
...stack,
|
|
1292
|
+
...stack$1,
|
|
1223
1293
|
border: 0,
|
|
1224
1294
|
margin: 0,
|
|
1225
1295
|
padding: 0
|
|
@@ -1231,7 +1301,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1231
1301
|
}),
|
|
1232
1302
|
provider === "cursor-subscription" && catalog.accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1233
1303
|
style: {
|
|
1234
|
-
border,
|
|
1304
|
+
border: border$1,
|
|
1235
1305
|
borderRadius: "var(--dsw-radius-lg, 12px)",
|
|
1236
1306
|
padding: 14
|
|
1237
1307
|
},
|
|
@@ -1249,8 +1319,8 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1249
1319
|
const models = accountModelRows(account, preferences);
|
|
1250
1320
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1251
1321
|
style: {
|
|
1252
|
-
...stack,
|
|
1253
|
-
border,
|
|
1322
|
+
...stack$1,
|
|
1323
|
+
border: border$1,
|
|
1254
1324
|
borderRadius: "var(--dsw-radius-lg, 12px)",
|
|
1255
1325
|
padding: 14,
|
|
1256
1326
|
margin: 0
|
|
@@ -1270,7 +1340,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1270
1340
|
}),
|
|
1271
1341
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1272
1342
|
style: {
|
|
1273
|
-
...stack,
|
|
1343
|
+
...stack$1,
|
|
1274
1344
|
gap: 6
|
|
1275
1345
|
},
|
|
1276
1346
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1327,7 +1397,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1327
1397
|
]
|
|
1328
1398
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1329
1399
|
style: {
|
|
1330
|
-
...stack,
|
|
1400
|
+
...stack$1,
|
|
1331
1401
|
marginTop: 12
|
|
1332
1402
|
},
|
|
1333
1403
|
children: [
|
|
@@ -1350,7 +1420,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1350
1420
|
t("accountsModelsAutomatic")
|
|
1351
1421
|
] }),
|
|
1352
1422
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1353
|
-
style: actions,
|
|
1423
|
+
style: actions$1,
|
|
1354
1424
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1355
1425
|
type: "button",
|
|
1356
1426
|
onClick: () => edit(account.key, {
|
|
@@ -1369,7 +1439,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1369
1439
|
}),
|
|
1370
1440
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1371
1441
|
style: {
|
|
1372
|
-
...stack,
|
|
1442
|
+
...stack$1,
|
|
1373
1443
|
gap: 8,
|
|
1374
1444
|
maxHeight: 240,
|
|
1375
1445
|
overflowY: "auto"
|
|
@@ -1416,9 +1486,9 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1416
1486
|
}),
|
|
1417
1487
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
1418
1488
|
style: {
|
|
1419
|
-
...stack,
|
|
1489
|
+
...stack$1,
|
|
1420
1490
|
gap: 8,
|
|
1421
|
-
borderTop: border,
|
|
1491
|
+
borderTop: border$1,
|
|
1422
1492
|
padding: "14px 0 8px",
|
|
1423
1493
|
position: "sticky",
|
|
1424
1494
|
bottom: 0,
|
|
@@ -1429,7 +1499,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1429
1499
|
children: saveError
|
|
1430
1500
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1431
1501
|
style: {
|
|
1432
|
-
...actions,
|
|
1502
|
+
...actions$1,
|
|
1433
1503
|
justifyContent: "flex-end"
|
|
1434
1504
|
},
|
|
1435
1505
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1704,6 +1774,19 @@ function usageBarColor(used, elapsed, preset = "standard", fresh = true) {
|
|
|
1704
1774
|
const state = usageColorState(used, elapsed, preset, fresh);
|
|
1705
1775
|
return state === "red" ? "var(--dsw-alias-state-error-primary)" : state === "yellow" ? "var(--dsw-alias-state-warn-label)" : "var(--dsw-alias-state-success-primary)";
|
|
1706
1776
|
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Marker position from the window's display-only assumed start
|
|
1779
|
+
* (`displayStartsAt`): the reading and its age are irrelevant, exactly
|
|
1780
|
+
* like the verified marker. Returns undefined when the window carries no
|
|
1781
|
+
* assumed start or its reset has passed.
|
|
1782
|
+
*/
|
|
1783
|
+
function displayStartPercent(window$1, now = Date.now()) {
|
|
1784
|
+
const end = window$1.resetsAt;
|
|
1785
|
+
const start = window$1.displayStartsAt;
|
|
1786
|
+
if (end === void 0 || !Number.isFinite(end) || end <= now) return void 0;
|
|
1787
|
+
if (start === void 0 || !Number.isFinite(start) || start >= end || start > now) return void 0;
|
|
1788
|
+
return Math.min(100, Math.max(0, (now - start) / (end - start) * 100));
|
|
1789
|
+
}
|
|
1707
1790
|
|
|
1708
1791
|
//#endregion
|
|
1709
1792
|
//#region src/client/usage-color-preferences.ts
|
|
@@ -1765,6 +1848,7 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1765
1848
|
stale
|
|
1766
1849
|
}, current);
|
|
1767
1850
|
const elapsed = elapsedPercent(quota, current);
|
|
1851
|
+
const marker = elapsed ?? displayStartPercent(quota, current);
|
|
1768
1852
|
const pace = fresh ? elapsed : void 0;
|
|
1769
1853
|
const state = usageColorState(quota.usedPercent, pace, preset, fresh);
|
|
1770
1854
|
const lead = pace === void 0 ? 0 : Math.round(used - pace);
|
|
@@ -1774,7 +1858,7 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1774
1858
|
remaining: Math.round(100 - used)
|
|
1775
1859
|
}) : t("usageMeterInvalid"),
|
|
1776
1860
|
!valid ? "" : !fresh ? t("usageMeterStale") : state === "red" ? t("usageMeterNearLimit") : pace === void 0 && preset !== "remaining" ? t("usageMeterUnknownTime") : state === "yellow" ? t("usageMeterAhead") : t("usageMeterNoWarning"),
|
|
1777
|
-
|
|
1861
|
+
marker === void 0 ? "" : t("usageMeterPace", { elapsed: Math.round(marker) }),
|
|
1778
1862
|
pace === void 0 ? "" : lead > 0 ? t("usageMeterPaceAhead", { points: lead }) : lead < 0 ? t("usageMeterPaceBehind", { points: -lead }) : t("usageMeterPaceEven"),
|
|
1779
1863
|
quota.resetsAt !== void 0 && quota.resetsAt > current ? t("usageMeterReset", { duration: resetCountdownParts(quota.resetsAt - current).map((part) => t(UNIT_KEYS[part.unit][part.count === 1 ? 0 : 1], { count: part.count })).join(" ") }) : ""
|
|
1780
1864
|
].filter(Boolean).join(" · ");
|
|
@@ -1797,12 +1881,12 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1797
1881
|
borderRadius: "inherit",
|
|
1798
1882
|
width: `${used}%`,
|
|
1799
1883
|
background: usageBarColor(quota.usedPercent, pace, preset, fresh)
|
|
1800
|
-
} }),
|
|
1884
|
+
} }), marker !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1801
1885
|
"aria-hidden": "true",
|
|
1802
1886
|
"data-usage-time-marker": true,
|
|
1803
1887
|
style: {
|
|
1804
1888
|
position: "absolute",
|
|
1805
|
-
left: `clamp(0px, calc(${
|
|
1889
|
+
left: `clamp(0px, calc(${marker}% - 1px), calc(100% - 2px))`,
|
|
1806
1890
|
top: 0,
|
|
1807
1891
|
bottom: 0,
|
|
1808
1892
|
width: 2,
|
|
@@ -1981,6 +2065,10 @@ function ExternalUsageCards({ rpc, t }) {
|
|
|
1981
2065
|
style: subscriptionCardStyles.error,
|
|
1982
2066
|
children: t("usageError", { message: errors[id] })
|
|
1983
2067
|
}),
|
|
2068
|
+
snapshot !== void 0 && snapshot.supported === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2069
|
+
style: subscriptionCardStyles.status,
|
|
2070
|
+
children: t("usageUnsupported")
|
|
2071
|
+
}),
|
|
1984
2072
|
snapshot?.windows?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1985
2073
|
style: subscriptionCardStyles.status,
|
|
1986
2074
|
children: t("usageEmpty")
|
|
@@ -2022,7 +2110,7 @@ function ExternalUsageCards({ rpc, t }) {
|
|
|
2022
2110
|
|
|
2023
2111
|
//#endregion
|
|
2024
2112
|
//#region src/client/CursorCard.tsx
|
|
2025
|
-
function message(error) {
|
|
2113
|
+
function message$2(error) {
|
|
2026
2114
|
return error instanceof Error ? error.message : String(error);
|
|
2027
2115
|
}
|
|
2028
2116
|
/** Cursor's single connected account, rendered with the same card and Manage dialog as the other providers. */
|
|
@@ -2047,7 +2135,7 @@ function CursorCard({ rpc, t }) {
|
|
|
2047
2135
|
if (value.authenticated) setAuthorizeUrl(void 0);
|
|
2048
2136
|
if (value.error) setError(value.error);
|
|
2049
2137
|
} catch (failure) {
|
|
2050
|
-
setError(message(failure));
|
|
2138
|
+
setError(message$2(failure));
|
|
2051
2139
|
}
|
|
2052
2140
|
}, [rpc]);
|
|
2053
2141
|
const refreshUsage = (0, react.useCallback)(async () => {
|
|
@@ -2060,7 +2148,7 @@ function CursorCard({ rpc, t }) {
|
|
|
2060
2148
|
setError(void 0);
|
|
2061
2149
|
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
2062
2150
|
} catch (failure) {
|
|
2063
|
-
setError(message(failure));
|
|
2151
|
+
setError(message$2(failure));
|
|
2064
2152
|
setUsageStale(true);
|
|
2065
2153
|
} finally {
|
|
2066
2154
|
setLoading(false);
|
|
@@ -2087,7 +2175,7 @@ function CursorCard({ rpc, t }) {
|
|
|
2087
2175
|
window.open(value.authorizeUrl, "_blank", "noopener");
|
|
2088
2176
|
await refreshStatus();
|
|
2089
2177
|
} catch (failure) {
|
|
2090
|
-
setError(message(failure));
|
|
2178
|
+
setError(message$2(failure));
|
|
2091
2179
|
}
|
|
2092
2180
|
};
|
|
2093
2181
|
const cancel = async () => {
|
|
@@ -2096,7 +2184,7 @@ function CursorCard({ rpc, t }) {
|
|
|
2096
2184
|
setAuthorizeUrl(void 0);
|
|
2097
2185
|
await refreshStatus();
|
|
2098
2186
|
} catch (failure) {
|
|
2099
|
-
setError(message(failure));
|
|
2187
|
+
setError(message$2(failure));
|
|
2100
2188
|
}
|
|
2101
2189
|
};
|
|
2102
2190
|
const logout = async () => {
|
|
@@ -2111,7 +2199,7 @@ function CursorCard({ rpc, t }) {
|
|
|
2111
2199
|
setUsageStale(false);
|
|
2112
2200
|
await refreshStatus();
|
|
2113
2201
|
} catch (failure) {
|
|
2114
|
-
setError(message(failure));
|
|
2202
|
+
setError(message$2(failure));
|
|
2115
2203
|
}
|
|
2116
2204
|
};
|
|
2117
2205
|
const connected = status?.authenticated === true;
|
|
@@ -2265,220 +2353,1267 @@ function CursorCard({ rpc, t }) {
|
|
|
2265
2353
|
}
|
|
2266
2354
|
|
|
2267
2355
|
//#endregion
|
|
2268
|
-
//#region src/client/
|
|
2269
|
-
const STATE_KEYS = {
|
|
2270
|
-
available: "resetCreditAvailable",
|
|
2271
|
-
notUsable: "resetCreditNotUsable",
|
|
2272
|
-
paused: "resetCreditPaused",
|
|
2273
|
-
exhausted: "resetCreditExhausted",
|
|
2274
|
-
expired: "resetCreditExpired"
|
|
2275
|
-
};
|
|
2356
|
+
//#region src/client/ollama-draft.ts
|
|
2276
2357
|
/**
|
|
2277
|
-
*
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2358
|
+
* Append the checked candidates to the draft's model list. Existing rows are
|
|
2359
|
+
* never replaced or reordered, ids already present are skipped, and adopted
|
|
2360
|
+
* rows keep the candidate order. Optional fields are set only when the
|
|
2361
|
+
* candidate advertises a value, because `exactOptionalPropertyTypes` (and the
|
|
2362
|
+
* store) must not see an explicit `undefined`.
|
|
2363
|
+
* @param current - the draft list before adoption.
|
|
2364
|
+
* @param candidates - the cloud list as fetched, in display order.
|
|
2365
|
+
* @param selectedIds - ids the user checked.
|
|
2366
|
+
* @returns a new list; the input array is not mutated.
|
|
2280
2367
|
*/
|
|
2281
|
-
function
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2368
|
+
function adoptCandidates(current, candidates, selectedIds) {
|
|
2369
|
+
const models = [...current ?? []];
|
|
2370
|
+
const present = new Set(models.map((model) => model.id));
|
|
2371
|
+
const selected = new Set(selectedIds);
|
|
2372
|
+
for (const candidate of candidates) {
|
|
2373
|
+
if (!selected.has(candidate.id) || present.has(candidate.id)) continue;
|
|
2374
|
+
present.add(candidate.id);
|
|
2375
|
+
models.push({
|
|
2376
|
+
id: candidate.id,
|
|
2377
|
+
name: candidate.name,
|
|
2378
|
+
...candidate.contextWindow === void 0 ? {} : { contextWindow: candidate.contextWindow },
|
|
2379
|
+
...candidate.vision ? { vision: true } : {},
|
|
2380
|
+
...candidate.thinking ? { thinking: true } : {},
|
|
2381
|
+
...candidate.efforts.length ? { efforts: [...candidate.efforts] } : {}
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
return models;
|
|
2287
2385
|
}
|
|
2288
2386
|
/**
|
|
2289
|
-
*
|
|
2290
|
-
*
|
|
2291
|
-
*
|
|
2292
|
-
*
|
|
2387
|
+
* Swap one model with its neighbour. An id that is absent or a move past
|
|
2388
|
+
* either end returns the same order.
|
|
2389
|
+
* @param models - the current list.
|
|
2390
|
+
* @param id - the row to move.
|
|
2391
|
+
* @param delta - `-1` moves up, `1` moves down.
|
|
2392
|
+
* @returns a new list; the input array is not mutated.
|
|
2293
2393
|
*/
|
|
2294
|
-
function
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2394
|
+
function moveModel(models, id, delta) {
|
|
2395
|
+
const next = [...models];
|
|
2396
|
+
const index = next.findIndex((model) => model.id === id);
|
|
2397
|
+
const target = index + delta;
|
|
2398
|
+
if (index === -1 || target < 0 || target >= next.length) return next;
|
|
2399
|
+
const moved = next[index];
|
|
2400
|
+
next[index] = next[target];
|
|
2401
|
+
next[target] = moved;
|
|
2402
|
+
return next;
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Drop one model from the list.
|
|
2406
|
+
* @param models - the current list.
|
|
2407
|
+
* @param id - the row to drop; an unknown id leaves the list unchanged.
|
|
2408
|
+
* @returns a new list; the input array is not mutated.
|
|
2409
|
+
*/
|
|
2410
|
+
function removeModel(models, id) {
|
|
2411
|
+
return models.filter((model) => model.id !== id);
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Short context-window label for a model row.
|
|
2415
|
+
* @param contextWindow - the advertised window in tokens.
|
|
2416
|
+
* @returns `'1M'`, `'262K'`, the bare number below 1000, or `undefined` when unknown.
|
|
2417
|
+
*/
|
|
2418
|
+
function contextLabel(contextWindow) {
|
|
2419
|
+
if (contextWindow === void 0) return void 0;
|
|
2420
|
+
if (contextWindow < 1e3) return String(contextWindow);
|
|
2421
|
+
if (contextWindow >= 1e6) {
|
|
2422
|
+
const millions = contextWindow / 1e6;
|
|
2423
|
+
return `${Number.isInteger(millions) ? millions : Number(millions.toFixed(1))}M`;
|
|
2301
2424
|
}
|
|
2302
|
-
return
|
|
2425
|
+
return `${Math.round(contextWindow / 1e3)}K`;
|
|
2303
2426
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2427
|
+
/**
|
|
2428
|
+
* Rename one model row. An empty display name deletes the field so the
|
|
2429
|
+
* validator keeps accepting the draft; a blank id leaves the list unchanged.
|
|
2430
|
+
* @param models - the current list.
|
|
2431
|
+
* @param id - the row to rename; an unknown id leaves the list unchanged.
|
|
2432
|
+
* @param name - the new display name; whitespace trims before storing.
|
|
2433
|
+
* @returns a new list; the input array is not mutated.
|
|
2434
|
+
*/
|
|
2435
|
+
function renameModel(models, id, name) {
|
|
2436
|
+
return models.map((model) => {
|
|
2437
|
+
if (model.id !== id) return model;
|
|
2438
|
+
const next = { ...model };
|
|
2439
|
+
if (name === "") delete next.name;
|
|
2440
|
+
else next.name = name;
|
|
2441
|
+
return next;
|
|
2442
|
+
});
|
|
2306
2443
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2444
|
+
/**
|
|
2445
|
+
* Set or clear one model row's default reasoning effort. An empty choice
|
|
2446
|
+
* deletes the field so the resolver falls back to the vendor or family
|
|
2447
|
+
* default.
|
|
2448
|
+
* @param models - the current list.
|
|
2449
|
+
* @param id - the row to edit; an unknown id leaves the list unchanged.
|
|
2450
|
+
* @param effort - the chosen level, or `''` to clear.
|
|
2451
|
+
* @returns a new list; the input array is not mutated.
|
|
2452
|
+
*/
|
|
2453
|
+
function setModelEffort(models, id, effort) {
|
|
2454
|
+
return models.map((model) => {
|
|
2455
|
+
if (model.id !== id) return model;
|
|
2456
|
+
if (effort === "") {
|
|
2457
|
+
const next = { ...model };
|
|
2458
|
+
delete next.defaultEffort;
|
|
2459
|
+
return next;
|
|
2460
|
+
}
|
|
2461
|
+
return {
|
|
2462
|
+
...model,
|
|
2463
|
+
defaultEffort: effort
|
|
2464
|
+
};
|
|
2314
2465
|
});
|
|
2315
|
-
if (hasLeft) return t("resetCreditLeft", { left });
|
|
2316
|
-
return t("resetCreditRemainingUnknown");
|
|
2317
2466
|
}
|
|
2318
|
-
/**
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
return
|
|
2467
|
+
/**
|
|
2468
|
+
* Set or clear one model row's local context-window override. `undefined`
|
|
2469
|
+
* deletes the field so the model follows the endpoint again; the runtime
|
|
2470
|
+
* caps a stored value at the advertised window.
|
|
2471
|
+
* @param models - the current list.
|
|
2472
|
+
* @param id - the row to edit; an unknown id leaves the list unchanged.
|
|
2473
|
+
* @param tokens - a positive safe integer, or `undefined` to clear.
|
|
2474
|
+
* @returns a new list; the input array is not mutated.
|
|
2475
|
+
*/
|
|
2476
|
+
function setModelContext(models, id, tokens) {
|
|
2477
|
+
return models.map((model) => {
|
|
2478
|
+
if (model.id !== id) return model;
|
|
2479
|
+
const next = { ...model };
|
|
2480
|
+
if (tokens === void 0) delete next.contextOverride;
|
|
2481
|
+
else next.contextOverride = tokens;
|
|
2482
|
+
return next;
|
|
2483
|
+
});
|
|
2329
2484
|
}
|
|
2330
|
-
/**
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2485
|
+
/**
|
|
2486
|
+
* Parse the context input's text. Only a positive safe integer is a value;
|
|
2487
|
+
* an empty string clears the override.
|
|
2488
|
+
* @param text - what the user typed.
|
|
2489
|
+
* @returns the number, `undefined` for blank, or `null` for text to reject.
|
|
2490
|
+
*/
|
|
2491
|
+
function parseContextInput(text) {
|
|
2492
|
+
const trimmed = text.trim();
|
|
2493
|
+
if (trimmed === "") return void 0;
|
|
2494
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
2495
|
+
const value = Number(trimmed);
|
|
2496
|
+
return Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
2334
2497
|
}
|
|
2335
|
-
/**
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2498
|
+
/**
|
|
2499
|
+
* Untick one model: drop it from the list and keep its full row (display
|
|
2500
|
+
* name, default effort, context, ...) in the stash so ticking it again is
|
|
2501
|
+
* lossless, even when the cloud listing no longer carries the id.
|
|
2502
|
+
* @param models - the current list.
|
|
2503
|
+
* @param stash - the stash before this untick.
|
|
2504
|
+
* @param id - the row to untick; an absent id changes nothing.
|
|
2505
|
+
* @returns the new list and stash; neither input is mutated.
|
|
2506
|
+
*/
|
|
2507
|
+
function untickModel(models, stash, id) {
|
|
2508
|
+
const row = models.find((model) => model.id === id);
|
|
2509
|
+
if (row === void 0) return {
|
|
2510
|
+
models: [...models],
|
|
2511
|
+
stash
|
|
2512
|
+
};
|
|
2513
|
+
return {
|
|
2514
|
+
models: removeModel(models, id),
|
|
2515
|
+
stash: new Map(stash).set(id, row)
|
|
2516
|
+
};
|
|
2341
2517
|
}
|
|
2342
|
-
/**
|
|
2343
|
-
|
|
2344
|
-
|
|
2518
|
+
/**
|
|
2519
|
+
* Tick one model: restore its stashed row when this dialog unticked it,
|
|
2520
|
+
* otherwise adopt it from the fetched candidates (which never copies the
|
|
2521
|
+
* vendor default effort). A model already in the list, or unknown to both
|
|
2522
|
+
* sources, leaves the list unchanged.
|
|
2523
|
+
* @param models - the current list.
|
|
2524
|
+
* @param stash - rows unticked earlier in this dialog.
|
|
2525
|
+
* @param candidates - the cloud list as fetched.
|
|
2526
|
+
* @param id - the row to tick.
|
|
2527
|
+
* @returns a new list and the stash without the restored row.
|
|
2528
|
+
*/
|
|
2529
|
+
function tickModel(models, stash, candidates, id) {
|
|
2530
|
+
if (models.some((model) => model.id === id)) return {
|
|
2531
|
+
models: [...models],
|
|
2532
|
+
stash
|
|
2533
|
+
};
|
|
2534
|
+
const stashed = stash.get(id);
|
|
2535
|
+
if (stashed === void 0) return {
|
|
2536
|
+
models: adoptCandidates(models, candidates, [id]),
|
|
2537
|
+
stash
|
|
2538
|
+
};
|
|
2539
|
+
const next = new Map(stash);
|
|
2540
|
+
next.delete(id);
|
|
2541
|
+
return {
|
|
2542
|
+
models: [...models, stashed],
|
|
2543
|
+
stash: next
|
|
2544
|
+
};
|
|
2345
2545
|
}
|
|
2346
|
-
const summaryStyle = {
|
|
2347
|
-
...subscriptionCardStyles.usageMeta,
|
|
2348
|
-
cursor: "pointer",
|
|
2349
|
-
listStyle: "none",
|
|
2350
|
-
justifyContent: "flex-start",
|
|
2351
|
-
gap: 6
|
|
2352
|
-
};
|
|
2353
2546
|
/**
|
|
2354
|
-
*
|
|
2355
|
-
*
|
|
2356
|
-
*
|
|
2547
|
+
* Tick every listed id in order. Rows already in the list stay where they
|
|
2548
|
+
* are; restored and adopted rows append in the given order.
|
|
2549
|
+
* @param models - the current list.
|
|
2550
|
+
* @param stash - rows unticked earlier in this dialog.
|
|
2551
|
+
* @param candidates - the cloud list as fetched.
|
|
2552
|
+
* @param ids - the ids to tick, typically the filtered view.
|
|
2553
|
+
* @returns the new list and stash; neither input is mutated.
|
|
2357
2554
|
*/
|
|
2358
|
-
function
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2405
|
-
"data-reset-state": mode === "grants" ? state : void 0,
|
|
2406
|
-
"data-reset-claimable": mode === "grants" && credit.claimable === true ? "true" : void 0,
|
|
2407
|
-
role: mode === "grants" ? "group" : void 0,
|
|
2408
|
-
"aria-label": mode === "grants" ? resetCreditRowName(t, credit, now) : void 0,
|
|
2409
|
-
style: {
|
|
2410
|
-
...subscriptionCardStyles.usageMeta,
|
|
2411
|
-
minHeight: 28,
|
|
2412
|
-
gap: 12,
|
|
2413
|
-
cursor: mode === "grants" ? "default" : void 0
|
|
2414
|
-
},
|
|
2415
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2416
|
-
style: {
|
|
2417
|
-
display: "inline-flex",
|
|
2418
|
-
alignItems: "center",
|
|
2419
|
-
gap: 8,
|
|
2420
|
-
flexShrink: 0
|
|
2421
|
-
},
|
|
2422
|
-
children: [
|
|
2423
|
-
t("resetCreditFull"),
|
|
2424
|
-
mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2425
|
-
"aria-hidden": "true",
|
|
2426
|
-
"data-reset-state-word": state,
|
|
2427
|
-
children: t(STATE_KEYS[state])
|
|
2428
|
-
}),
|
|
2429
|
-
showUse && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2430
|
-
type: "button",
|
|
2431
|
-
"aria-disabled": useAction.disabled,
|
|
2432
|
-
title: useAction.title,
|
|
2433
|
-
style: {
|
|
2434
|
-
minHeight: 22,
|
|
2435
|
-
height: 22,
|
|
2436
|
-
padding: "0 7px",
|
|
2437
|
-
borderRadius: 6,
|
|
2438
|
-
opacity: useAction.disabled ? .35 : 1,
|
|
2439
|
-
cursor: useAction.disabled ? "not-allowed" : "pointer"
|
|
2440
|
-
},
|
|
2441
|
-
onClick: () => {
|
|
2442
|
-
if (!useAction.disabled) useAction.onUse();
|
|
2443
|
-
},
|
|
2444
|
-
children: useAction.busy ? t("resetUseChecking") : useAction.label
|
|
2445
|
-
})
|
|
2446
|
-
]
|
|
2447
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2448
|
-
style: { textAlign: "right" },
|
|
2449
|
-
children: [
|
|
2450
|
-
mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2451
|
-
"aria-hidden": "true",
|
|
2452
|
-
children: [remainingText(t, credit), " · "]
|
|
2453
|
-
}),
|
|
2454
|
-
mode === "grants" && state === "notUsable" && credit.cooldownUntil !== void 0 && credit.cooldownUntil > now && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2455
|
-
"aria-hidden": "true",
|
|
2456
|
-
children: [t("resetCreditCooldownUntil", { date: new Date(credit.cooldownUntil).toLocaleString() }), " · "]
|
|
2457
|
-
}),
|
|
2458
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2459
|
-
"aria-hidden": mode === "grants" ? true : void 0,
|
|
2460
|
-
children: expiryText(t, credit)
|
|
2461
|
-
})
|
|
2462
|
-
]
|
|
2463
|
-
})]
|
|
2464
|
-
}, credit.id ?? index);
|
|
2465
|
-
}),
|
|
2466
|
-
statusMessage !== void 0 && statusMessage !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2467
|
-
role: "status",
|
|
2468
|
-
style: subscriptionCardStyles.status,
|
|
2469
|
-
children: statusMessage
|
|
2470
|
-
})
|
|
2471
|
-
]
|
|
2472
|
-
})
|
|
2473
|
-
]
|
|
2555
|
+
function tickModels(models, stash, candidates, ids) {
|
|
2556
|
+
let state = {
|
|
2557
|
+
models: [...models],
|
|
2558
|
+
stash
|
|
2559
|
+
};
|
|
2560
|
+
for (const id of ids) state = tickModel(state.models, state.stash, candidates, id);
|
|
2561
|
+
return state;
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Untick every listed id, stashing each removed row. Ids outside the list,
|
|
2565
|
+
* such as filtered-out rows, are untouched.
|
|
2566
|
+
* @param models - the current list.
|
|
2567
|
+
* @param stash - the stash before this untick.
|
|
2568
|
+
* @param ids - the ids to untick, typically the filtered view.
|
|
2569
|
+
* @returns the new list and stash; neither input is mutated.
|
|
2570
|
+
*/
|
|
2571
|
+
function untickModels(models, stash, ids) {
|
|
2572
|
+
let state = {
|
|
2573
|
+
models: [...models],
|
|
2574
|
+
stash
|
|
2575
|
+
};
|
|
2576
|
+
for (const id of ids) state = untickModel(state.models, state.stash, id);
|
|
2577
|
+
return state;
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* The effort choices for one ticked row. The row's own `efforts` decide the
|
|
2581
|
+
* ids because the store rejects a default outside them, so the selector must
|
|
2582
|
+
* never offer anything else. A snapshot of the endpoint (`labels`) may only
|
|
2583
|
+
* supply friendlier names for the same ids. A saved default the list no
|
|
2584
|
+
* longer contains is kept visible and marked unavailable instead of
|
|
2585
|
+
* silently reading as "follow the provider".
|
|
2586
|
+
* @param row - the draft row.
|
|
2587
|
+
* @param fetched - efforts from this dialog's latest fetch, when it returned the row.
|
|
2588
|
+
* @param labels - the opened dialog's snapshot of named efforts for the id.
|
|
2589
|
+
* @returns the options in picker order.
|
|
2590
|
+
*/
|
|
2591
|
+
function effortOptions(row, fetched, labels) {
|
|
2592
|
+
const ids = row.efforts ?? fetched ?? labels?.map((entry) => entry.id) ?? [];
|
|
2593
|
+
const options = ids.map((id) => ({
|
|
2594
|
+
id,
|
|
2595
|
+
name: labels?.find((entry) => entry.id === id)?.name ?? id
|
|
2596
|
+
}));
|
|
2597
|
+
if (row.defaultEffort !== void 0 && !ids.includes(row.defaultEffort)) options.push({
|
|
2598
|
+
id: row.defaultEffort,
|
|
2599
|
+
name: row.defaultEffort,
|
|
2600
|
+
unavailable: true
|
|
2474
2601
|
});
|
|
2602
|
+
return options;
|
|
2475
2603
|
}
|
|
2476
2604
|
|
|
2477
2605
|
//#endregion
|
|
2478
|
-
//#region src/client/
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2606
|
+
//#region src/client/OllamaSettingsManager.tsx
|
|
2607
|
+
const border = "0.5px solid var(--dsw-alias-border-l2)";
|
|
2608
|
+
const stack = {
|
|
2609
|
+
display: "grid",
|
|
2610
|
+
gap: 12,
|
|
2611
|
+
minWidth: 0
|
|
2612
|
+
};
|
|
2613
|
+
const actions = {
|
|
2614
|
+
display: "flex",
|
|
2615
|
+
flexWrap: "wrap",
|
|
2616
|
+
gap: 8,
|
|
2617
|
+
alignItems: "center"
|
|
2618
|
+
};
|
|
2619
|
+
function message$1(error) {
|
|
2620
|
+
return error instanceof Error ? error.message : String(error);
|
|
2621
|
+
}
|
|
2622
|
+
/** `vision · thinking · context 262K`, dropping whatever the model does not advertise. */
|
|
2623
|
+
function capabilities(t, model) {
|
|
2624
|
+
const context = contextLabel(model.contextWindow);
|
|
2625
|
+
return [
|
|
2626
|
+
model.vision === true ? t("ollamaCapVision") : false,
|
|
2627
|
+
model.thinking === true ? t("ollamaCapThinking") : false,
|
|
2628
|
+
context === void 0 ? false : t("ollamaContext", { tokens: context })
|
|
2629
|
+
].filter(Boolean).join(" · ");
|
|
2630
|
+
}
|
|
2631
|
+
/**
|
|
2632
|
+
* Ollama Cloud settings dialog. It owns one draft (model list, web toggle,
|
|
2633
|
+
* base URL and credential reference) that Save writes in a single
|
|
2634
|
+
* `ollamaSetSettings` call, so a failure keeps every edit. Fetching the cloud
|
|
2635
|
+
* catalog is a read-only side trip, and a key typed for the fetch never
|
|
2636
|
+
* leaves this component. The model list follows the shared model editor: a
|
|
2637
|
+
* filter, select all/none over the filtered rows, and one checkbox per
|
|
2638
|
+
* model, where ticking edits the draft and enables Save. Ollama adds what the
|
|
2639
|
+
* shared editor lacks: a display name, ordering, and a local context window.
|
|
2640
|
+
* Unticking keeps the row's full settings in a dialog-local stash, so ticking
|
|
2641
|
+
* it again restores them even when the cloud listing no longer returns it.
|
|
2642
|
+
*/
|
|
2643
|
+
function OllamaSettingsManager({ rpc, t, onClose, onSaved }) {
|
|
2644
|
+
const dialog = (0, react.useRef)(null);
|
|
2645
|
+
const title = (0, react.useId)();
|
|
2646
|
+
const description = (0, react.useId)();
|
|
2647
|
+
const [view, setView] = (0, react.useState)();
|
|
2648
|
+
const [draft, setDraft] = (0, react.useState)({});
|
|
2649
|
+
const [dirty, setDirty] = (0, react.useState)(false);
|
|
2650
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
2651
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
2652
|
+
const [error, setError] = (0, react.useState)("");
|
|
2653
|
+
const [saveError, setSaveError] = (0, react.useState)("");
|
|
2654
|
+
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
2655
|
+
const [apiKey, setApiKey] = (0, react.useState)("");
|
|
2656
|
+
const [candidates, setCandidates] = (0, react.useState)();
|
|
2657
|
+
const [fetching, setFetching] = (0, react.useState)(false);
|
|
2658
|
+
const [fetchError, setFetchError] = (0, react.useState)("");
|
|
2659
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
2660
|
+
const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
2661
|
+
const [stash, setStash] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
2662
|
+
const [contextText, setContextText] = (0, react.useState)({});
|
|
2663
|
+
const alive = (0, react.useRef)(true);
|
|
2664
|
+
const saveLock = (0, react.useRef)(false);
|
|
2665
|
+
const fetchLock = (0, react.useRef)(false);
|
|
2666
|
+
(0, react.useEffect)(() => {
|
|
2667
|
+
alive.current = true;
|
|
2668
|
+
const element = dialog.current;
|
|
2669
|
+
const previous = document.activeElement;
|
|
2670
|
+
element.showModal();
|
|
2671
|
+
return () => {
|
|
2672
|
+
alive.current = false;
|
|
2673
|
+
element.close();
|
|
2674
|
+
if (previous instanceof HTMLElement && previous.isConnected) previous.focus();
|
|
2675
|
+
};
|
|
2676
|
+
}, []);
|
|
2677
|
+
(0, react.useEffect)(() => {
|
|
2678
|
+
let current = true;
|
|
2679
|
+
setLoading(true);
|
|
2680
|
+
setError("");
|
|
2681
|
+
callSubscriptionsAuth(rpc, "ollamaSettings", {}).then((data) => {
|
|
2682
|
+
if (!current) return;
|
|
2683
|
+
setView(data);
|
|
2684
|
+
setDraft({ ...data.settings });
|
|
2685
|
+
setStash(/* @__PURE__ */ new Map());
|
|
2686
|
+
setContextText({});
|
|
2687
|
+
setDirty(false);
|
|
2688
|
+
}).catch((failure) => {
|
|
2689
|
+
if (current) setError(t("ollamaModelsLoadFailed", { message: message$1(failure) }));
|
|
2690
|
+
}).finally(() => {
|
|
2691
|
+
if (current) setLoading(false);
|
|
2692
|
+
});
|
|
2693
|
+
return () => {
|
|
2694
|
+
current = false;
|
|
2695
|
+
};
|
|
2696
|
+
}, [rpc, attempt]);
|
|
2697
|
+
function edit(next) {
|
|
2698
|
+
setDraft(next);
|
|
2699
|
+
setDirty(true);
|
|
2700
|
+
}
|
|
2701
|
+
/** Fetch the cloud catalog; a failure keeps the previous list and the draft. */
|
|
2702
|
+
async function fetchCandidates() {
|
|
2703
|
+
if (fetchLock.current) return;
|
|
2704
|
+
fetchLock.current = true;
|
|
2705
|
+
setFetching(true);
|
|
2706
|
+
setFetchError("");
|
|
2707
|
+
try {
|
|
2708
|
+
const value = await callSubscriptionsAuth(rpc, "ollamaModels", {
|
|
2709
|
+
...apiKey ? { apiKey } : {},
|
|
2710
|
+
...draft.baseURL ? { baseURL: draft.baseURL } : {}
|
|
2711
|
+
});
|
|
2712
|
+
if (!alive.current) return;
|
|
2713
|
+
setCandidates(value.models);
|
|
2714
|
+
} catch (failure) {
|
|
2715
|
+
if (alive.current) setFetchError(t("ollamaFetchFailed", { message: message$1(failure) }));
|
|
2716
|
+
} finally {
|
|
2717
|
+
fetchLock.current = false;
|
|
2718
|
+
if (alive.current) setFetching(false);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
/** Save the whole draft; a failure keeps the dialog open with every edit. */
|
|
2722
|
+
async function save() {
|
|
2723
|
+
if (saveLock.current) return;
|
|
2724
|
+
saveLock.current = true;
|
|
2725
|
+
setSaving(true);
|
|
2726
|
+
setSaveError("");
|
|
2727
|
+
try {
|
|
2728
|
+
await callSubscriptionsAuth(rpc, "ollamaSetSettings", { settings: draft });
|
|
2729
|
+
if (alive.current) {
|
|
2730
|
+
onSaved();
|
|
2731
|
+
onClose();
|
|
2732
|
+
}
|
|
2733
|
+
} catch (failure) {
|
|
2734
|
+
if (alive.current) setSaveError(t("ollamaModelsSaveFailed", { message: message$1(failure) }));
|
|
2735
|
+
} finally {
|
|
2736
|
+
saveLock.current = false;
|
|
2737
|
+
if (alive.current) setSaving(false);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
/** Drop every stored override, then reload the draft from the deployment values. */
|
|
2741
|
+
async function resetDefaults() {
|
|
2742
|
+
if (saveLock.current) return;
|
|
2743
|
+
if (!window.confirm(t("ollamaResetConfirm"))) return;
|
|
2744
|
+
saveLock.current = true;
|
|
2745
|
+
setSaving(true);
|
|
2746
|
+
setSaveError("");
|
|
2747
|
+
try {
|
|
2748
|
+
await callSubscriptionsAuth(rpc, "ollamaSetSettings", { settings: {} });
|
|
2749
|
+
if (alive.current) {
|
|
2750
|
+
setAttempt((value) => value + 1);
|
|
2751
|
+
onSaved();
|
|
2752
|
+
}
|
|
2753
|
+
} catch (failure) {
|
|
2754
|
+
if (alive.current) setSaveError(t("ollamaModelsSaveFailed", { message: message$1(failure) }));
|
|
2755
|
+
} finally {
|
|
2756
|
+
saveLock.current = false;
|
|
2757
|
+
if (alive.current) setSaving(false);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
function editOverride(field, value) {
|
|
2761
|
+
const next = { ...draft };
|
|
2762
|
+
if (value.trim()) next[field] = value;
|
|
2763
|
+
else delete next[field];
|
|
2764
|
+
edit(next);
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* One list for both sources, like the shared editor: the ticked models in
|
|
2768
|
+
* draft order first (so Move up/down is visible), then the unticked ones
|
|
2769
|
+
* (stashed in this dialog, or fetched and not yet adopted). A kept model
|
|
2770
|
+
* the fetch did not return stays listed, ticked or not.
|
|
2771
|
+
*/
|
|
2772
|
+
const fetchedById = new Map((candidates ?? []).map((candidate) => [candidate.id, candidate]));
|
|
2773
|
+
const present = new Set((draft.models ?? []).map((model) => model.id));
|
|
2774
|
+
const asRow = (model, ticked) => {
|
|
2775
|
+
const fetched = fetchedById.get(model.id);
|
|
2776
|
+
const contextWindow = fetched?.contextWindow ?? model.contextWindow;
|
|
2777
|
+
return {
|
|
2778
|
+
id: model.id,
|
|
2779
|
+
name: model.name ?? fetched?.name ?? model.id,
|
|
2780
|
+
ticked,
|
|
2781
|
+
vision: model.vision === true || fetched?.vision === true,
|
|
2782
|
+
thinking: model.thinking === true || fetched?.thinking === true,
|
|
2783
|
+
...contextWindow === void 0 ? {} : { contextWindow },
|
|
2784
|
+
efforts: model.efforts ?? fetched?.efforts ?? []
|
|
2785
|
+
};
|
|
2786
|
+
};
|
|
2787
|
+
const shown = [
|
|
2788
|
+
...(draft.models ?? []).map((model) => asRow(model, true)),
|
|
2789
|
+
...[...stash.values()].filter((model) => !present.has(model.id)).map((model) => asRow(model, false)),
|
|
2790
|
+
...(candidates ?? []).filter((candidate) => !present.has(candidate.id) && !stash.has(candidate.id)).map((candidate) => asRow({ id: candidate.id }, false))
|
|
2791
|
+
].filter((row) => `${row.name} ${row.id}`.toLowerCase().includes(query.trim().toLowerCase()));
|
|
2792
|
+
/** Rename and default-effort edits ride the tested draft helpers. */
|
|
2793
|
+
function rename(id, name) {
|
|
2794
|
+
edit({
|
|
2795
|
+
...draft,
|
|
2796
|
+
models: renameModel(draft.models ?? [], id, name)
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
function setEffort(id, effort) {
|
|
2800
|
+
edit({
|
|
2801
|
+
...draft,
|
|
2802
|
+
models: setModelEffort(draft.models ?? [], id, effort)
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
/** Keep the typed text; only a valid number (or blank) reaches the draft. */
|
|
2806
|
+
function setContext(id, text) {
|
|
2807
|
+
setContextText((current) => ({
|
|
2808
|
+
...current,
|
|
2809
|
+
[id]: text
|
|
2810
|
+
}));
|
|
2811
|
+
const tokens = parseContextInput(text);
|
|
2812
|
+
if (tokens !== null) edit({
|
|
2813
|
+
...draft,
|
|
2814
|
+
models: setModelContext(draft.models ?? [], id, tokens)
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
function applyTicks(next) {
|
|
2818
|
+
setStash(next.stash);
|
|
2819
|
+
edit({
|
|
2820
|
+
...draft,
|
|
2821
|
+
models: next.models
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
const invalidContext = Object.entries(contextText).filter(([id, text]) => present.has(id) && parseContextInput(text) === null).map(([id]) => id);
|
|
2825
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
2826
|
+
className: "dsh-subscription-manager",
|
|
2827
|
+
ref: dialog,
|
|
2828
|
+
"aria-labelledby": title,
|
|
2829
|
+
"aria-describedby": description,
|
|
2830
|
+
onCancel: (event) => {
|
|
2831
|
+
if (saveLock.current) event.preventDefault();
|
|
2832
|
+
},
|
|
2833
|
+
onClose,
|
|
2834
|
+
style: {
|
|
2835
|
+
width: 620,
|
|
2836
|
+
maxWidth: "calc(100vw - 32px)",
|
|
2837
|
+
maxHeight: "calc(100dvh - 32px)",
|
|
2838
|
+
overflow: "auto"
|
|
2839
|
+
},
|
|
2840
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: providerSettingsCss }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2841
|
+
style: {
|
|
2842
|
+
...stack,
|
|
2843
|
+
padding: 20
|
|
2844
|
+
},
|
|
2845
|
+
children: [
|
|
2846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
2847
|
+
style: {
|
|
2848
|
+
...actions,
|
|
2849
|
+
justifyContent: "space-between"
|
|
2850
|
+
},
|
|
2851
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
2852
|
+
id: title,
|
|
2853
|
+
style: { margin: 0 },
|
|
2854
|
+
children: t("ollamaManageTitle")
|
|
2855
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2856
|
+
type: "button",
|
|
2857
|
+
autoFocus: true,
|
|
2858
|
+
disabled: saving,
|
|
2859
|
+
onClick: onClose,
|
|
2860
|
+
children: t("imageClose")
|
|
2861
|
+
})]
|
|
2862
|
+
}),
|
|
2863
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2864
|
+
id: description,
|
|
2865
|
+
className: "dsh-subscription-hint",
|
|
2866
|
+
children: t("ollamaManageHint")
|
|
2867
|
+
}),
|
|
2868
|
+
view?.configOverridesModels === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2869
|
+
className: "dsh-subscription-hint",
|
|
2870
|
+
children: t("ollamaCatalogOverride")
|
|
2871
|
+
}),
|
|
2872
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2873
|
+
role: "alert",
|
|
2874
|
+
children: error
|
|
2875
|
+
}),
|
|
2876
|
+
loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2877
|
+
role: "status",
|
|
2878
|
+
className: "dsh-subscription-hint",
|
|
2879
|
+
children: t("modelDefaultsLoading")
|
|
2880
|
+
}),
|
|
2881
|
+
!loading && !view && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2882
|
+
type: "button",
|
|
2883
|
+
onClick: () => setAttempt((value) => value + 1),
|
|
2884
|
+
children: t("modelDefaultsRetry")
|
|
2885
|
+
}),
|
|
2886
|
+
view && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
2887
|
+
disabled: saving,
|
|
2888
|
+
style: {
|
|
2889
|
+
...stack,
|
|
2890
|
+
border: 0,
|
|
2891
|
+
margin: 0,
|
|
2892
|
+
padding: 0,
|
|
2893
|
+
minWidth: 0
|
|
2894
|
+
},
|
|
2895
|
+
children: [
|
|
2896
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2897
|
+
style: {
|
|
2898
|
+
borderTop: border,
|
|
2899
|
+
paddingTop: 12,
|
|
2900
|
+
display: "grid",
|
|
2901
|
+
gap: 12
|
|
2902
|
+
},
|
|
2903
|
+
children: [
|
|
2904
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2905
|
+
style: { margin: 0 },
|
|
2906
|
+
children: t("modelsEdit")
|
|
2907
|
+
}),
|
|
2908
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2909
|
+
style: actions,
|
|
2910
|
+
children: [
|
|
2911
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2912
|
+
type: "password",
|
|
2913
|
+
autoComplete: "off",
|
|
2914
|
+
style: { flex: "1 1 180px" },
|
|
2915
|
+
"aria-label": t("ollamaKeyPlaceholder"),
|
|
2916
|
+
placeholder: t("ollamaKeyPlaceholder"),
|
|
2917
|
+
value: apiKey,
|
|
2918
|
+
onChange: (event) => setApiKey(event.target.value)
|
|
2919
|
+
}),
|
|
2920
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2921
|
+
type: "button",
|
|
2922
|
+
className: "dsh-subscription-quiet",
|
|
2923
|
+
disabled: fetching,
|
|
2924
|
+
onClick: () => {
|
|
2925
|
+
fetchCandidates();
|
|
2926
|
+
},
|
|
2927
|
+
children: fetching ? t("ollamaFetching") : t("ollamaFetch")
|
|
2928
|
+
}),
|
|
2929
|
+
candidates !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2930
|
+
className: "dsh-subscription-hint",
|
|
2931
|
+
children: t("ollamaCandidatesCount", { count: candidates.length })
|
|
2932
|
+
})
|
|
2933
|
+
]
|
|
2934
|
+
}),
|
|
2935
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2936
|
+
className: "dsh-subscription-hint",
|
|
2937
|
+
children: t("ollamaKeyHint")
|
|
2938
|
+
}),
|
|
2939
|
+
fetchError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2940
|
+
role: "alert",
|
|
2941
|
+
children: fetchError
|
|
2942
|
+
}),
|
|
2943
|
+
candidates?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2944
|
+
className: "dsh-subscription-hint",
|
|
2945
|
+
children: t("ollamaCandidatesEmpty")
|
|
2946
|
+
}),
|
|
2947
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2948
|
+
style: actions,
|
|
2949
|
+
children: [
|
|
2950
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2951
|
+
style: { flex: "1 1 180px" },
|
|
2952
|
+
value: query,
|
|
2953
|
+
onChange: (event) => setQuery(event.target.value),
|
|
2954
|
+
placeholder: t("modelDefaultsFilterPlaceholder"),
|
|
2955
|
+
"aria-label": t("modelDefaultsFilterPlaceholder")
|
|
2956
|
+
}),
|
|
2957
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2958
|
+
type: "button",
|
|
2959
|
+
onClick: () => applyTicks(tickModels(draft.models ?? [], stash, candidates ?? [], shown.map((row) => row.id))),
|
|
2960
|
+
children: t("modelsSelectAll")
|
|
2961
|
+
}),
|
|
2962
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2963
|
+
type: "button",
|
|
2964
|
+
onClick: () => applyTicks(untickModels(draft.models ?? [], stash, shown.map((row) => row.id))),
|
|
2965
|
+
children: t("modelsSelectNone")
|
|
2966
|
+
})
|
|
2967
|
+
]
|
|
2968
|
+
}),
|
|
2969
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2970
|
+
style: {
|
|
2971
|
+
maxHeight: 360,
|
|
2972
|
+
overflowY: "auto",
|
|
2973
|
+
display: "grid",
|
|
2974
|
+
gap: 8
|
|
2975
|
+
},
|
|
2976
|
+
children: [shown.map((row) => {
|
|
2977
|
+
const stored = (draft.models ?? []).find((entry) => entry.id === row.id);
|
|
2978
|
+
const index = (draft.models ?? []).findIndex((entry) => entry.id === row.id);
|
|
2979
|
+
const open = expanded.has(row.id);
|
|
2980
|
+
const details = `${row.id}-details`;
|
|
2981
|
+
const efforts = stored === void 0 ? [] : effortOptions(stored, fetchedById.get(row.id)?.efforts, view.models.find((entry) => entry.id === row.id)?.efforts);
|
|
2982
|
+
const contextProblem = invalidContext.includes(row.id);
|
|
2983
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2984
|
+
style: {
|
|
2985
|
+
borderBottom: border,
|
|
2986
|
+
padding: "8px 2px",
|
|
2987
|
+
display: "grid",
|
|
2988
|
+
gap: 8
|
|
2989
|
+
},
|
|
2990
|
+
children: [
|
|
2991
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2992
|
+
style: { overflowWrap: "anywhere" },
|
|
2993
|
+
children: [
|
|
2994
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2995
|
+
type: "checkbox",
|
|
2996
|
+
checked: row.ticked,
|
|
2997
|
+
onChange: (event) => {
|
|
2998
|
+
applyTicks(event.target.checked ? tickModel(draft.models ?? [], stash, candidates ?? [], row.id) : untickModel(draft.models ?? [], stash, row.id));
|
|
2999
|
+
}
|
|
3000
|
+
}),
|
|
3001
|
+
" ",
|
|
3002
|
+
row.name,
|
|
3003
|
+
" ",
|
|
3004
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3005
|
+
style: subscriptionCardStyles.status,
|
|
3006
|
+
children: capabilities(t, row)
|
|
3007
|
+
})
|
|
3008
|
+
]
|
|
3009
|
+
}),
|
|
3010
|
+
stored !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3011
|
+
style: actions,
|
|
3012
|
+
children: [
|
|
3013
|
+
row.thinking && efforts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3014
|
+
style: actions,
|
|
3015
|
+
children: [t("modelDefaultsTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
3016
|
+
"aria-label": `${row.name} ${t("modelDefaultsTitle")}`,
|
|
3017
|
+
value: stored.defaultEffort ?? "",
|
|
3018
|
+
onChange: (event) => setEffort(row.id, event.target.value),
|
|
3019
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3020
|
+
value: "",
|
|
3021
|
+
children: t("modelDefaultsFollowProvider")
|
|
3022
|
+
}), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3023
|
+
value: effort.id,
|
|
3024
|
+
disabled: effort.unavailable === true,
|
|
3025
|
+
children: effort.unavailable === true ? `${effort.name} (${t("modelsUnavailable")})` : effort.name
|
|
3026
|
+
}, effort.id))]
|
|
3027
|
+
})]
|
|
3028
|
+
}),
|
|
3029
|
+
row.thinking && efforts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("ollamaEffortsUnknown") }),
|
|
3030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3031
|
+
style: actions,
|
|
3032
|
+
children: [t("modelsContext"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3033
|
+
style: { width: 140 },
|
|
3034
|
+
inputMode: "numeric",
|
|
3035
|
+
"aria-invalid": contextProblem,
|
|
3036
|
+
value: contextText[row.id] ?? (stored.contextOverride === void 0 ? "" : String(stored.contextOverride)),
|
|
3037
|
+
"aria-label": `${row.name} ${t("modelsContext")}`,
|
|
3038
|
+
placeholder: row.contextWindow === void 0 ? "" : String(row.contextWindow),
|
|
3039
|
+
onChange: (event) => setContext(row.id, event.target.value)
|
|
3040
|
+
})]
|
|
3041
|
+
}),
|
|
3042
|
+
row.contextWindow !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("modelsContextBounds", {
|
|
3043
|
+
default: row.contextWindow,
|
|
3044
|
+
max: row.contextWindow
|
|
3045
|
+
}) }),
|
|
3046
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3047
|
+
type: "button",
|
|
3048
|
+
className: "dsh-subscription-quiet",
|
|
3049
|
+
"aria-expanded": open,
|
|
3050
|
+
"aria-controls": details,
|
|
3051
|
+
"aria-label": `${row.name} ${t("ollamaRowDetails")}`,
|
|
3052
|
+
onClick: () => {
|
|
3053
|
+
const next = new Set(expanded);
|
|
3054
|
+
if (open) next.delete(row.id);
|
|
3055
|
+
else next.add(row.id);
|
|
3056
|
+
setExpanded(next);
|
|
3057
|
+
},
|
|
3058
|
+
children: [
|
|
3059
|
+
t("ollamaRowDetails"),
|
|
3060
|
+
" ",
|
|
3061
|
+
open ? "▾" : "▸"
|
|
3062
|
+
]
|
|
3063
|
+
})
|
|
3064
|
+
]
|
|
3065
|
+
}),
|
|
3066
|
+
contextProblem && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3067
|
+
role: "alert",
|
|
3068
|
+
children: t("modelsContextInvalid", { model: row.name })
|
|
3069
|
+
}),
|
|
3070
|
+
stored !== void 0 && open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3071
|
+
id: details,
|
|
3072
|
+
style: actions,
|
|
3073
|
+
children: [
|
|
3074
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3075
|
+
style: actions,
|
|
3076
|
+
children: [t("ollamaDisplayName"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3077
|
+
value: stored.name ?? "",
|
|
3078
|
+
placeholder: row.id,
|
|
3079
|
+
"aria-label": `${row.name} ${t("ollamaDisplayName")}`,
|
|
3080
|
+
onChange: (event) => rename(row.id, event.target.value),
|
|
3081
|
+
onBlur: (event) => rename(row.id, event.target.value.trim())
|
|
3082
|
+
})]
|
|
3083
|
+
}),
|
|
3084
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3085
|
+
type: "button",
|
|
3086
|
+
disabled: index === 0,
|
|
3087
|
+
"aria-label": `${row.name} ${t("ollamaMoveUp")}`,
|
|
3088
|
+
onClick: () => edit({
|
|
3089
|
+
...draft,
|
|
3090
|
+
models: moveModel(draft.models ?? [], row.id, -1)
|
|
3091
|
+
}),
|
|
3092
|
+
children: t("ollamaMoveUp")
|
|
3093
|
+
}),
|
|
3094
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3095
|
+
type: "button",
|
|
3096
|
+
disabled: index === (draft.models ?? []).length - 1,
|
|
3097
|
+
"aria-label": `${row.name} ${t("ollamaMoveDown")}`,
|
|
3098
|
+
onClick: () => edit({
|
|
3099
|
+
...draft,
|
|
3100
|
+
models: moveModel(draft.models ?? [], row.id, 1)
|
|
3101
|
+
}),
|
|
3102
|
+
children: t("ollamaMoveDown")
|
|
3103
|
+
})
|
|
3104
|
+
]
|
|
3105
|
+
})
|
|
3106
|
+
]
|
|
3107
|
+
}, row.id);
|
|
3108
|
+
}), shown.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3109
|
+
className: "dsh-subscription-hint",
|
|
3110
|
+
children: query.trim() ? t("modelDefaultsFilterEmpty", { query }) : t("ollamaModelsEmpty")
|
|
3111
|
+
})]
|
|
3112
|
+
}),
|
|
3113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("modelsContextHint") })
|
|
3114
|
+
]
|
|
3115
|
+
}),
|
|
3116
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3117
|
+
style: {
|
|
3118
|
+
...stack,
|
|
3119
|
+
gap: 6
|
|
3120
|
+
},
|
|
3121
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3122
|
+
className: "dsh-subscription-label",
|
|
3123
|
+
children: t("ollamaBaseURL")
|
|
3124
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3125
|
+
value: draft.baseURL ?? "",
|
|
3126
|
+
placeholder: view.effective.baseURL,
|
|
3127
|
+
onChange: (event) => editOverride("baseURL", event.target.value)
|
|
3128
|
+
})]
|
|
3129
|
+
}),
|
|
3130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3131
|
+
style: {
|
|
3132
|
+
...stack,
|
|
3133
|
+
gap: 6
|
|
3134
|
+
},
|
|
3135
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3136
|
+
className: "dsh-subscription-label",
|
|
3137
|
+
children: t("ollamaApiKeyRef")
|
|
3138
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3139
|
+
value: draft.apiKeyRef ?? "",
|
|
3140
|
+
placeholder: view.effective.apiKeyRef,
|
|
3141
|
+
onChange: (event) => editOverride("apiKeyRef", event.target.value)
|
|
3142
|
+
})]
|
|
3143
|
+
}),
|
|
3144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3145
|
+
className: "dsh-subscription-hint",
|
|
3146
|
+
children: t("ollamaConnectionHint")
|
|
3147
|
+
}),
|
|
3148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
3149
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3150
|
+
type: "checkbox",
|
|
3151
|
+
checked: (draft.web ?? view.effective.web) === true,
|
|
3152
|
+
onChange: (event) => edit({
|
|
3153
|
+
...draft,
|
|
3154
|
+
web: event.target.checked
|
|
3155
|
+
})
|
|
3156
|
+
}),
|
|
3157
|
+
" ",
|
|
3158
|
+
t("modelsWebSearch")
|
|
3159
|
+
] }),
|
|
3160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3161
|
+
className: "dsh-subscription-hint",
|
|
3162
|
+
children: t("ollamaWebHint")
|
|
3163
|
+
})
|
|
3164
|
+
]
|
|
3165
|
+
}),
|
|
3166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
3167
|
+
style: {
|
|
3168
|
+
...stack,
|
|
3169
|
+
gap: 8,
|
|
3170
|
+
borderTop: border,
|
|
3171
|
+
padding: "14px 0 8px",
|
|
3172
|
+
position: "sticky",
|
|
3173
|
+
bottom: 0,
|
|
3174
|
+
background: "var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))"
|
|
3175
|
+
},
|
|
3176
|
+
children: [saveError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3177
|
+
role: "alert",
|
|
3178
|
+
children: saveError
|
|
3179
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3180
|
+
style: {
|
|
3181
|
+
...actions,
|
|
3182
|
+
justifyContent: "flex-end"
|
|
3183
|
+
},
|
|
3184
|
+
children: [
|
|
3185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3186
|
+
type: "button",
|
|
3187
|
+
style: { marginRight: "auto" },
|
|
3188
|
+
disabled: loading || saving,
|
|
3189
|
+
onClick: () => {
|
|
3190
|
+
resetDefaults();
|
|
3191
|
+
},
|
|
3192
|
+
children: t("ollamaResetDefaults")
|
|
3193
|
+
}),
|
|
3194
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3195
|
+
type: "button",
|
|
3196
|
+
disabled: saving,
|
|
3197
|
+
onClick: onClose,
|
|
3198
|
+
children: t("cancel")
|
|
3199
|
+
}),
|
|
3200
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3201
|
+
type: "button",
|
|
3202
|
+
className: "dsh-subscription-primary",
|
|
3203
|
+
disabled: loading || saving || !dirty || invalidContext.length > 0,
|
|
3204
|
+
onClick: () => {
|
|
3205
|
+
save();
|
|
3206
|
+
},
|
|
3207
|
+
children: saving ? t("modelDefaultsSaving") : t("modelsSave")
|
|
3208
|
+
})
|
|
3209
|
+
]
|
|
3210
|
+
})]
|
|
3211
|
+
})
|
|
3212
|
+
]
|
|
3213
|
+
})]
|
|
3214
|
+
});
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
//#endregion
|
|
3218
|
+
//#region src/client/OllamaCard.tsx
|
|
3219
|
+
function message(error) {
|
|
3220
|
+
return error instanceof Error ? error.message : String(error);
|
|
3221
|
+
}
|
|
3222
|
+
/**
|
|
3223
|
+
* Ollama Cloud route card: the catalog the model picker shows, the web
|
|
3224
|
+
* capability toggle, and the API-key usage row. The settings dialog owns
|
|
3225
|
+
* every write; the card only reads, so a save is reflected by re-reading.
|
|
3226
|
+
*/
|
|
3227
|
+
function OllamaCard({ rpc, t }) {
|
|
3228
|
+
const [view, setView] = (0, react.useState)();
|
|
3229
|
+
const [configured, setConfigured] = (0, react.useState)();
|
|
3230
|
+
const [usage, setUsage] = (0, react.useState)();
|
|
3231
|
+
const [observedAt, setObservedAt] = (0, react.useState)();
|
|
3232
|
+
const [usageStale, setUsageStale] = (0, react.useState)(false);
|
|
3233
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
3234
|
+
const [statusError, setStatusError] = (0, react.useState)();
|
|
3235
|
+
const [usageError, setUsageError] = (0, react.useState)();
|
|
3236
|
+
const [manage, setManage] = (0, react.useState)(false);
|
|
3237
|
+
const refreshUsage = (0, react.useCallback)(async () => {
|
|
3238
|
+
setLoading(true);
|
|
3239
|
+
try {
|
|
3240
|
+
const value = await callSubscriptionsAuth(rpc, "externalUsage", { source: "ollama-cloud" });
|
|
3241
|
+
setUsage(value);
|
|
3242
|
+
setObservedAt(value.observedAt ?? Date.now());
|
|
3243
|
+
setUsageStale(value.stale === true);
|
|
3244
|
+
setUsageError(void 0);
|
|
3245
|
+
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
3246
|
+
} catch (failure) {
|
|
3247
|
+
setUsageError(message(failure));
|
|
3248
|
+
setUsageStale(true);
|
|
3249
|
+
} finally {
|
|
3250
|
+
setLoading(false);
|
|
3251
|
+
}
|
|
3252
|
+
}, [rpc]);
|
|
3253
|
+
const refreshStatus = (0, react.useCallback)(async () => {
|
|
3254
|
+
try {
|
|
3255
|
+
const [settings, status] = await Promise.all([callSubscriptionsAuth(rpc, "ollamaSettings", {}), callSubscriptionsAuth(rpc, "externalStatus", {})]);
|
|
3256
|
+
setView(settings);
|
|
3257
|
+
setStatusError(void 0);
|
|
3258
|
+
const isConfigured = status["ollama-cloud"]?.configured === true;
|
|
3259
|
+
setConfigured(isConfigured);
|
|
3260
|
+
if (isConfigured) refreshUsage();
|
|
3261
|
+
} catch (failure) {
|
|
3262
|
+
setStatusError(message(failure));
|
|
3263
|
+
}
|
|
3264
|
+
}, [rpc, refreshUsage]);
|
|
3265
|
+
(0, react.useEffect)(() => {
|
|
3266
|
+
refreshStatus();
|
|
3267
|
+
}, [refreshStatus]);
|
|
3268
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3269
|
+
style: subscriptionCardStyles.card,
|
|
3270
|
+
children: [
|
|
3271
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3272
|
+
style: subscriptionCardStyles.header,
|
|
3273
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
3274
|
+
...subscriptionCardStyles.dot,
|
|
3275
|
+
background: configured === true ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-label-dimmed)"
|
|
3276
|
+
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3277
|
+
style: subscriptionCardStyles.name,
|
|
3278
|
+
children: t("ollamaTitle")
|
|
3279
|
+
})]
|
|
3280
|
+
}),
|
|
3281
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3282
|
+
style: subscriptionCardStyles.status,
|
|
3283
|
+
children: view === void 0 ? t("checking") : view.models.length > 0 ? t("ollamaStatus", { count: view.models.length }) : t("ollamaStatusNone")
|
|
3284
|
+
}),
|
|
3285
|
+
view !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3286
|
+
style: subscriptionCardStyles.status,
|
|
3287
|
+
children: view.effective.web ? t("ollamaWebOn") : t("ollamaWebOff")
|
|
3288
|
+
}),
|
|
3289
|
+
statusError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3290
|
+
style: subscriptionCardStyles.error,
|
|
3291
|
+
children: t("ollamaModelsLoadFailed", { message: statusError })
|
|
3292
|
+
}),
|
|
3293
|
+
view !== void 0 && configured === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3294
|
+
style: subscriptionCardStyles.status,
|
|
3295
|
+
children: t("ollamaConfigureHint", { ref: view.effective.apiKeyRef })
|
|
3296
|
+
}),
|
|
3297
|
+
configured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3298
|
+
style: subscriptionCardStyles.account,
|
|
3299
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3300
|
+
style: subscriptionCardStyles.accountHeader,
|
|
3301
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3302
|
+
style: subscriptionCardStyles.defaultStar,
|
|
3303
|
+
title: t("defaultBadge"),
|
|
3304
|
+
children: "★"
|
|
3305
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3306
|
+
style: subscriptionCardStyles.accountName,
|
|
3307
|
+
children: t("externalUsageApiKey")
|
|
3308
|
+
})]
|
|
3309
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3310
|
+
style: subscriptionCardStyles.usage,
|
|
3311
|
+
children: [
|
|
3312
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3313
|
+
style: subscriptionCardStyles.usageHeader,
|
|
3314
|
+
children: [
|
|
3315
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3316
|
+
style: subscriptionCardStyles.usageTitle,
|
|
3317
|
+
children: t("usageTitle")
|
|
3318
|
+
}),
|
|
3319
|
+
usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3320
|
+
style: subscriptionCardStyles.usagePlan,
|
|
3321
|
+
children: t("usagePlan", { plan: usage.plan })
|
|
3322
|
+
}),
|
|
3323
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3324
|
+
type: "button",
|
|
3325
|
+
style: subscriptionCardStyles.usageRefresh,
|
|
3326
|
+
disabled: loading,
|
|
3327
|
+
onClick: () => {
|
|
3328
|
+
refreshUsage();
|
|
3329
|
+
},
|
|
3330
|
+
children: t("usageRefresh")
|
|
3331
|
+
})
|
|
3332
|
+
]
|
|
3333
|
+
}),
|
|
3334
|
+
loading && usage === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3335
|
+
style: subscriptionCardStyles.status,
|
|
3336
|
+
children: t("usageLoading")
|
|
3337
|
+
}),
|
|
3338
|
+
usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3339
|
+
style: subscriptionCardStyles.error,
|
|
3340
|
+
children: t("usageError", { message: usageError })
|
|
3341
|
+
}),
|
|
3342
|
+
usage !== void 0 && usage.supported === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3343
|
+
style: subscriptionCardStyles.status,
|
|
3344
|
+
children: t("usageUnsupported")
|
|
3345
|
+
}),
|
|
3346
|
+
usage?.windows?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3347
|
+
style: subscriptionCardStyles.status,
|
|
3348
|
+
children: t("usageEmpty")
|
|
3349
|
+
}),
|
|
3350
|
+
usage?.windows?.map((window$1, index) => {
|
|
3351
|
+
const percent = displayUsedPercent(window$1.usedPercent);
|
|
3352
|
+
const label = externalUsageWindowLabel(t, window$1);
|
|
3353
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3354
|
+
style: subscriptionCardStyles.usageRow,
|
|
3355
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3356
|
+
style: subscriptionCardStyles.usageMeta,
|
|
3357
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [percent === void 0 ? t("usageMeterInvalid") : `${percent}%`, window$1.resetsAt === void 0 ? "" : ` · ${t("usageResets", { date: new Date(window$1.resetsAt).toLocaleString() })}`] })]
|
|
3358
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
3359
|
+
window: window$1,
|
|
3360
|
+
t,
|
|
3361
|
+
observedAt,
|
|
3362
|
+
stale: usageStale
|
|
3363
|
+
})]
|
|
3364
|
+
}, index);
|
|
3365
|
+
})
|
|
3366
|
+
]
|
|
3367
|
+
})]
|
|
3368
|
+
}),
|
|
3369
|
+
view?.configOverridesModels === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3370
|
+
style: subscriptionCardStyles.status,
|
|
3371
|
+
children: t("ollamaCatalogOverride")
|
|
3372
|
+
}),
|
|
3373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3374
|
+
style: subscriptionCardStyles.actions,
|
|
3375
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3376
|
+
type: "button",
|
|
3377
|
+
style: subscriptionCardStyles.button,
|
|
3378
|
+
"aria-haspopup": "dialog",
|
|
3379
|
+
onClick: () => setManage(true),
|
|
3380
|
+
children: t("accountsManage")
|
|
3381
|
+
}), configured !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3382
|
+
type: "button",
|
|
3383
|
+
style: subscriptionCardStyles.button,
|
|
3384
|
+
onClick: () => {
|
|
3385
|
+
refreshStatus();
|
|
3386
|
+
},
|
|
3387
|
+
children: t("usageRefresh")
|
|
3388
|
+
})]
|
|
3389
|
+
}),
|
|
3390
|
+
manage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OllamaSettingsManager, {
|
|
3391
|
+
rpc,
|
|
3392
|
+
t,
|
|
3393
|
+
onClose: () => setManage(false),
|
|
3394
|
+
onSaved: () => {
|
|
3395
|
+
refreshStatus();
|
|
3396
|
+
}
|
|
3397
|
+
})
|
|
3398
|
+
]
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
//#endregion
|
|
3403
|
+
//#region src/client/reset-credits-view.tsx
|
|
3404
|
+
const STATE_KEYS = {
|
|
3405
|
+
available: "resetCreditAvailable",
|
|
3406
|
+
notUsable: "resetCreditNotUsable",
|
|
3407
|
+
paused: "resetCreditPaused",
|
|
3408
|
+
exhausted: "resetCreditExhausted",
|
|
3409
|
+
expired: "resetCreditExpired"
|
|
3410
|
+
};
|
|
3411
|
+
/**
|
|
3412
|
+
* Classify one grant. Expired outranks every other flag. A future
|
|
3413
|
+
* `cooldownUntil` or `usableNow: false` is not usable even when `claimable`
|
|
3414
|
+
* is true. `claimable` does not change the word: it is not an action.
|
|
3415
|
+
*/
|
|
3416
|
+
function resetCreditState(credit, now) {
|
|
3417
|
+
if (credit.expiresAt !== void 0 && credit.expiresAt <= now) return "expired";
|
|
3418
|
+
if (credit.paused === true) return "paused";
|
|
3419
|
+
if (credit.resetsLeft === 0) return "exhausted";
|
|
3420
|
+
if (credit.cooldownUntil !== void 0 && credit.cooldownUntil > now || credit.usableNow === false) return "notUsable";
|
|
3421
|
+
return "available";
|
|
3422
|
+
}
|
|
3423
|
+
/**
|
|
3424
|
+
* Headline count. Codex passes one entry per credit, so the count is the
|
|
3425
|
+
* list length. Claude passes one entry per grant, so the count is the sum of
|
|
3426
|
+
* `resetsLeft` on grants that are neither expired nor paused. A missing
|
|
3427
|
+
* `resetsLeft` adds nothing; the row says the count is unavailable.
|
|
3428
|
+
*/
|
|
3429
|
+
function resetCreditsAvailableCount(credits, now, mode) {
|
|
3430
|
+
if (mode === "credits") return credits.length;
|
|
3431
|
+
let sum = 0;
|
|
3432
|
+
for (const credit of credits) {
|
|
3433
|
+
const state = resetCreditState(credit, now);
|
|
3434
|
+
if (state === "expired" || state === "paused") continue;
|
|
3435
|
+
if (typeof credit.resetsLeft === "number" && Number.isFinite(credit.resetsLeft) && credit.resetsLeft > 0) sum += credit.resetsLeft;
|
|
3436
|
+
}
|
|
3437
|
+
return sum;
|
|
3438
|
+
}
|
|
3439
|
+
function expiryText(t, credit) {
|
|
3440
|
+
return credit.expiresAt === void 0 ? t("resetCreditExpiryUnknown") : t("resetCreditExpires", { date: new Date(credit.expiresAt).toLocaleString() });
|
|
3441
|
+
}
|
|
3442
|
+
function remainingText(t, credit) {
|
|
3443
|
+
const left = credit.resetsLeft;
|
|
3444
|
+
const total = credit.resetsTotal;
|
|
3445
|
+
const hasLeft = typeof left === "number" && Number.isFinite(left);
|
|
3446
|
+
if (hasLeft && typeof total === "number" && Number.isFinite(total)) return t("resetCreditRemaining", {
|
|
3447
|
+
left,
|
|
3448
|
+
total
|
|
3449
|
+
});
|
|
3450
|
+
if (hasLeft) return t("resetCreditLeft", { left });
|
|
3451
|
+
return t("resetCreditRemainingUnknown");
|
|
3452
|
+
}
|
|
3453
|
+
/** Accessible name of one Claude grant row: state, remaining, and expiry, not a control name. */
|
|
3454
|
+
function resetCreditRowName(t, credit, now) {
|
|
3455
|
+
const state = resetCreditState(credit, now);
|
|
3456
|
+
const parts = [
|
|
3457
|
+
t("resetCreditFull"),
|
|
3458
|
+
t(STATE_KEYS[state]),
|
|
3459
|
+
remainingText(t, credit),
|
|
3460
|
+
expiryText(t, credit)
|
|
3461
|
+
];
|
|
3462
|
+
if (state === "notUsable" && credit.cooldownUntil !== void 0 && credit.cooldownUntil > now) parts.push(t("resetCreditCooldownUntil", { date: new Date(credit.cooldownUntil).toLocaleString() }));
|
|
3463
|
+
return parts.join(", ");
|
|
3464
|
+
}
|
|
3465
|
+
/** Claude omits an empty grant list; Codex still shows a defined, possibly empty, list. */
|
|
3466
|
+
function showsResetCredits(provider, credits) {
|
|
3467
|
+
if (provider === "codex") return credits !== void 0;
|
|
3468
|
+
return (credits?.length ?? 0) > 0;
|
|
3469
|
+
}
|
|
3470
|
+
/** The error line the settings card mounts. Claude never interpolates provider text. */
|
|
3471
|
+
function ResetCreditsErrorLine({ provider, message: message$3, t }) {
|
|
3472
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3473
|
+
style: subscriptionCardStyles.error,
|
|
3474
|
+
children: resetCreditsErrorText(provider, message$3, t)
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
/** Fixed sentence for Claude. Codex may include the lookup message; Claude must not. */
|
|
3478
|
+
function resetCreditsErrorText(provider, message$3, t) {
|
|
3479
|
+
return provider === "claude" ? t("resetCreditsErrorFixed") : t("resetCreditsError", { message: message$3 });
|
|
3480
|
+
}
|
|
3481
|
+
const summaryStyle = {
|
|
3482
|
+
...subscriptionCardStyles.usageMeta,
|
|
3483
|
+
cursor: "pointer",
|
|
3484
|
+
listStyle: "none",
|
|
3485
|
+
justifyContent: "flex-start",
|
|
3486
|
+
gap: 6
|
|
3487
|
+
};
|
|
3488
|
+
/**
|
|
3489
|
+
* The closed disclosure both providers share. Claude rows are plain text:
|
|
3490
|
+
* no button, no `role="button"`, and `cursor: default`, including when
|
|
3491
|
+
* `claimable` is true.
|
|
3492
|
+
*/
|
|
3493
|
+
function ResetCreditsDisclosure({ mode, credits, t, now = Date.now(), nextCredit, useAction, statusMessage }) {
|
|
3494
|
+
const count = resetCreditsAvailableCount(credits, now, mode);
|
|
3495
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
3496
|
+
className: "subscriptions-reset-credits",
|
|
3497
|
+
style: subscriptionCardStyles.usageRow,
|
|
3498
|
+
children: [
|
|
3499
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
3500
|
+
style: summaryStyle,
|
|
3501
|
+
children: [
|
|
3502
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3503
|
+
"aria-hidden": "true",
|
|
3504
|
+
width: "12",
|
|
3505
|
+
height: "12",
|
|
3506
|
+
viewBox: "0 0 16 16",
|
|
3507
|
+
style: { flexShrink: 0 },
|
|
3508
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
3509
|
+
d: "m6 3 5 5-5 5",
|
|
3510
|
+
fill: "none",
|
|
3511
|
+
stroke: "currentColor",
|
|
3512
|
+
strokeWidth: "1.5",
|
|
3513
|
+
strokeLinecap: "round",
|
|
3514
|
+
strokeLinejoin: "round"
|
|
3515
|
+
})
|
|
3516
|
+
}),
|
|
3517
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetCreditsTitle") }),
|
|
3518
|
+
mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetCreditReadOnlyLabel") }),
|
|
3519
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3520
|
+
style: { marginLeft: "auto" },
|
|
3521
|
+
children: t("resetCreditsAvailable", { count })
|
|
3522
|
+
})
|
|
3523
|
+
]
|
|
3524
|
+
}),
|
|
3525
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: ".subscriptions-reset-credits > summary::-webkit-details-marker { display: none; }.subscriptions-reset-credits[open] > summary > svg { transform: rotate(90deg); }" }),
|
|
3526
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3527
|
+
style: {
|
|
3528
|
+
paddingLeft: 18,
|
|
3529
|
+
marginTop: 6
|
|
3530
|
+
},
|
|
3531
|
+
children: [
|
|
3532
|
+
mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3533
|
+
style: subscriptionCardStyles.status,
|
|
3534
|
+
children: t("resetCreditReadOnly")
|
|
3535
|
+
}),
|
|
3536
|
+
credits.map((credit, index) => {
|
|
3537
|
+
const state = resetCreditState(credit, now);
|
|
3538
|
+
const showUse = mode === "credits" && useAction !== void 0 && credit === nextCredit;
|
|
3539
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3540
|
+
"data-reset-state": mode === "grants" ? state : void 0,
|
|
3541
|
+
"data-reset-claimable": mode === "grants" && credit.claimable === true ? "true" : void 0,
|
|
3542
|
+
role: mode === "grants" ? "group" : void 0,
|
|
3543
|
+
"aria-label": mode === "grants" ? resetCreditRowName(t, credit, now) : void 0,
|
|
3544
|
+
style: {
|
|
3545
|
+
...subscriptionCardStyles.usageMeta,
|
|
3546
|
+
minHeight: 28,
|
|
3547
|
+
gap: 12,
|
|
3548
|
+
cursor: mode === "grants" ? "default" : void 0
|
|
3549
|
+
},
|
|
3550
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3551
|
+
style: {
|
|
3552
|
+
display: "inline-flex",
|
|
3553
|
+
alignItems: "center",
|
|
3554
|
+
gap: 8,
|
|
3555
|
+
flexShrink: 0
|
|
3556
|
+
},
|
|
3557
|
+
children: [
|
|
3558
|
+
t("resetCreditFull"),
|
|
3559
|
+
mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3560
|
+
"aria-hidden": "true",
|
|
3561
|
+
"data-reset-state-word": state,
|
|
3562
|
+
children: t(STATE_KEYS[state])
|
|
3563
|
+
}),
|
|
3564
|
+
showUse && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3565
|
+
type: "button",
|
|
3566
|
+
"aria-disabled": useAction.disabled,
|
|
3567
|
+
title: useAction.title,
|
|
3568
|
+
style: {
|
|
3569
|
+
minHeight: 22,
|
|
3570
|
+
height: 22,
|
|
3571
|
+
padding: "0 7px",
|
|
3572
|
+
borderRadius: 6,
|
|
3573
|
+
opacity: useAction.disabled ? .35 : 1,
|
|
3574
|
+
cursor: useAction.disabled ? "not-allowed" : "pointer"
|
|
3575
|
+
},
|
|
3576
|
+
onClick: () => {
|
|
3577
|
+
if (!useAction.disabled) useAction.onUse();
|
|
3578
|
+
},
|
|
3579
|
+
children: useAction.busy ? t("resetUseChecking") : useAction.label
|
|
3580
|
+
})
|
|
3581
|
+
]
|
|
3582
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3583
|
+
style: { textAlign: "right" },
|
|
3584
|
+
children: [
|
|
3585
|
+
mode === "grants" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3586
|
+
"aria-hidden": "true",
|
|
3587
|
+
children: [remainingText(t, credit), " · "]
|
|
3588
|
+
}),
|
|
3589
|
+
mode === "grants" && state === "notUsable" && credit.cooldownUntil !== void 0 && credit.cooldownUntil > now && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3590
|
+
"aria-hidden": "true",
|
|
3591
|
+
children: [t("resetCreditCooldownUntil", { date: new Date(credit.cooldownUntil).toLocaleString() }), " · "]
|
|
3592
|
+
}),
|
|
3593
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3594
|
+
"aria-hidden": mode === "grants" ? true : void 0,
|
|
3595
|
+
children: expiryText(t, credit)
|
|
3596
|
+
})
|
|
3597
|
+
]
|
|
3598
|
+
})]
|
|
3599
|
+
}, credit.id ?? index);
|
|
3600
|
+
}),
|
|
3601
|
+
statusMessage !== void 0 && statusMessage !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3602
|
+
role: "status",
|
|
3603
|
+
style: subscriptionCardStyles.status,
|
|
3604
|
+
children: statusMessage
|
|
3605
|
+
})
|
|
3606
|
+
]
|
|
3607
|
+
})
|
|
3608
|
+
]
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
//#endregion
|
|
3613
|
+
//#region src/client/UsageBadgeDisplaySetting.tsx
|
|
3614
|
+
/** Browser-local display preference; never changes provider or account settings. */
|
|
3615
|
+
function UsageBadgeDisplaySetting({ t }) {
|
|
3616
|
+
const mode = useUsageBadgeMode();
|
|
2482
3617
|
const preset = useUsageColorPreset();
|
|
2483
3618
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
2484
3619
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2872,12 +4007,12 @@ function SubscriptionsSection(props) {
|
|
|
2872
4007
|
const usageInflightRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
2873
4008
|
const usageRosterSignatureRef = (0, react.useRef)();
|
|
2874
4009
|
const [managedProvider, setManagedProvider] = (0, react.useState)();
|
|
2875
|
-
const setProviderError = (0, react.useCallback)((provider, message$
|
|
4010
|
+
const setProviderError = (0, react.useCallback)((provider, message$3) => {
|
|
2876
4011
|
if (!mountedRef.current) return;
|
|
2877
4012
|
setErrors((prev) => {
|
|
2878
4013
|
const next = { ...prev };
|
|
2879
|
-
if (message$
|
|
2880
|
-
else next[provider] = message$
|
|
4014
|
+
if (message$3 === void 0) delete next[provider];
|
|
4015
|
+
else next[provider] = message$3;
|
|
2881
4016
|
return next;
|
|
2882
4017
|
});
|
|
2883
4018
|
}, []);
|
|
@@ -2895,8 +4030,8 @@ function SubscriptionsSection(props) {
|
|
|
2895
4030
|
try {
|
|
2896
4031
|
response = await callSubscriptionsAuth(rpc, "status", {});
|
|
2897
4032
|
} catch (error) {
|
|
2898
|
-
const message$
|
|
2899
|
-
for (const { id } of PROVIDERS) setProviderError(id, message$
|
|
4033
|
+
const message$3 = error instanceof Error ? error.message : String(error);
|
|
4034
|
+
for (const { id } of PROVIDERS) setProviderError(id, message$3);
|
|
2900
4035
|
return;
|
|
2901
4036
|
}
|
|
2902
4037
|
if (!mountedRef.current) return;
|
|
@@ -3466,6 +4601,10 @@ function SubscriptionsSection(props) {
|
|
|
3466
4601
|
rpc,
|
|
3467
4602
|
t
|
|
3468
4603
|
}),
|
|
4604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OllamaCard, {
|
|
4605
|
+
rpc,
|
|
4606
|
+
t
|
|
4607
|
+
}),
|
|
3469
4608
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalUsageCards, {
|
|
3470
4609
|
rpc,
|
|
3471
4610
|
t
|
|
@@ -4573,7 +5712,8 @@ const PROVIDER_NAMES = {
|
|
|
4573
5712
|
"opencode-go": "OpenCode Go",
|
|
4574
5713
|
"kimi-coding": "Kimi Code",
|
|
4575
5714
|
"minimax": "MiniMax",
|
|
4576
|
-
"minimax-cn": "MiniMax CN"
|
|
5715
|
+
"minimax-cn": "MiniMax CN",
|
|
5716
|
+
"ollama-cloud": "Ollama Cloud"
|
|
4577
5717
|
};
|
|
4578
5718
|
/** Key of one account's cached windows: `provider:accountKey`. */
|
|
4579
5719
|
function usageKeyOf(provider, account) {
|
|
@@ -4649,7 +5789,8 @@ async function loadBadgeRoster(rpc) {
|
|
|
4649
5789
|
["opencode-go", "opencode-go"],
|
|
4650
5790
|
["kimi-code", "kimi-coding"],
|
|
4651
5791
|
["minimax", "minimax"],
|
|
4652
|
-
["minimax-cn", "minimax-cn"]
|
|
5792
|
+
["minimax-cn", "minimax-cn"],
|
|
5793
|
+
["ollama-cloud", "ollama-cloud"]
|
|
4653
5794
|
]) {
|
|
4654
5795
|
refreshed.add(provider);
|
|
4655
5796
|
if (external.value[source]?.configured) roster.push({
|
|
@@ -4667,7 +5808,7 @@ async function loadBadgeRoster(rpc) {
|
|
|
4667
5808
|
}
|
|
4668
5809
|
async function usageOf(rpc, { provider, account }) {
|
|
4669
5810
|
if (provider === "cursor-subscription") return callSubscriptionsAuth(rpc, "cursorUsage", {});
|
|
4670
|
-
if (provider === "opencode-go" || provider === "kimi-coding" || provider === "minimax" || provider === "minimax-cn") return callSubscriptionsAuth(rpc, "externalUsage", { source: provider === "kimi-coding" ? "kimi-code" : provider });
|
|
5811
|
+
if (provider === "opencode-go" || provider === "kimi-coding" || provider === "minimax" || provider === "minimax-cn" || provider === "ollama-cloud") return callSubscriptionsAuth(rpc, "externalUsage", { source: provider === "kimi-coding" ? "kimi-code" : provider });
|
|
4671
5812
|
return callSubscriptionsAuth(rpc, "usage", {
|
|
4672
5813
|
provider,
|
|
4673
5814
|
account: account.key
|