dsh-subscriptions 0.1.5 → 0.2.2
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 +212 -154
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.d.ts +73 -2
- package/lib/auth/rpc.js +124 -23
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +13 -0
- package/lib/client/OllamaCard.js +94 -0
- package/lib/client/OllamaSettingsManager.d.ts +24 -0
- package/lib/client/OllamaSettingsManager.js +329 -0
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +109 -26
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +257 -46
- package/lib/client/SubscriptionUsageBadge.d.ts +5 -11
- package/lib/client/SubscriptionUsageBadge.js +301 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +259 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +69 -27
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +78 -0
- package/lib/client/locales.js +121 -34
- package/lib/client/ollama-draft.d.ts +229 -0
- package/lib/client/ollama-draft.js +255 -0
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.d.ts +7 -0
- package/lib/client/usage-pace.js +43 -10
- package/lib/client.js +1647 -266
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +28 -3
- package/lib/index.js +9329 -8217
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +14 -1
- package/lib/provider-settings.js +64 -20
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +23 -3
- package/lib/providers/claude.js +167 -92
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.d.ts +14 -0
- package/lib/providers/common.js +118 -38
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.d.ts +9 -2
- package/lib/providers/external-usage-controller.js +30 -7
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +140 -20
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.d.ts +90 -0
- package/lib/providers/ollama-settings.js +213 -0
- package/lib/providers/ollama-web.d.ts +46 -0
- package/lib/providers/ollama-web.js +231 -0
- package/lib/providers/ollama.d.ts +107 -0
- package/lib/providers/ollama.js +608 -0
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +141 -50
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +26 -4
package/lib/client.js
CHANGED
|
@@ -72,6 +72,11 @@ const en = {
|
|
|
72
72
|
modelsWebSearch: "Web search",
|
|
73
73
|
modelsSave: "Save changes",
|
|
74
74
|
modelsUnavailable: "Currently unavailable",
|
|
75
|
+
claudeCacheTtl: "Prompt cache lifetime",
|
|
76
|
+
claudeCacheTtlFollowConfig: "Follow config",
|
|
77
|
+
claudeCacheTtl5m: "5 minutes",
|
|
78
|
+
claudeCacheTtl1h: "1 hour",
|
|
79
|
+
claudeCacheTtlHint: "Overrides the claudePromptCacheTtl config for this Claude route; Follow config uses the configured value. Saved changes apply from the next request. One hour keeps cached prefixes longer but writes them at a higher input price; compaction and session-title requests stay at five minutes.",
|
|
75
80
|
nav: "Subscriptions",
|
|
76
81
|
intro: "Log a subscription provider in or out. Login opens the provider’s authorization page in a new tab; headless setups can paste the callback URL or code instead.",
|
|
77
82
|
unavailable: "Connection unavailable; subscription status cannot be loaded.",
|
|
@@ -108,6 +113,7 @@ const en = {
|
|
|
108
113
|
usageRefresh: "Refresh",
|
|
109
114
|
usageLoading: "Loading usage…",
|
|
110
115
|
usageEmpty: "No usage windows reported.",
|
|
116
|
+
usageUnsupported: "This endpoint has no usage surface.",
|
|
111
117
|
usageError: "Usage lookup failed: {message}",
|
|
112
118
|
usageSession: "5-hour window",
|
|
113
119
|
usageWeekly: "Weekly",
|
|
@@ -233,7 +239,40 @@ const en = {
|
|
|
233
239
|
speedFast: "Fast",
|
|
234
240
|
speedFastDescription: "1.5x speed, more usage",
|
|
235
241
|
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"
|
|
242
|
+
commandFastUnavailable: "The current model has no fast tier; /fast only works on Codex models whose catalog advertises one",
|
|
243
|
+
ollamaTitle: "Ollama Cloud",
|
|
244
|
+
ollamaStatus: "{count} model(s) in the picker",
|
|
245
|
+
ollamaStatusNone: "No models configured",
|
|
246
|
+
ollamaWebOn: "Web search/fetch enabled",
|
|
247
|
+
ollamaWebOff: "Web search/fetch off",
|
|
248
|
+
ollamaManageTitle: "Manage Ollama Cloud",
|
|
249
|
+
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.",
|
|
250
|
+
ollamaFetch: "Fetch cloud models",
|
|
251
|
+
ollamaFetching: "Fetching…",
|
|
252
|
+
ollamaKeyPlaceholder: "One-time API key (optional)",
|
|
253
|
+
ollamaKeyHint: "A key typed here is used for this fetch only and is never saved. Leave it blank to use the stored credential.",
|
|
254
|
+
ollamaFetchFailed: "Could not fetch cloud models: {message}",
|
|
255
|
+
ollamaCandidatesCount: "{count} returned",
|
|
256
|
+
ollamaCandidatesEmpty: "No cloud models returned.",
|
|
257
|
+
ollamaModelsEmpty: "No models configured. Fetch the cloud list and tick the ones to keep.",
|
|
258
|
+
ollamaModelsLoadFailed: "Could not load Ollama settings: {message}",
|
|
259
|
+
ollamaModelsSaveFailed: "Could not save Ollama settings: {message}",
|
|
260
|
+
ollamaConfigureHint: "No API key found at {ref}. Add it to the credentials file (~/.dsh/.credentials.yaml).",
|
|
261
|
+
ollamaDisplayName: "Display name",
|
|
262
|
+
ollamaRowDetails: "Details",
|
|
263
|
+
ollamaEffortsUnknown: "This model reasons; its level set will come from live discovery.",
|
|
264
|
+
ollamaMoveUp: "Move up",
|
|
265
|
+
ollamaMoveDown: "Move down",
|
|
266
|
+
ollamaBaseURL: "Base URL",
|
|
267
|
+
ollamaApiKeyRef: "API key reference",
|
|
268
|
+
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.",
|
|
269
|
+
ollamaWebHint: "Enables the Ollama Cloud web search and fetch providers. The host still has to select them before the web tools use them.",
|
|
270
|
+
ollamaCatalogOverride: "A profile pins this route's model list; the picker ignores store models until that override is removed.",
|
|
271
|
+
ollamaResetDefaults: "Restore deployment defaults",
|
|
272
|
+
ollamaResetConfirm: "Clear every saved Ollama override? The profile values and built-in defaults apply again.",
|
|
273
|
+
ollamaCapVision: "vision",
|
|
274
|
+
ollamaCapThinking: "thinking",
|
|
275
|
+
ollamaContext: "context {tokens}"
|
|
237
276
|
};
|
|
238
277
|
/** zh strings, one per {@link en} key. */
|
|
239
278
|
const zh = {
|
|
@@ -273,6 +312,11 @@ const zh = {
|
|
|
273
312
|
modelsWebSearch: "网页搜索",
|
|
274
313
|
modelsSave: "保存更改",
|
|
275
314
|
modelsUnavailable: "当前不可用",
|
|
315
|
+
claudeCacheTtl: "提示词缓存时长",
|
|
316
|
+
claudeCacheTtlFollowConfig: "跟随配置",
|
|
317
|
+
claudeCacheTtl5m: "5 分钟",
|
|
318
|
+
claudeCacheTtl1h: "1 小时",
|
|
319
|
+
claudeCacheTtlHint: "覆盖此 Claude 路由的 claudePromptCacheTtl 配置;选择「跟随配置」时使用配置值。保存后自下一次请求生效。1 小时可更久保留缓存前缀,但写入按更高的输入价格计费;压缩与会话标题请求仍为 5 分钟。",
|
|
276
320
|
nav: "订阅",
|
|
277
321
|
intro: "在此登录或退出订阅服务商。点击登录会在新标签页打开服务商的授权页面;无浏览器环境可改为粘贴回调 URL 或授权码。",
|
|
278
322
|
unavailable: "连接不可用,无法加载订阅状态。",
|
|
@@ -309,6 +353,7 @@ const zh = {
|
|
|
309
353
|
usageRefresh: "刷新",
|
|
310
354
|
usageLoading: "用量加载中…",
|
|
311
355
|
usageEmpty: "服务商未返回任何用量窗口。",
|
|
356
|
+
usageUnsupported: "该端点不提供用量查询。",
|
|
312
357
|
usageError: "用量查询失败:{message}",
|
|
313
358
|
usageSession: "5 小时窗口",
|
|
314
359
|
usageWeekly: "每周",
|
|
@@ -434,7 +479,40 @@ const zh = {
|
|
|
434
479
|
speedFast: "快速",
|
|
435
480
|
speedFastDescription: "约 1.5 倍速度,消耗更多用量",
|
|
436
481
|
commandFast: "切换 Codex 速度档(标准/快速)",
|
|
437
|
-
commandFastUnavailable: "当前模型不支持快速档;/fast 仅对目录声明了 fast tier 的 Codex 模型可用"
|
|
482
|
+
commandFastUnavailable: "当前模型不支持快速档;/fast 仅对目录声明了 fast tier 的 Codex 模型可用",
|
|
483
|
+
ollamaTitle: "Ollama Cloud",
|
|
484
|
+
ollamaStatus: "选择器中 {count} 个模型",
|
|
485
|
+
ollamaStatusNone: "尚未配置模型",
|
|
486
|
+
ollamaWebOn: "网页搜索/抓取已启用",
|
|
487
|
+
ollamaWebOff: "网页搜索/抓取已关闭",
|
|
488
|
+
ollamaManageTitle: "管理 Ollama Cloud",
|
|
489
|
+
ollamaManageHint: "选择此路由提供的云端模型后保存。模型选择器按此顺序显示这份列表;API 密钥本身仍保存在 DSH 凭据中。",
|
|
490
|
+
ollamaFetch: "拉取云端模型",
|
|
491
|
+
ollamaFetching: "拉取中…",
|
|
492
|
+
ollamaKeyPlaceholder: "一次性 API 密钥(可选)",
|
|
493
|
+
ollamaKeyHint: "在此输入的密钥仅用于本次拉取,不会保存。留空则使用已存凭据。",
|
|
494
|
+
ollamaFetchFailed: "拉取云端模型失败:{message}",
|
|
495
|
+
ollamaCandidatesCount: "返回 {count} 个",
|
|
496
|
+
ollamaCandidatesEmpty: "未返回任何云端模型。",
|
|
497
|
+
ollamaModelsEmpty: "尚未配置模型。请拉取云端列表并勾选需要保留的模型。",
|
|
498
|
+
ollamaModelsLoadFailed: "Ollama 设置加载失败:{message}",
|
|
499
|
+
ollamaModelsSaveFailed: "Ollama 设置保存失败:{message}",
|
|
500
|
+
ollamaConfigureHint: "未在 {ref} 找到 API 密钥。请在凭据文件(~/.dsh/.credentials.yaml)中添加。",
|
|
501
|
+
ollamaDisplayName: "显示名",
|
|
502
|
+
ollamaRowDetails: "详情",
|
|
503
|
+
ollamaEffortsUnknown: "该模型支持思考,推理档位将由实时发现补充。",
|
|
504
|
+
ollamaMoveUp: "上移",
|
|
505
|
+
ollamaMoveDown: "下移",
|
|
506
|
+
ollamaBaseURL: "基础 URL",
|
|
507
|
+
ollamaApiKeyRef: "API 密钥引用",
|
|
508
|
+
ollamaConnectionHint: "基础 URL 用于此路由的请求与凭据查询;引用是 DSH 凭据的名称,不是密钥本身。请求会把解析出的密钥发往该地址,请只填写可信主机。",
|
|
509
|
+
ollamaWebHint: "启用 Ollama Cloud 的网页搜索与抓取能力。宿主仍需选择它们,网页工具才会使用。",
|
|
510
|
+
ollamaCatalogOverride: "配置档案固定了此路由的模型列表;在移除该覆盖前,选择器会忽略存储中的模型。",
|
|
511
|
+
ollamaResetDefaults: "恢复部署默认值",
|
|
512
|
+
ollamaResetConfirm: "清除全部已保存的 Ollama 覆盖项?将重新使用配置档案值与内置默认值。",
|
|
513
|
+
ollamaCapVision: "视觉",
|
|
514
|
+
ollamaCapThinking: "思考",
|
|
515
|
+
ollamaContext: "上下文 {tokens}"
|
|
438
516
|
};
|
|
439
517
|
|
|
440
518
|
//#endregion
|
|
@@ -508,6 +586,19 @@ function mergeLatestAccounts(draft, latest) {
|
|
|
508
586
|
...latest.accounts === void 0 ? {} : { accounts: latest.accounts }
|
|
509
587
|
};
|
|
510
588
|
}
|
|
589
|
+
/**
|
|
590
|
+
* The account manager owns the Claude prompt-cache TTL picker. The model
|
|
591
|
+
* editor's draft carries the TTL it loaded, so the dialog always replaces it
|
|
592
|
+
* with the picker's choice when touched, or the freshly re-read value
|
|
593
|
+
* otherwise.
|
|
594
|
+
*/
|
|
595
|
+
function mergePromptCacheTtl(settings, promptCacheTtl) {
|
|
596
|
+
const { promptCacheTtl: _stale,...rest } = settings;
|
|
597
|
+
return promptCacheTtl === void 0 ? rest : {
|
|
598
|
+
...rest,
|
|
599
|
+
promptCacheTtl
|
|
600
|
+
};
|
|
601
|
+
}
|
|
511
602
|
/** Only touched accounts replace their latest preferences; other settings survive. */
|
|
512
603
|
function mergeAccountChanges(latest, changes) {
|
|
513
604
|
return {
|
|
@@ -521,8 +612,10 @@ function mergeAccountChanges(latest, changes) {
|
|
|
521
612
|
|
|
522
613
|
//#endregion
|
|
523
614
|
//#region src/client/ProviderModelEditor.tsx
|
|
524
|
-
|
|
525
|
-
const
|
|
615
|
+
/** Providers whose runtime honours a local context-window override. */
|
|
616
|
+
const supportsContext = (provider) => provider === "codex" || provider === "claude";
|
|
617
|
+
const border$2 = "0.5px solid var(--dsw-alias-border-l2, #ddd)";
|
|
618
|
+
const actions$2 = {
|
|
526
619
|
display: "flex",
|
|
527
620
|
flexWrap: "wrap",
|
|
528
621
|
gap: 8,
|
|
@@ -595,7 +688,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
595
688
|
setError("");
|
|
596
689
|
const { accounts: _accounts,...settings } = {
|
|
597
690
|
...draft,
|
|
598
|
-
...provider
|
|
691
|
+
...supportsContext(provider) ? { contextWindows: windows } : {}
|
|
599
692
|
};
|
|
600
693
|
return {
|
|
601
694
|
settings,
|
|
@@ -613,7 +706,8 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
613
706
|
draft,
|
|
614
707
|
contexts,
|
|
615
708
|
efforts,
|
|
616
|
-
provider
|
|
709
|
+
provider,
|
|
710
|
+
t
|
|
617
711
|
]);
|
|
618
712
|
const allModels = catalog?.models ?? [];
|
|
619
713
|
const known = new Set(allModels.map((model) => model.id));
|
|
@@ -625,7 +719,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
625
719
|
const selected = new Set(draft.visibleModels ?? allModels.map((model) => model.id));
|
|
626
720
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
627
721
|
style: {
|
|
628
|
-
borderTop: border$
|
|
722
|
+
borderTop: border$2,
|
|
629
723
|
marginTop: 12,
|
|
630
724
|
paddingTop: 12
|
|
631
725
|
},
|
|
@@ -648,7 +742,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
648
742
|
children: error
|
|
649
743
|
}),
|
|
650
744
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
651
|
-
style: actions$
|
|
745
|
+
style: actions$2,
|
|
652
746
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
653
747
|
type: "button",
|
|
654
748
|
className: "dsh-subscription-quiet",
|
|
@@ -688,7 +782,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
688
782
|
t("modelsAutomatic")
|
|
689
783
|
] }),
|
|
690
784
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
691
|
-
style: actions$
|
|
785
|
+
style: actions$2,
|
|
692
786
|
children: [
|
|
693
787
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
694
788
|
style: { flex: "1 1 180px" },
|
|
@@ -724,7 +818,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
724
818
|
},
|
|
725
819
|
children: [models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
726
820
|
style: {
|
|
727
|
-
borderBottom: border$
|
|
821
|
+
borderBottom: border$2,
|
|
728
822
|
padding: "8px 2px",
|
|
729
823
|
display: "grid",
|
|
730
824
|
gap: 8
|
|
@@ -750,9 +844,9 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
750
844
|
!known.has(model.id) && ` (${t("modelsUnavailable")})`
|
|
751
845
|
]
|
|
752
846
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
753
|
-
style: actions$
|
|
847
|
+
style: actions$2,
|
|
754
848
|
children: [(model.efforts?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
755
|
-
style: actions$
|
|
849
|
+
style: actions$2,
|
|
756
850
|
children: [t("modelDefaultsTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
757
851
|
"aria-label": `${model.name} ${t("modelDefaultsTitle")}`,
|
|
758
852
|
value: efforts[model.id] ?? "",
|
|
@@ -784,10 +878,10 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
784
878
|
}, effort.id))
|
|
785
879
|
]
|
|
786
880
|
})]
|
|
787
|
-
}), provider
|
|
788
|
-
style: actions$
|
|
881
|
+
}), supportsContext(provider) && model.maxContextWindow !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
882
|
+
style: actions$2,
|
|
789
883
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
790
|
-
style: actions$
|
|
884
|
+
style: actions$2,
|
|
791
885
|
children: [t("modelsContext"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
792
886
|
style: { width: 140 },
|
|
793
887
|
inputMode: "numeric",
|
|
@@ -810,7 +904,7 @@ const ProviderModelEditor = (0, react.forwardRef)(function ProviderModelEditor$1
|
|
|
810
904
|
})]
|
|
811
905
|
}, model.id)), models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelDefaultsFilterEmpty", { query }) })]
|
|
812
906
|
}),
|
|
813
|
-
provider
|
|
907
|
+
supportsContext(provider) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("modelsContextHint") }),
|
|
814
908
|
catalog.tools.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
815
909
|
style: {
|
|
816
910
|
display: "grid",
|
|
@@ -1054,13 +1148,13 @@ const subscriptionChromeCss = `
|
|
|
1054
1148
|
|
|
1055
1149
|
//#endregion
|
|
1056
1150
|
//#region src/client/ProviderAccountManager.tsx
|
|
1057
|
-
const border = "0.5px solid var(--dsw-alias-border-l2)";
|
|
1058
|
-
const stack = {
|
|
1151
|
+
const border$1 = "0.5px solid var(--dsw-alias-border-l2)";
|
|
1152
|
+
const stack$1 = {
|
|
1059
1153
|
display: "grid",
|
|
1060
1154
|
gap: 12,
|
|
1061
1155
|
minWidth: 0
|
|
1062
1156
|
};
|
|
1063
|
-
const actions = {
|
|
1157
|
+
const actions$1 = {
|
|
1064
1158
|
display: "flex",
|
|
1065
1159
|
flexWrap: "wrap",
|
|
1066
1160
|
gap: 8,
|
|
@@ -1079,6 +1173,8 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1079
1173
|
const [saveError, setSaveError] = (0, react.useState)("");
|
|
1080
1174
|
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
1081
1175
|
const [modelsDirty, setModelsDirty] = (0, react.useState)(false);
|
|
1176
|
+
const [ttlDraft, setTtlDraft] = (0, react.useState)();
|
|
1177
|
+
const [ttlDirty, setTtlDirty] = (0, react.useState)(false);
|
|
1082
1178
|
const alive = (0, react.useRef)(true);
|
|
1083
1179
|
const saveLock = (0, react.useRef)(false);
|
|
1084
1180
|
const editor = (0, react.useRef)(null);
|
|
@@ -1098,7 +1194,11 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1098
1194
|
setLoading(true);
|
|
1099
1195
|
setError("");
|
|
1100
1196
|
callSubscriptionsAuth(rpc, provider === "cursor-subscription" ? "cursorSettings" : "providerSettings", { provider }).then((data) => {
|
|
1101
|
-
if (current)
|
|
1197
|
+
if (current) {
|
|
1198
|
+
setCatalog(data);
|
|
1199
|
+
setTtlDraft(data.settings.promptCacheTtl);
|
|
1200
|
+
setTtlDirty(false);
|
|
1201
|
+
}
|
|
1102
1202
|
}).catch((error$1) => {
|
|
1103
1203
|
if (current) setError(t("accountsLoadFailed", { message: error$1 instanceof Error ? error$1.message : String(error$1) }));
|
|
1104
1204
|
}).finally(() => {
|
|
@@ -1146,13 +1246,13 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1146
1246
|
if (provider === "cursor-subscription") await callSubscriptionsAuth(rpc, "cursorSetSettings", { settings: models.settings ?? latest.settings });
|
|
1147
1247
|
else await callSubscriptionsAuth(rpc, "setProviderSettings", {
|
|
1148
1248
|
provider,
|
|
1149
|
-
settings: mergeAccountChanges(models.settings === void 0 ? latest.settings : mergeLatestAccounts(models.settings, latest.settings), changes)
|
|
1249
|
+
settings: mergeAccountChanges(mergePromptCacheTtl(models.settings === void 0 ? latest.settings : mergeLatestAccounts(models.settings, latest.settings), ttlDirty ? ttlDraft : latest.settings.promptCacheTtl), changes)
|
|
1150
1250
|
});
|
|
1151
1251
|
if (alive.current) onClose();
|
|
1152
1252
|
} catch (error$1) {
|
|
1153
1253
|
if (alive.current) {
|
|
1154
|
-
const message$
|
|
1155
|
-
setSaveError((savedEfforts ? t("modelsPartialSave") + " " : "") + t("accountsSaveFailed", { message: message$
|
|
1254
|
+
const message$3 = error$1 instanceof Error ? error$1.message : String(error$1);
|
|
1255
|
+
setSaveError((savedEfforts ? t("modelsPartialSave") + " " : "") + t("accountsSaveFailed", { message: message$3 }));
|
|
1156
1256
|
}
|
|
1157
1257
|
} finally {
|
|
1158
1258
|
saveLock.current = false;
|
|
@@ -1176,13 +1276,13 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1176
1276
|
},
|
|
1177
1277
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: providerSettingsCss }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1178
1278
|
style: {
|
|
1179
|
-
...stack,
|
|
1279
|
+
...stack$1,
|
|
1180
1280
|
padding: 20
|
|
1181
1281
|
},
|
|
1182
1282
|
children: [
|
|
1183
1283
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1184
1284
|
style: {
|
|
1185
|
-
...actions,
|
|
1285
|
+
...actions$1,
|
|
1186
1286
|
justifyContent: "space-between"
|
|
1187
1287
|
},
|
|
1188
1288
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
@@ -1219,7 +1319,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1219
1319
|
catalog && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1220
1320
|
disabled: saving || loading,
|
|
1221
1321
|
style: {
|
|
1222
|
-
...stack,
|
|
1322
|
+
...stack$1,
|
|
1223
1323
|
border: 0,
|
|
1224
1324
|
margin: 0,
|
|
1225
1325
|
padding: 0
|
|
@@ -1231,7 +1331,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1231
1331
|
}),
|
|
1232
1332
|
provider === "cursor-subscription" && catalog.accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1233
1333
|
style: {
|
|
1234
|
-
border,
|
|
1334
|
+
border: border$1,
|
|
1235
1335
|
borderRadius: "var(--dsw-radius-lg, 12px)",
|
|
1236
1336
|
padding: 14
|
|
1237
1337
|
},
|
|
@@ -1249,8 +1349,8 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1249
1349
|
const models = accountModelRows(account, preferences);
|
|
1250
1350
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1251
1351
|
style: {
|
|
1252
|
-
...stack,
|
|
1253
|
-
border,
|
|
1352
|
+
...stack$1,
|
|
1353
|
+
border: border$1,
|
|
1254
1354
|
borderRadius: "var(--dsw-radius-lg, 12px)",
|
|
1255
1355
|
padding: 14,
|
|
1256
1356
|
margin: 0
|
|
@@ -1270,7 +1370,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1270
1370
|
}),
|
|
1271
1371
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1272
1372
|
style: {
|
|
1273
|
-
...stack,
|
|
1373
|
+
...stack$1,
|
|
1274
1374
|
gap: 6
|
|
1275
1375
|
},
|
|
1276
1376
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1322,12 +1422,13 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1322
1422
|
},
|
|
1323
1423
|
children: [
|
|
1324
1424
|
t("accountsModels"),
|
|
1325
|
-
" ·
|
|
1425
|
+
" ·",
|
|
1426
|
+
" ",
|
|
1326
1427
|
preferences.poolModels === void 0 ? t("accountsModelsAll") : t("accountsModelsCount", { count: preferences.poolModels.length })
|
|
1327
1428
|
]
|
|
1328
1429
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1329
1430
|
style: {
|
|
1330
|
-
...stack,
|
|
1431
|
+
...stack$1,
|
|
1331
1432
|
marginTop: 12
|
|
1332
1433
|
},
|
|
1333
1434
|
children: [
|
|
@@ -1350,7 +1451,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1350
1451
|
t("accountsModelsAutomatic")
|
|
1351
1452
|
] }),
|
|
1352
1453
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1353
|
-
style: actions,
|
|
1454
|
+
style: actions$1,
|
|
1354
1455
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1355
1456
|
type: "button",
|
|
1356
1457
|
onClick: () => edit(account.key, {
|
|
@@ -1369,7 +1470,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1369
1470
|
}),
|
|
1370
1471
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1371
1472
|
style: {
|
|
1372
|
-
...stack,
|
|
1473
|
+
...stack$1,
|
|
1373
1474
|
gap: 8,
|
|
1374
1475
|
maxHeight: 240,
|
|
1375
1476
|
overflowY: "auto"
|
|
@@ -1414,11 +1515,57 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1414
1515
|
disabled: saving,
|
|
1415
1516
|
onDirtyChange: setModelsDirty
|
|
1416
1517
|
}),
|
|
1518
|
+
provider === "claude" && catalog && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1519
|
+
style: {
|
|
1520
|
+
...stack$1,
|
|
1521
|
+
gap: 6,
|
|
1522
|
+
borderTop: border$1,
|
|
1523
|
+
marginTop: 12,
|
|
1524
|
+
paddingTop: 12
|
|
1525
|
+
},
|
|
1526
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1527
|
+
style: {
|
|
1528
|
+
...stack$1,
|
|
1529
|
+
gap: 6
|
|
1530
|
+
},
|
|
1531
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1532
|
+
className: "dsh-subscription-label",
|
|
1533
|
+
children: t("claudeCacheTtl")
|
|
1534
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1535
|
+
"aria-label": t("claudeCacheTtl"),
|
|
1536
|
+
style: { width: 220 },
|
|
1537
|
+
value: ttlDraft ?? "",
|
|
1538
|
+
disabled: saving,
|
|
1539
|
+
onChange: (event) => {
|
|
1540
|
+
const value = event.target.value;
|
|
1541
|
+
setTtlDraft(value === "" ? void 0 : value);
|
|
1542
|
+
setTtlDirty(true);
|
|
1543
|
+
},
|
|
1544
|
+
children: [
|
|
1545
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1546
|
+
value: "",
|
|
1547
|
+
children: t("claudeCacheTtlFollowConfig")
|
|
1548
|
+
}),
|
|
1549
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1550
|
+
value: "5m",
|
|
1551
|
+
children: t("claudeCacheTtl5m")
|
|
1552
|
+
}),
|
|
1553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1554
|
+
value: "1h",
|
|
1555
|
+
children: t("claudeCacheTtl1h")
|
|
1556
|
+
})
|
|
1557
|
+
]
|
|
1558
|
+
})]
|
|
1559
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1560
|
+
className: "dsh-subscription-hint",
|
|
1561
|
+
children: t("claudeCacheTtlHint")
|
|
1562
|
+
})]
|
|
1563
|
+
}),
|
|
1417
1564
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
1418
1565
|
style: {
|
|
1419
|
-
...stack,
|
|
1566
|
+
...stack$1,
|
|
1420
1567
|
gap: 8,
|
|
1421
|
-
borderTop: border,
|
|
1568
|
+
borderTop: border$1,
|
|
1422
1569
|
padding: "14px 0 8px",
|
|
1423
1570
|
position: "sticky",
|
|
1424
1571
|
bottom: 0,
|
|
@@ -1429,7 +1576,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1429
1576
|
children: saveError
|
|
1430
1577
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1431
1578
|
style: {
|
|
1432
|
-
...actions,
|
|
1579
|
+
...actions$1,
|
|
1433
1580
|
justifyContent: "flex-end"
|
|
1434
1581
|
},
|
|
1435
1582
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1440,7 +1587,7 @@ function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
1440
1587
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1441
1588
|
type: "button",
|
|
1442
1589
|
className: "dsh-subscription-primary",
|
|
1443
|
-
disabled: loading || saving || !Object.keys(changes).length && !modelsDirty,
|
|
1590
|
+
disabled: loading || saving || !Object.keys(changes).length && !modelsDirty && !ttlDirty,
|
|
1444
1591
|
onClick: () => {
|
|
1445
1592
|
save();
|
|
1446
1593
|
},
|
|
@@ -1704,6 +1851,19 @@ function usageBarColor(used, elapsed, preset = "standard", fresh = true) {
|
|
|
1704
1851
|
const state = usageColorState(used, elapsed, preset, fresh);
|
|
1705
1852
|
return state === "red" ? "var(--dsw-alias-state-error-primary)" : state === "yellow" ? "var(--dsw-alias-state-warn-label)" : "var(--dsw-alias-state-success-primary)";
|
|
1706
1853
|
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Marker position from the window's display-only assumed start
|
|
1856
|
+
* (`displayStartsAt`): the reading and its age are irrelevant, exactly
|
|
1857
|
+
* like the verified marker. Returns undefined when the window carries no
|
|
1858
|
+
* assumed start or its reset has passed.
|
|
1859
|
+
*/
|
|
1860
|
+
function displayStartPercent(window$1, now = Date.now()) {
|
|
1861
|
+
const end = window$1.resetsAt;
|
|
1862
|
+
const start = window$1.displayStartsAt;
|
|
1863
|
+
if (end === void 0 || !Number.isFinite(end) || end <= now) return void 0;
|
|
1864
|
+
if (start === void 0 || !Number.isFinite(start) || start >= end || start > now) return void 0;
|
|
1865
|
+
return Math.min(100, Math.max(0, (now - start) / (end - start) * 100));
|
|
1866
|
+
}
|
|
1707
1867
|
|
|
1708
1868
|
//#endregion
|
|
1709
1869
|
//#region src/client/usage-color-preferences.ts
|
|
@@ -1765,6 +1925,7 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1765
1925
|
stale
|
|
1766
1926
|
}, current);
|
|
1767
1927
|
const elapsed = elapsedPercent(quota, current);
|
|
1928
|
+
const marker = elapsed ?? displayStartPercent(quota, current);
|
|
1768
1929
|
const pace = fresh ? elapsed : void 0;
|
|
1769
1930
|
const state = usageColorState(quota.usedPercent, pace, preset, fresh);
|
|
1770
1931
|
const lead = pace === void 0 ? 0 : Math.round(used - pace);
|
|
@@ -1774,7 +1935,7 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1774
1935
|
remaining: Math.round(100 - used)
|
|
1775
1936
|
}) : t("usageMeterInvalid"),
|
|
1776
1937
|
!valid ? "" : !fresh ? t("usageMeterStale") : state === "red" ? t("usageMeterNearLimit") : pace === void 0 && preset !== "remaining" ? t("usageMeterUnknownTime") : state === "yellow" ? t("usageMeterAhead") : t("usageMeterNoWarning"),
|
|
1777
|
-
|
|
1938
|
+
marker === void 0 ? "" : t("usageMeterPace", { elapsed: Math.round(marker) }),
|
|
1778
1939
|
pace === void 0 ? "" : lead > 0 ? t("usageMeterPaceAhead", { points: lead }) : lead < 0 ? t("usageMeterPaceBehind", { points: -lead }) : t("usageMeterPaceEven"),
|
|
1779
1940
|
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
1941
|
].filter(Boolean).join(" · ");
|
|
@@ -1797,12 +1958,12 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1797
1958
|
borderRadius: "inherit",
|
|
1798
1959
|
width: `${used}%`,
|
|
1799
1960
|
background: usageBarColor(quota.usedPercent, pace, preset, fresh)
|
|
1800
|
-
} }),
|
|
1961
|
+
} }), marker !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1801
1962
|
"aria-hidden": "true",
|
|
1802
1963
|
"data-usage-time-marker": true,
|
|
1803
1964
|
style: {
|
|
1804
1965
|
position: "absolute",
|
|
1805
|
-
left: `clamp(0px, calc(${
|
|
1966
|
+
left: `clamp(0px, calc(${marker}% - 1px), calc(100% - 2px))`,
|
|
1806
1967
|
top: 0,
|
|
1807
1968
|
bottom: 0,
|
|
1808
1969
|
width: 2,
|
|
@@ -1981,140 +2142,1206 @@ function ExternalUsageCards({ rpc, t }) {
|
|
|
1981
2142
|
style: subscriptionCardStyles.error,
|
|
1982
2143
|
children: t("usageError", { message: errors[id] })
|
|
1983
2144
|
}),
|
|
1984
|
-
snapshot
|
|
1985
|
-
style: subscriptionCardStyles.status,
|
|
1986
|
-
children: t("
|
|
2145
|
+
snapshot !== void 0 && snapshot.supported === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2146
|
+
style: subscriptionCardStyles.status,
|
|
2147
|
+
children: t("usageUnsupported")
|
|
2148
|
+
}),
|
|
2149
|
+
snapshot?.windows?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2150
|
+
style: subscriptionCardStyles.status,
|
|
2151
|
+
children: t("usageEmpty")
|
|
2152
|
+
}),
|
|
2153
|
+
snapshot?.windows?.map((window$1, index) => {
|
|
2154
|
+
const percent = displayUsedPercent(window$1.usedPercent);
|
|
2155
|
+
const label = externalUsageWindowLabel(t, window$1);
|
|
2156
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2157
|
+
style: subscriptionCardStyles.usageRow,
|
|
2158
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2159
|
+
style: subscriptionCardStyles.usageMeta,
|
|
2160
|
+
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() })}`] })]
|
|
2161
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
2162
|
+
window: window$1,
|
|
2163
|
+
t,
|
|
2164
|
+
observedAt: observedAt[id],
|
|
2165
|
+
stale: errors[id] !== void 0 || snapshot?.stale === true
|
|
2166
|
+
})]
|
|
2167
|
+
}, index);
|
|
2168
|
+
})
|
|
2169
|
+
]
|
|
2170
|
+
})]
|
|
2171
|
+
}),
|
|
2172
|
+
!configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2173
|
+
style: subscriptionCardStyles.actions,
|
|
2174
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2175
|
+
type: "button",
|
|
2176
|
+
style: subscriptionCardStyles.button,
|
|
2177
|
+
onClick: () => {
|
|
2178
|
+
refreshStatus();
|
|
2179
|
+
},
|
|
2180
|
+
children: t("usageRefresh")
|
|
2181
|
+
})
|
|
2182
|
+
})
|
|
2183
|
+
]
|
|
2184
|
+
}, id);
|
|
2185
|
+
}) });
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
//#endregion
|
|
2189
|
+
//#region src/client/CursorCard.tsx
|
|
2190
|
+
function message$2(error) {
|
|
2191
|
+
return error instanceof Error ? error.message : String(error);
|
|
2192
|
+
}
|
|
2193
|
+
/** Cursor's single connected account, rendered with the same card and Manage dialog as the other providers. */
|
|
2194
|
+
function CursorCard({ rpc, t }) {
|
|
2195
|
+
const [status, setStatus] = (0, react.useState)();
|
|
2196
|
+
const [usage, setUsage] = (0, react.useState)();
|
|
2197
|
+
const [observedAt, setObservedAt] = (0, react.useState)();
|
|
2198
|
+
const [usageStale, setUsageStale] = (0, react.useState)(false);
|
|
2199
|
+
const [authorizeUrl, setAuthorizeUrl] = (0, react.useState)();
|
|
2200
|
+
const [error, setError] = (0, react.useState)();
|
|
2201
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
2202
|
+
const [manage, setManage] = (0, react.useState)(false);
|
|
2203
|
+
const authenticated = (0, react.useRef)();
|
|
2204
|
+
const refreshStatus = (0, react.useCallback)(async () => {
|
|
2205
|
+
try {
|
|
2206
|
+
const value = await callSubscriptionsAuth(rpc, "cursorStatus", {});
|
|
2207
|
+
setStatus(value);
|
|
2208
|
+
if (authenticated.current !== value.authenticated) {
|
|
2209
|
+
authenticated.current = value.authenticated;
|
|
2210
|
+
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
2211
|
+
}
|
|
2212
|
+
if (value.authenticated) setAuthorizeUrl(void 0);
|
|
2213
|
+
if (value.error) setError(value.error);
|
|
2214
|
+
} catch (failure) {
|
|
2215
|
+
setError(message$2(failure));
|
|
2216
|
+
}
|
|
2217
|
+
}, [rpc]);
|
|
2218
|
+
const refreshUsage = (0, react.useCallback)(async () => {
|
|
2219
|
+
setLoading(true);
|
|
2220
|
+
try {
|
|
2221
|
+
const value = await callSubscriptionsAuth(rpc, "cursorUsage", {});
|
|
2222
|
+
setUsage(value);
|
|
2223
|
+
setObservedAt(value.observedAt ?? Date.now());
|
|
2224
|
+
setUsageStale(value.stale === true);
|
|
2225
|
+
setError(void 0);
|
|
2226
|
+
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
2227
|
+
} catch (failure) {
|
|
2228
|
+
setError(message$2(failure));
|
|
2229
|
+
setUsageStale(true);
|
|
2230
|
+
} finally {
|
|
2231
|
+
setLoading(false);
|
|
2232
|
+
}
|
|
2233
|
+
}, [rpc]);
|
|
2234
|
+
(0, react.useEffect)(() => {
|
|
2235
|
+
refreshStatus();
|
|
2236
|
+
}, [refreshStatus]);
|
|
2237
|
+
(0, react.useEffect)(() => {
|
|
2238
|
+
if (status?.busy !== true) return;
|
|
2239
|
+
const timer = setInterval(() => {
|
|
2240
|
+
refreshStatus();
|
|
2241
|
+
}, 2e3);
|
|
2242
|
+
return () => clearInterval(timer);
|
|
2243
|
+
}, [status?.busy, refreshStatus]);
|
|
2244
|
+
(0, react.useEffect)(() => {
|
|
2245
|
+
if (status?.authenticated === true) refreshUsage();
|
|
2246
|
+
}, [status?.authenticated, refreshUsage]);
|
|
2247
|
+
const login = async () => {
|
|
2248
|
+
setError(void 0);
|
|
2249
|
+
try {
|
|
2250
|
+
const value = await callSubscriptionsAuth(rpc, "cursorLogin", {});
|
|
2251
|
+
setAuthorizeUrl(value.authorizeUrl);
|
|
2252
|
+
window.open(value.authorizeUrl, "_blank", "noopener");
|
|
2253
|
+
await refreshStatus();
|
|
2254
|
+
} catch (failure) {
|
|
2255
|
+
setError(message$2(failure));
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
const cancel = async () => {
|
|
2259
|
+
try {
|
|
2260
|
+
await callSubscriptionsAuth(rpc, "cursorCancel", {});
|
|
2261
|
+
setAuthorizeUrl(void 0);
|
|
2262
|
+
await refreshStatus();
|
|
2263
|
+
} catch (failure) {
|
|
2264
|
+
setError(message$2(failure));
|
|
2265
|
+
}
|
|
2266
|
+
};
|
|
2267
|
+
const logout = async () => {
|
|
2268
|
+
if (!window.confirm(t("logoutAccountConfirm", {
|
|
2269
|
+
account: "Cursor",
|
|
2270
|
+
provider: "Cursor"
|
|
2271
|
+
}))) return;
|
|
2272
|
+
try {
|
|
2273
|
+
await callSubscriptionsAuth(rpc, "cursorLogout", {});
|
|
2274
|
+
setUsage(void 0);
|
|
2275
|
+
setObservedAt(void 0);
|
|
2276
|
+
setUsageStale(false);
|
|
2277
|
+
await refreshStatus();
|
|
2278
|
+
} catch (failure) {
|
|
2279
|
+
setError(message$2(failure));
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
const connected = status?.authenticated === true;
|
|
2283
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2284
|
+
style: subscriptionCardStyles.card,
|
|
2285
|
+
children: [
|
|
2286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
|
+
style: subscriptionCardStyles.header,
|
|
2288
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
2289
|
+
...subscriptionCardStyles.dot,
|
|
2290
|
+
background: status?.busy ? "var(--dsw-alias-state-warn-label)" : connected ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-label-dimmed)"
|
|
2291
|
+
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2292
|
+
style: subscriptionCardStyles.name,
|
|
2293
|
+
children: t("cursorTitle")
|
|
2294
|
+
})]
|
|
2295
|
+
}),
|
|
2296
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2297
|
+
style: subscriptionCardStyles.status,
|
|
2298
|
+
children: status === void 0 ? t("checking") : status.busy ? t("loginInProgress") : connected ? t("loggedInCount", { count: 1 }) : t("notLoggedIn")
|
|
2299
|
+
}),
|
|
2300
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2301
|
+
style: subscriptionCardStyles.error,
|
|
2302
|
+
children: error
|
|
2303
|
+
}),
|
|
2304
|
+
connected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2305
|
+
style: subscriptionCardStyles.account,
|
|
2306
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2307
|
+
style: subscriptionCardStyles.accountHeader,
|
|
2308
|
+
children: [
|
|
2309
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2310
|
+
style: subscriptionCardStyles.defaultStar,
|
|
2311
|
+
title: t("defaultBadge"),
|
|
2312
|
+
children: "★"
|
|
2313
|
+
}),
|
|
2314
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2315
|
+
style: subscriptionCardStyles.accountName,
|
|
2316
|
+
children: "Cursor"
|
|
2317
|
+
}),
|
|
2318
|
+
status.expiresAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2319
|
+
style: subscriptionCardStyles.status,
|
|
2320
|
+
children: t("accountExpires", { date: new Date(status.expiresAt).toLocaleString() })
|
|
2321
|
+
}),
|
|
2322
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2323
|
+
type: "button",
|
|
2324
|
+
style: {
|
|
2325
|
+
...subscriptionCardStyles.button,
|
|
2326
|
+
marginLeft: "auto",
|
|
2327
|
+
flexShrink: 0
|
|
2328
|
+
},
|
|
2329
|
+
onClick: () => {
|
|
2330
|
+
logout();
|
|
2331
|
+
},
|
|
2332
|
+
children: t("logout")
|
|
2333
|
+
})
|
|
2334
|
+
]
|
|
2335
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2336
|
+
style: subscriptionCardStyles.usage,
|
|
2337
|
+
children: [
|
|
2338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2339
|
+
style: subscriptionCardStyles.usageHeader,
|
|
2340
|
+
children: [
|
|
2341
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2342
|
+
style: subscriptionCardStyles.usageTitle,
|
|
2343
|
+
children: t("usageTitle")
|
|
2344
|
+
}),
|
|
2345
|
+
usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2346
|
+
style: subscriptionCardStyles.status,
|
|
2347
|
+
children: t("usagePlan", { plan: usage.plan })
|
|
2348
|
+
}),
|
|
2349
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2350
|
+
type: "button",
|
|
2351
|
+
style: subscriptionCardStyles.usageRefresh,
|
|
2352
|
+
disabled: loading,
|
|
2353
|
+
onClick: () => {
|
|
2354
|
+
refreshUsage();
|
|
2355
|
+
},
|
|
2356
|
+
children: t("usageRefresh")
|
|
2357
|
+
})
|
|
2358
|
+
]
|
|
2359
|
+
}),
|
|
2360
|
+
loading && usage === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2361
|
+
style: subscriptionCardStyles.status,
|
|
2362
|
+
children: t("usageLoading")
|
|
2363
|
+
}),
|
|
2364
|
+
usage?.windows?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2365
|
+
style: subscriptionCardStyles.status,
|
|
2366
|
+
children: t("usageEmpty")
|
|
2367
|
+
}),
|
|
2368
|
+
usage?.windows?.map((window$1, index) => {
|
|
2369
|
+
const percent = displayUsedPercent(window$1.usedPercent);
|
|
2370
|
+
const label = window$1.scope === "Included" ? t("cursorIncluded") : window$1.scope === "Cursor Models" ? t("cursorModels") : window$1.scope === "Other Models" ? t("cursorOtherModels") : window$1.scope === "Included requests" ? t("cursorIncludedRequests") : window$1.scope ?? t("usageWindow");
|
|
2371
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2372
|
+
style: subscriptionCardStyles.usageRow,
|
|
2373
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2374
|
+
style: subscriptionCardStyles.usageMeta,
|
|
2375
|
+
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() })}`] })]
|
|
2376
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
2377
|
+
window: window$1,
|
|
2378
|
+
t,
|
|
2379
|
+
observedAt,
|
|
2380
|
+
stale: usageStale
|
|
2381
|
+
})]
|
|
2382
|
+
}, index);
|
|
2383
|
+
})
|
|
2384
|
+
]
|
|
2385
|
+
})]
|
|
2386
|
+
}),
|
|
2387
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2388
|
+
style: subscriptionCardStyles.actions,
|
|
2389
|
+
children: [
|
|
2390
|
+
status?.authenticated === false && !status.busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2391
|
+
type: "button",
|
|
2392
|
+
style: subscriptionCardStyles.button,
|
|
2393
|
+
onClick: () => {
|
|
2394
|
+
login();
|
|
2395
|
+
},
|
|
2396
|
+
children: t("login")
|
|
2397
|
+
}),
|
|
2398
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2399
|
+
type: "button",
|
|
2400
|
+
style: subscriptionCardStyles.button,
|
|
2401
|
+
"aria-haspopup": "dialog",
|
|
2402
|
+
onClick: () => setManage(true),
|
|
2403
|
+
children: t("accountsManage")
|
|
2404
|
+
}),
|
|
2405
|
+
status?.busy === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2406
|
+
type: "button",
|
|
2407
|
+
style: subscriptionCardStyles.button,
|
|
2408
|
+
onClick: () => {
|
|
2409
|
+
cancel();
|
|
2410
|
+
},
|
|
2411
|
+
children: t("cancel")
|
|
2412
|
+
})
|
|
2413
|
+
]
|
|
2414
|
+
}),
|
|
2415
|
+
status?.busy === true && authorizeUrl !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2416
|
+
href: authorizeUrl,
|
|
2417
|
+
target: "_blank",
|
|
2418
|
+
rel: "noopener noreferrer",
|
|
2419
|
+
children: t("cursorOpenLogin")
|
|
2420
|
+
}),
|
|
2421
|
+
manage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderAccountManager, {
|
|
2422
|
+
provider: "cursor-subscription",
|
|
2423
|
+
name: "Cursor",
|
|
2424
|
+
rpc,
|
|
2425
|
+
t,
|
|
2426
|
+
onClose: () => setManage(false)
|
|
2427
|
+
})
|
|
2428
|
+
]
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
//#endregion
|
|
2433
|
+
//#region src/client/ollama-draft.ts
|
|
2434
|
+
/**
|
|
2435
|
+
* Append the checked candidates to the draft's model list. Existing rows are
|
|
2436
|
+
* never replaced or reordered, ids already present are skipped, and adopted
|
|
2437
|
+
* rows keep the candidate order. Optional fields are set only when the
|
|
2438
|
+
* candidate advertises a value, because `exactOptionalPropertyTypes` (and the
|
|
2439
|
+
* store) must not see an explicit `undefined`.
|
|
2440
|
+
* @param current - the draft list before adoption.
|
|
2441
|
+
* @param candidates - the cloud list as fetched, in display order.
|
|
2442
|
+
* @param selectedIds - ids the user checked.
|
|
2443
|
+
* @returns a new list; the input array is not mutated.
|
|
2444
|
+
*/
|
|
2445
|
+
function adoptCandidates(current, candidates, selectedIds) {
|
|
2446
|
+
const models = [...current ?? []];
|
|
2447
|
+
const present = new Set(models.map((model) => model.id));
|
|
2448
|
+
const selected = new Set(selectedIds);
|
|
2449
|
+
for (const candidate of candidates) {
|
|
2450
|
+
if (!selected.has(candidate.id) || present.has(candidate.id)) continue;
|
|
2451
|
+
present.add(candidate.id);
|
|
2452
|
+
models.push({
|
|
2453
|
+
id: candidate.id,
|
|
2454
|
+
name: candidate.name,
|
|
2455
|
+
...candidate.contextWindow === void 0 ? {} : { contextWindow: candidate.contextWindow },
|
|
2456
|
+
...candidate.vision ? { vision: true } : {},
|
|
2457
|
+
...candidate.thinking ? { thinking: true } : {},
|
|
2458
|
+
...candidate.efforts.length ? { efforts: [...candidate.efforts] } : {}
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
return models;
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Swap one model with its neighbour. An id that is absent or a move past
|
|
2465
|
+
* either end returns the same order.
|
|
2466
|
+
* @param models - the current list.
|
|
2467
|
+
* @param id - the row to move.
|
|
2468
|
+
* @param delta - `-1` moves up, `1` moves down.
|
|
2469
|
+
* @returns a new list; the input array is not mutated.
|
|
2470
|
+
*/
|
|
2471
|
+
function moveModel(models, id, delta) {
|
|
2472
|
+
const next = [...models];
|
|
2473
|
+
const index = next.findIndex((model) => model.id === id);
|
|
2474
|
+
const target = index + delta;
|
|
2475
|
+
if (index === -1 || target < 0 || target >= next.length) return next;
|
|
2476
|
+
const moved = next[index];
|
|
2477
|
+
next[index] = next[target];
|
|
2478
|
+
next[target] = moved;
|
|
2479
|
+
return next;
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Drop one model from the list.
|
|
2483
|
+
* @param models - the current list.
|
|
2484
|
+
* @param id - the row to drop; an unknown id leaves the list unchanged.
|
|
2485
|
+
* @returns a new list; the input array is not mutated.
|
|
2486
|
+
*/
|
|
2487
|
+
function removeModel(models, id) {
|
|
2488
|
+
return models.filter((model) => model.id !== id);
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Short context-window label for a model row.
|
|
2492
|
+
* @param contextWindow - the advertised window in tokens.
|
|
2493
|
+
* @returns `'1M'`, `'262K'`, the bare number below 1000, or `undefined` when unknown.
|
|
2494
|
+
*/
|
|
2495
|
+
function contextLabel(contextWindow) {
|
|
2496
|
+
if (contextWindow === void 0) return void 0;
|
|
2497
|
+
if (contextWindow < 1e3) return String(contextWindow);
|
|
2498
|
+
if (contextWindow >= 1e6) {
|
|
2499
|
+
const millions = contextWindow / 1e6;
|
|
2500
|
+
return `${Number.isInteger(millions) ? millions : Number(millions.toFixed(1))}M`;
|
|
2501
|
+
}
|
|
2502
|
+
return `${Math.round(contextWindow / 1e3)}K`;
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Rename one model row. An empty display name deletes the field so the
|
|
2506
|
+
* validator keeps accepting the draft; a blank id leaves the list unchanged.
|
|
2507
|
+
* @param models - the current list.
|
|
2508
|
+
* @param id - the row to rename; an unknown id leaves the list unchanged.
|
|
2509
|
+
* @param name - the new display name; whitespace trims before storing.
|
|
2510
|
+
* @returns a new list; the input array is not mutated.
|
|
2511
|
+
*/
|
|
2512
|
+
function renameModel(models, id, name) {
|
|
2513
|
+
return models.map((model) => {
|
|
2514
|
+
if (model.id !== id) return model;
|
|
2515
|
+
const next = { ...model };
|
|
2516
|
+
if (name === "") delete next.name;
|
|
2517
|
+
else next.name = name;
|
|
2518
|
+
return next;
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Set or clear one model row's default reasoning effort. An empty choice
|
|
2523
|
+
* deletes the field so the resolver falls back to the vendor or family
|
|
2524
|
+
* default.
|
|
2525
|
+
* @param models - the current list.
|
|
2526
|
+
* @param id - the row to edit; an unknown id leaves the list unchanged.
|
|
2527
|
+
* @param effort - the chosen level, or `''` to clear.
|
|
2528
|
+
* @returns a new list; the input array is not mutated.
|
|
2529
|
+
*/
|
|
2530
|
+
function setModelEffort(models, id, effort) {
|
|
2531
|
+
return models.map((model) => {
|
|
2532
|
+
if (model.id !== id) return model;
|
|
2533
|
+
if (effort === "") {
|
|
2534
|
+
const next = { ...model };
|
|
2535
|
+
delete next.defaultEffort;
|
|
2536
|
+
return next;
|
|
2537
|
+
}
|
|
2538
|
+
return {
|
|
2539
|
+
...model,
|
|
2540
|
+
defaultEffort: effort
|
|
2541
|
+
};
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Set or clear one model row's local context-window override. `undefined`
|
|
2546
|
+
* deletes the field so the model follows the endpoint again; the runtime
|
|
2547
|
+
* caps a stored value at the advertised window.
|
|
2548
|
+
* @param models - the current list.
|
|
2549
|
+
* @param id - the row to edit; an unknown id leaves the list unchanged.
|
|
2550
|
+
* @param tokens - a positive safe integer, or `undefined` to clear.
|
|
2551
|
+
* @returns a new list; the input array is not mutated.
|
|
2552
|
+
*/
|
|
2553
|
+
function setModelContext(models, id, tokens) {
|
|
2554
|
+
return models.map((model) => {
|
|
2555
|
+
if (model.id !== id) return model;
|
|
2556
|
+
const next = { ...model };
|
|
2557
|
+
if (tokens === void 0) delete next.contextOverride;
|
|
2558
|
+
else next.contextOverride = tokens;
|
|
2559
|
+
return next;
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Parse the context input's text. Only a positive safe integer is a value;
|
|
2564
|
+
* an empty string clears the override.
|
|
2565
|
+
* @param text - what the user typed.
|
|
2566
|
+
* @returns the number, `undefined` for blank, or `null` for text to reject.
|
|
2567
|
+
*/
|
|
2568
|
+
function parseContextInput(text) {
|
|
2569
|
+
const trimmed = text.trim();
|
|
2570
|
+
if (trimmed === "") return void 0;
|
|
2571
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
2572
|
+
const value = Number(trimmed);
|
|
2573
|
+
return Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Untick one model: drop it from the list and keep its full row (display
|
|
2577
|
+
* name, default effort, context, ...) in the stash so ticking it again is
|
|
2578
|
+
* lossless, even when the cloud listing no longer carries the id.
|
|
2579
|
+
* @param models - the current list.
|
|
2580
|
+
* @param stash - the stash before this untick.
|
|
2581
|
+
* @param id - the row to untick; an absent id changes nothing.
|
|
2582
|
+
* @returns the new list and stash; neither input is mutated.
|
|
2583
|
+
*/
|
|
2584
|
+
function untickModel(models, stash, id) {
|
|
2585
|
+
const row = models.find((model) => model.id === id);
|
|
2586
|
+
if (row === void 0) return {
|
|
2587
|
+
models: [...models],
|
|
2588
|
+
stash
|
|
2589
|
+
};
|
|
2590
|
+
return {
|
|
2591
|
+
models: removeModel(models, id),
|
|
2592
|
+
stash: new Map(stash).set(id, row)
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Tick one model: restore its stashed row when this dialog unticked it,
|
|
2597
|
+
* otherwise adopt it from the fetched candidates (which never copies the
|
|
2598
|
+
* vendor default effort). A model already in the list, or unknown to both
|
|
2599
|
+
* sources, leaves the list unchanged.
|
|
2600
|
+
* @param models - the current list.
|
|
2601
|
+
* @param stash - rows unticked earlier in this dialog.
|
|
2602
|
+
* @param candidates - the cloud list as fetched.
|
|
2603
|
+
* @param id - the row to tick.
|
|
2604
|
+
* @returns a new list and the stash without the restored row.
|
|
2605
|
+
*/
|
|
2606
|
+
function tickModel(models, stash, candidates, id) {
|
|
2607
|
+
if (models.some((model) => model.id === id)) return {
|
|
2608
|
+
models: [...models],
|
|
2609
|
+
stash
|
|
2610
|
+
};
|
|
2611
|
+
const stashed = stash.get(id);
|
|
2612
|
+
if (stashed === void 0) return {
|
|
2613
|
+
models: adoptCandidates(models, candidates, [id]),
|
|
2614
|
+
stash
|
|
2615
|
+
};
|
|
2616
|
+
const next = new Map(stash);
|
|
2617
|
+
next.delete(id);
|
|
2618
|
+
return {
|
|
2619
|
+
models: [...models, stashed],
|
|
2620
|
+
stash: next
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Tick every listed id in order. Rows already in the list stay where they
|
|
2625
|
+
* are; restored and adopted rows append in the given order.
|
|
2626
|
+
* @param models - the current list.
|
|
2627
|
+
* @param stash - rows unticked earlier in this dialog.
|
|
2628
|
+
* @param candidates - the cloud list as fetched.
|
|
2629
|
+
* @param ids - the ids to tick, typically the filtered view.
|
|
2630
|
+
* @returns the new list and stash; neither input is mutated.
|
|
2631
|
+
*/
|
|
2632
|
+
function tickModels(models, stash, candidates, ids) {
|
|
2633
|
+
let state = {
|
|
2634
|
+
models: [...models],
|
|
2635
|
+
stash
|
|
2636
|
+
};
|
|
2637
|
+
for (const id of ids) state = tickModel(state.models, state.stash, candidates, id);
|
|
2638
|
+
return state;
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Untick every listed id, stashing each removed row. Ids outside the list,
|
|
2642
|
+
* such as filtered-out rows, are untouched.
|
|
2643
|
+
* @param models - the current list.
|
|
2644
|
+
* @param stash - the stash before this untick.
|
|
2645
|
+
* @param ids - the ids to untick, typically the filtered view.
|
|
2646
|
+
* @returns the new list and stash; neither input is mutated.
|
|
2647
|
+
*/
|
|
2648
|
+
function untickModels(models, stash, ids) {
|
|
2649
|
+
let state = {
|
|
2650
|
+
models: [...models],
|
|
2651
|
+
stash
|
|
2652
|
+
};
|
|
2653
|
+
for (const id of ids) state = untickModel(state.models, state.stash, id);
|
|
2654
|
+
return state;
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* The effort choices for one ticked row. The row's own `efforts` decide the
|
|
2658
|
+
* ids because the store rejects a default outside them, so the selector must
|
|
2659
|
+
* never offer anything else. A snapshot of the endpoint (`labels`) may only
|
|
2660
|
+
* supply friendlier names for the same ids. A saved default the list no
|
|
2661
|
+
* longer contains is kept visible and marked unavailable instead of
|
|
2662
|
+
* silently reading as "follow the provider".
|
|
2663
|
+
* @param row - the draft row.
|
|
2664
|
+
* @param fetched - efforts from this dialog's latest fetch, when it returned the row.
|
|
2665
|
+
* @param labels - the opened dialog's snapshot of named efforts for the id.
|
|
2666
|
+
* @returns the options in picker order.
|
|
2667
|
+
*/
|
|
2668
|
+
function effortOptions(row, fetched, labels) {
|
|
2669
|
+
const ids = row.efforts ?? fetched ?? labels?.map((entry) => entry.id) ?? [];
|
|
2670
|
+
const options = ids.map((id) => ({
|
|
2671
|
+
id,
|
|
2672
|
+
name: labels?.find((entry) => entry.id === id)?.name ?? id
|
|
2673
|
+
}));
|
|
2674
|
+
if (row.defaultEffort !== void 0 && !ids.includes(row.defaultEffort)) options.push({
|
|
2675
|
+
id: row.defaultEffort,
|
|
2676
|
+
name: row.defaultEffort,
|
|
2677
|
+
unavailable: true
|
|
2678
|
+
});
|
|
2679
|
+
return options;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
//#endregion
|
|
2683
|
+
//#region src/client/OllamaSettingsManager.tsx
|
|
2684
|
+
const border = "0.5px solid var(--dsw-alias-border-l2)";
|
|
2685
|
+
const stack = {
|
|
2686
|
+
display: "grid",
|
|
2687
|
+
gap: 12,
|
|
2688
|
+
minWidth: 0
|
|
2689
|
+
};
|
|
2690
|
+
const actions = {
|
|
2691
|
+
display: "flex",
|
|
2692
|
+
flexWrap: "wrap",
|
|
2693
|
+
gap: 8,
|
|
2694
|
+
alignItems: "center"
|
|
2695
|
+
};
|
|
2696
|
+
function message$1(error) {
|
|
2697
|
+
return error instanceof Error ? error.message : String(error);
|
|
2698
|
+
}
|
|
2699
|
+
/** `vision · thinking · context 262K`, dropping whatever the model does not advertise. */
|
|
2700
|
+
function capabilities(t, model) {
|
|
2701
|
+
const context = contextLabel(model.contextWindow);
|
|
2702
|
+
return [
|
|
2703
|
+
model.vision === true ? t("ollamaCapVision") : false,
|
|
2704
|
+
model.thinking === true ? t("ollamaCapThinking") : false,
|
|
2705
|
+
context === void 0 ? false : t("ollamaContext", { tokens: context })
|
|
2706
|
+
].filter(Boolean).join(" · ");
|
|
2707
|
+
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Ollama Cloud settings dialog. It owns one draft (model list, web toggle,
|
|
2710
|
+
* base URL and credential reference) that Save writes in a single
|
|
2711
|
+
* `ollamaSetSettings` call, so a failure keeps every edit. Fetching the cloud
|
|
2712
|
+
* catalog is a read-only side trip, and a key typed for the fetch never
|
|
2713
|
+
* leaves this component. The model list follows the shared model editor: a
|
|
2714
|
+
* filter, select all/none over the filtered rows, and one checkbox per
|
|
2715
|
+
* model, where ticking edits the draft and enables Save. Ollama adds what the
|
|
2716
|
+
* shared editor lacks: a display name, ordering, and a local context window.
|
|
2717
|
+
* Unticking keeps the row's full settings in a dialog-local stash, so ticking
|
|
2718
|
+
* it again restores them even when the cloud listing no longer returns it.
|
|
2719
|
+
*/
|
|
2720
|
+
function OllamaSettingsManager({ rpc, t, onClose, onSaved }) {
|
|
2721
|
+
const dialog = (0, react.useRef)(null);
|
|
2722
|
+
const title = (0, react.useId)();
|
|
2723
|
+
const description = (0, react.useId)();
|
|
2724
|
+
const [view, setView] = (0, react.useState)();
|
|
2725
|
+
const [draft, setDraft] = (0, react.useState)({});
|
|
2726
|
+
const [dirty, setDirty] = (0, react.useState)(false);
|
|
2727
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
2728
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
2729
|
+
const [error, setError] = (0, react.useState)("");
|
|
2730
|
+
const [saveError, setSaveError] = (0, react.useState)("");
|
|
2731
|
+
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
2732
|
+
const [apiKey, setApiKey] = (0, react.useState)("");
|
|
2733
|
+
const [candidates, setCandidates] = (0, react.useState)();
|
|
2734
|
+
const [fetching, setFetching] = (0, react.useState)(false);
|
|
2735
|
+
const [fetchError, setFetchError] = (0, react.useState)("");
|
|
2736
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
2737
|
+
const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
2738
|
+
const [stash, setStash] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
2739
|
+
const [contextText, setContextText] = (0, react.useState)({});
|
|
2740
|
+
const alive = (0, react.useRef)(true);
|
|
2741
|
+
const saveLock = (0, react.useRef)(false);
|
|
2742
|
+
const fetchLock = (0, react.useRef)(false);
|
|
2743
|
+
(0, react.useEffect)(() => {
|
|
2744
|
+
alive.current = true;
|
|
2745
|
+
const element = dialog.current;
|
|
2746
|
+
const previous = document.activeElement;
|
|
2747
|
+
element.showModal();
|
|
2748
|
+
return () => {
|
|
2749
|
+
alive.current = false;
|
|
2750
|
+
element.close();
|
|
2751
|
+
if (previous instanceof HTMLElement && previous.isConnected) previous.focus();
|
|
2752
|
+
};
|
|
2753
|
+
}, []);
|
|
2754
|
+
(0, react.useEffect)(() => {
|
|
2755
|
+
let current = true;
|
|
2756
|
+
setLoading(true);
|
|
2757
|
+
setError("");
|
|
2758
|
+
callSubscriptionsAuth(rpc, "ollamaSettings", {}).then((data) => {
|
|
2759
|
+
if (!current) return;
|
|
2760
|
+
setView(data);
|
|
2761
|
+
setDraft({ ...data.settings });
|
|
2762
|
+
setStash(/* @__PURE__ */ new Map());
|
|
2763
|
+
setContextText({});
|
|
2764
|
+
setDirty(false);
|
|
2765
|
+
}).catch((failure) => {
|
|
2766
|
+
if (current) setError(t("ollamaModelsLoadFailed", { message: message$1(failure) }));
|
|
2767
|
+
}).finally(() => {
|
|
2768
|
+
if (current) setLoading(false);
|
|
2769
|
+
});
|
|
2770
|
+
return () => {
|
|
2771
|
+
current = false;
|
|
2772
|
+
};
|
|
2773
|
+
}, [rpc, attempt]);
|
|
2774
|
+
function edit(next) {
|
|
2775
|
+
setDraft(next);
|
|
2776
|
+
setDirty(true);
|
|
2777
|
+
}
|
|
2778
|
+
/** Fetch the cloud catalog; a failure keeps the previous list and the draft. */
|
|
2779
|
+
async function fetchCandidates() {
|
|
2780
|
+
if (fetchLock.current) return;
|
|
2781
|
+
fetchLock.current = true;
|
|
2782
|
+
setFetching(true);
|
|
2783
|
+
setFetchError("");
|
|
2784
|
+
try {
|
|
2785
|
+
const value = await callSubscriptionsAuth(rpc, "ollamaModels", {
|
|
2786
|
+
...apiKey ? { apiKey } : {},
|
|
2787
|
+
...draft.baseURL ? { baseURL: draft.baseURL } : {}
|
|
2788
|
+
});
|
|
2789
|
+
if (!alive.current) return;
|
|
2790
|
+
setCandidates(value.models);
|
|
2791
|
+
} catch (failure) {
|
|
2792
|
+
if (alive.current) setFetchError(t("ollamaFetchFailed", { message: message$1(failure) }));
|
|
2793
|
+
} finally {
|
|
2794
|
+
fetchLock.current = false;
|
|
2795
|
+
if (alive.current) setFetching(false);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
/** Save the whole draft; a failure keeps the dialog open with every edit. */
|
|
2799
|
+
async function save() {
|
|
2800
|
+
if (saveLock.current) return;
|
|
2801
|
+
saveLock.current = true;
|
|
2802
|
+
setSaving(true);
|
|
2803
|
+
setSaveError("");
|
|
2804
|
+
try {
|
|
2805
|
+
await callSubscriptionsAuth(rpc, "ollamaSetSettings", { settings: draft });
|
|
2806
|
+
if (alive.current) {
|
|
2807
|
+
onSaved();
|
|
2808
|
+
onClose();
|
|
2809
|
+
}
|
|
2810
|
+
} catch (failure) {
|
|
2811
|
+
if (alive.current) setSaveError(t("ollamaModelsSaveFailed", { message: message$1(failure) }));
|
|
2812
|
+
} finally {
|
|
2813
|
+
saveLock.current = false;
|
|
2814
|
+
if (alive.current) setSaving(false);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
/** Drop every stored override, then reload the draft from the deployment values. */
|
|
2818
|
+
async function resetDefaults() {
|
|
2819
|
+
if (saveLock.current) return;
|
|
2820
|
+
if (!window.confirm(t("ollamaResetConfirm"))) return;
|
|
2821
|
+
saveLock.current = true;
|
|
2822
|
+
setSaving(true);
|
|
2823
|
+
setSaveError("");
|
|
2824
|
+
try {
|
|
2825
|
+
await callSubscriptionsAuth(rpc, "ollamaSetSettings", { settings: {} });
|
|
2826
|
+
if (alive.current) {
|
|
2827
|
+
setAttempt((value) => value + 1);
|
|
2828
|
+
onSaved();
|
|
2829
|
+
}
|
|
2830
|
+
} catch (failure) {
|
|
2831
|
+
if (alive.current) setSaveError(t("ollamaModelsSaveFailed", { message: message$1(failure) }));
|
|
2832
|
+
} finally {
|
|
2833
|
+
saveLock.current = false;
|
|
2834
|
+
if (alive.current) setSaving(false);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
function editOverride(field, value) {
|
|
2838
|
+
const next = { ...draft };
|
|
2839
|
+
if (value.trim()) next[field] = value;
|
|
2840
|
+
else delete next[field];
|
|
2841
|
+
edit(next);
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* One list for both sources, like the shared editor: the ticked models in
|
|
2845
|
+
* draft order first (so Move up/down is visible), then the unticked ones
|
|
2846
|
+
* (stashed in this dialog, or fetched and not yet adopted). A kept model
|
|
2847
|
+
* the fetch did not return stays listed, ticked or not.
|
|
2848
|
+
*/
|
|
2849
|
+
const fetchedById = new Map((candidates ?? []).map((candidate) => [candidate.id, candidate]));
|
|
2850
|
+
const present = new Set((draft.models ?? []).map((model) => model.id));
|
|
2851
|
+
const asRow = (model, ticked) => {
|
|
2852
|
+
const fetched = fetchedById.get(model.id);
|
|
2853
|
+
const contextWindow = fetched?.contextWindow ?? model.contextWindow;
|
|
2854
|
+
return {
|
|
2855
|
+
id: model.id,
|
|
2856
|
+
name: model.name ?? fetched?.name ?? model.id,
|
|
2857
|
+
ticked,
|
|
2858
|
+
vision: model.vision === true || fetched?.vision === true,
|
|
2859
|
+
thinking: model.thinking === true || fetched?.thinking === true,
|
|
2860
|
+
...contextWindow === void 0 ? {} : { contextWindow },
|
|
2861
|
+
efforts: model.efforts ?? fetched?.efforts ?? []
|
|
2862
|
+
};
|
|
2863
|
+
};
|
|
2864
|
+
const shown = [
|
|
2865
|
+
...(draft.models ?? []).map((model) => asRow(model, true)),
|
|
2866
|
+
...[...stash.values()].filter((model) => !present.has(model.id)).map((model) => asRow(model, false)),
|
|
2867
|
+
...(candidates ?? []).filter((candidate) => !present.has(candidate.id) && !stash.has(candidate.id)).map((candidate) => asRow({ id: candidate.id }, false))
|
|
2868
|
+
].filter((row) => `${row.name} ${row.id}`.toLowerCase().includes(query.trim().toLowerCase()));
|
|
2869
|
+
/** Rename and default-effort edits ride the tested draft helpers. */
|
|
2870
|
+
function rename(id, name) {
|
|
2871
|
+
edit({
|
|
2872
|
+
...draft,
|
|
2873
|
+
models: renameModel(draft.models ?? [], id, name)
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
function setEffort(id, effort) {
|
|
2877
|
+
edit({
|
|
2878
|
+
...draft,
|
|
2879
|
+
models: setModelEffort(draft.models ?? [], id, effort)
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
/** Keep the typed text; only a valid number (or blank) reaches the draft. */
|
|
2883
|
+
function setContext(id, text) {
|
|
2884
|
+
setContextText((current) => ({
|
|
2885
|
+
...current,
|
|
2886
|
+
[id]: text
|
|
2887
|
+
}));
|
|
2888
|
+
const tokens = parseContextInput(text);
|
|
2889
|
+
if (tokens !== null) edit({
|
|
2890
|
+
...draft,
|
|
2891
|
+
models: setModelContext(draft.models ?? [], id, tokens)
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
function applyTicks(next) {
|
|
2895
|
+
setStash(next.stash);
|
|
2896
|
+
edit({
|
|
2897
|
+
...draft,
|
|
2898
|
+
models: next.models
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
const invalidContext = Object.entries(contextText).filter(([id, text]) => present.has(id) && parseContextInput(text) === null).map(([id]) => id);
|
|
2902
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
2903
|
+
className: "dsh-subscription-manager",
|
|
2904
|
+
ref: dialog,
|
|
2905
|
+
"aria-labelledby": title,
|
|
2906
|
+
"aria-describedby": description,
|
|
2907
|
+
onCancel: (event) => {
|
|
2908
|
+
if (saveLock.current) event.preventDefault();
|
|
2909
|
+
},
|
|
2910
|
+
onClose,
|
|
2911
|
+
style: {
|
|
2912
|
+
width: 620,
|
|
2913
|
+
maxWidth: "calc(100vw - 32px)",
|
|
2914
|
+
maxHeight: "calc(100dvh - 32px)",
|
|
2915
|
+
overflow: "auto"
|
|
2916
|
+
},
|
|
2917
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: providerSettingsCss }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2918
|
+
style: {
|
|
2919
|
+
...stack,
|
|
2920
|
+
padding: 20
|
|
2921
|
+
},
|
|
2922
|
+
children: [
|
|
2923
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
2924
|
+
style: {
|
|
2925
|
+
...actions,
|
|
2926
|
+
justifyContent: "space-between"
|
|
2927
|
+
},
|
|
2928
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
2929
|
+
id: title,
|
|
2930
|
+
style: { margin: 0 },
|
|
2931
|
+
children: t("ollamaManageTitle")
|
|
2932
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2933
|
+
type: "button",
|
|
2934
|
+
autoFocus: true,
|
|
2935
|
+
disabled: saving,
|
|
2936
|
+
onClick: onClose,
|
|
2937
|
+
children: t("imageClose")
|
|
2938
|
+
})]
|
|
2939
|
+
}),
|
|
2940
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2941
|
+
id: description,
|
|
2942
|
+
className: "dsh-subscription-hint",
|
|
2943
|
+
children: t("ollamaManageHint")
|
|
2944
|
+
}),
|
|
2945
|
+
view?.configOverridesModels === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2946
|
+
className: "dsh-subscription-hint",
|
|
2947
|
+
children: t("ollamaCatalogOverride")
|
|
2948
|
+
}),
|
|
2949
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2950
|
+
role: "alert",
|
|
2951
|
+
children: error
|
|
2952
|
+
}),
|
|
2953
|
+
loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2954
|
+
role: "status",
|
|
2955
|
+
className: "dsh-subscription-hint",
|
|
2956
|
+
children: t("modelDefaultsLoading")
|
|
2957
|
+
}),
|
|
2958
|
+
!loading && !view && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2959
|
+
type: "button",
|
|
2960
|
+
onClick: () => setAttempt((value) => value + 1),
|
|
2961
|
+
children: t("modelDefaultsRetry")
|
|
2962
|
+
}),
|
|
2963
|
+
view && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
2964
|
+
disabled: saving,
|
|
2965
|
+
style: {
|
|
2966
|
+
...stack,
|
|
2967
|
+
border: 0,
|
|
2968
|
+
margin: 0,
|
|
2969
|
+
padding: 0,
|
|
2970
|
+
minWidth: 0
|
|
2971
|
+
},
|
|
2972
|
+
children: [
|
|
2973
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2974
|
+
style: {
|
|
2975
|
+
borderTop: border,
|
|
2976
|
+
paddingTop: 12,
|
|
2977
|
+
display: "grid",
|
|
2978
|
+
gap: 12
|
|
2979
|
+
},
|
|
2980
|
+
children: [
|
|
2981
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2982
|
+
style: { margin: 0 },
|
|
2983
|
+
children: t("modelsEdit")
|
|
2984
|
+
}),
|
|
2985
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2986
|
+
style: actions,
|
|
2987
|
+
children: [
|
|
2988
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2989
|
+
type: "password",
|
|
2990
|
+
autoComplete: "off",
|
|
2991
|
+
style: { flex: "1 1 180px" },
|
|
2992
|
+
"aria-label": t("ollamaKeyPlaceholder"),
|
|
2993
|
+
placeholder: t("ollamaKeyPlaceholder"),
|
|
2994
|
+
value: apiKey,
|
|
2995
|
+
onChange: (event) => setApiKey(event.target.value)
|
|
2996
|
+
}),
|
|
2997
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2998
|
+
type: "button",
|
|
2999
|
+
className: "dsh-subscription-quiet",
|
|
3000
|
+
disabled: fetching,
|
|
3001
|
+
onClick: () => {
|
|
3002
|
+
fetchCandidates();
|
|
3003
|
+
},
|
|
3004
|
+
children: fetching ? t("ollamaFetching") : t("ollamaFetch")
|
|
3005
|
+
}),
|
|
3006
|
+
candidates !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3007
|
+
className: "dsh-subscription-hint",
|
|
3008
|
+
children: t("ollamaCandidatesCount", { count: candidates.length })
|
|
3009
|
+
})
|
|
3010
|
+
]
|
|
3011
|
+
}),
|
|
3012
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3013
|
+
className: "dsh-subscription-hint",
|
|
3014
|
+
children: t("ollamaKeyHint")
|
|
3015
|
+
}),
|
|
3016
|
+
fetchError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3017
|
+
role: "alert",
|
|
3018
|
+
children: fetchError
|
|
3019
|
+
}),
|
|
3020
|
+
candidates?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3021
|
+
className: "dsh-subscription-hint",
|
|
3022
|
+
children: t("ollamaCandidatesEmpty")
|
|
3023
|
+
}),
|
|
3024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3025
|
+
style: actions,
|
|
3026
|
+
children: [
|
|
3027
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3028
|
+
style: { flex: "1 1 180px" },
|
|
3029
|
+
value: query,
|
|
3030
|
+
onChange: (event) => setQuery(event.target.value),
|
|
3031
|
+
placeholder: t("modelDefaultsFilterPlaceholder"),
|
|
3032
|
+
"aria-label": t("modelDefaultsFilterPlaceholder")
|
|
3033
|
+
}),
|
|
3034
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3035
|
+
type: "button",
|
|
3036
|
+
onClick: () => applyTicks(tickModels(draft.models ?? [], stash, candidates ?? [], shown.map((row) => row.id))),
|
|
3037
|
+
children: t("modelsSelectAll")
|
|
3038
|
+
}),
|
|
3039
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3040
|
+
type: "button",
|
|
3041
|
+
onClick: () => applyTicks(untickModels(draft.models ?? [], stash, shown.map((row) => row.id))),
|
|
3042
|
+
children: t("modelsSelectNone")
|
|
3043
|
+
})
|
|
3044
|
+
]
|
|
3045
|
+
}),
|
|
3046
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3047
|
+
style: {
|
|
3048
|
+
maxHeight: 360,
|
|
3049
|
+
overflowY: "auto",
|
|
3050
|
+
display: "grid",
|
|
3051
|
+
gap: 8
|
|
3052
|
+
},
|
|
3053
|
+
children: [shown.map((row) => {
|
|
3054
|
+
const stored = (draft.models ?? []).find((entry) => entry.id === row.id);
|
|
3055
|
+
const index = (draft.models ?? []).findIndex((entry) => entry.id === row.id);
|
|
3056
|
+
const open = expanded.has(row.id);
|
|
3057
|
+
const details = `${row.id}-details`;
|
|
3058
|
+
const efforts = stored === void 0 ? [] : effortOptions(stored, fetchedById.get(row.id)?.efforts, view.models.find((entry) => entry.id === row.id)?.efforts);
|
|
3059
|
+
const contextProblem = invalidContext.includes(row.id);
|
|
3060
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3061
|
+
style: {
|
|
3062
|
+
borderBottom: border,
|
|
3063
|
+
padding: "8px 2px",
|
|
3064
|
+
display: "grid",
|
|
3065
|
+
gap: 8
|
|
3066
|
+
},
|
|
3067
|
+
children: [
|
|
3068
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3069
|
+
style: { overflowWrap: "anywhere" },
|
|
3070
|
+
children: [
|
|
3071
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3072
|
+
type: "checkbox",
|
|
3073
|
+
checked: row.ticked,
|
|
3074
|
+
onChange: (event) => {
|
|
3075
|
+
applyTicks(event.target.checked ? tickModel(draft.models ?? [], stash, candidates ?? [], row.id) : untickModel(draft.models ?? [], stash, row.id));
|
|
3076
|
+
}
|
|
3077
|
+
}),
|
|
3078
|
+
" ",
|
|
3079
|
+
row.name,
|
|
3080
|
+
" ",
|
|
3081
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3082
|
+
style: subscriptionCardStyles.status,
|
|
3083
|
+
children: capabilities(t, row)
|
|
3084
|
+
})
|
|
3085
|
+
]
|
|
3086
|
+
}),
|
|
3087
|
+
stored !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3088
|
+
style: actions,
|
|
3089
|
+
children: [
|
|
3090
|
+
row.thinking && efforts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3091
|
+
style: actions,
|
|
3092
|
+
children: [t("modelDefaultsTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
3093
|
+
"aria-label": `${row.name} ${t("modelDefaultsTitle")}`,
|
|
3094
|
+
value: stored.defaultEffort ?? "",
|
|
3095
|
+
onChange: (event) => setEffort(row.id, event.target.value),
|
|
3096
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3097
|
+
value: "",
|
|
3098
|
+
children: t("modelDefaultsFollowProvider")
|
|
3099
|
+
}), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3100
|
+
value: effort.id,
|
|
3101
|
+
disabled: effort.unavailable === true,
|
|
3102
|
+
children: effort.unavailable === true ? `${effort.name} (${t("modelsUnavailable")})` : effort.name
|
|
3103
|
+
}, effort.id))]
|
|
3104
|
+
})]
|
|
3105
|
+
}),
|
|
3106
|
+
row.thinking && efforts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("ollamaEffortsUnknown") }),
|
|
3107
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3108
|
+
style: actions,
|
|
3109
|
+
children: [t("modelsContext"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3110
|
+
style: { width: 140 },
|
|
3111
|
+
inputMode: "numeric",
|
|
3112
|
+
"aria-invalid": contextProblem,
|
|
3113
|
+
value: contextText[row.id] ?? (stored.contextOverride === void 0 ? "" : String(stored.contextOverride)),
|
|
3114
|
+
"aria-label": `${row.name} ${t("modelsContext")}`,
|
|
3115
|
+
placeholder: row.contextWindow === void 0 ? "" : String(row.contextWindow),
|
|
3116
|
+
onChange: (event) => setContext(row.id, event.target.value)
|
|
3117
|
+
})]
|
|
3118
|
+
}),
|
|
3119
|
+
row.contextWindow !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("modelsContextBounds", {
|
|
3120
|
+
default: row.contextWindow,
|
|
3121
|
+
max: row.contextWindow
|
|
3122
|
+
}) }),
|
|
3123
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3124
|
+
type: "button",
|
|
3125
|
+
className: "dsh-subscription-quiet",
|
|
3126
|
+
"aria-expanded": open,
|
|
3127
|
+
"aria-controls": details,
|
|
3128
|
+
"aria-label": `${row.name} ${t("ollamaRowDetails")}`,
|
|
3129
|
+
onClick: () => {
|
|
3130
|
+
const next = new Set(expanded);
|
|
3131
|
+
if (open) next.delete(row.id);
|
|
3132
|
+
else next.add(row.id);
|
|
3133
|
+
setExpanded(next);
|
|
3134
|
+
},
|
|
3135
|
+
children: [
|
|
3136
|
+
t("ollamaRowDetails"),
|
|
3137
|
+
" ",
|
|
3138
|
+
open ? "▾" : "▸"
|
|
3139
|
+
]
|
|
3140
|
+
})
|
|
3141
|
+
]
|
|
3142
|
+
}),
|
|
3143
|
+
contextProblem && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3144
|
+
role: "alert",
|
|
3145
|
+
children: t("modelsContextInvalid", { model: row.name })
|
|
3146
|
+
}),
|
|
3147
|
+
stored !== void 0 && open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3148
|
+
id: details,
|
|
3149
|
+
style: actions,
|
|
3150
|
+
children: [
|
|
3151
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3152
|
+
style: actions,
|
|
3153
|
+
children: [t("ollamaDisplayName"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3154
|
+
value: stored.name ?? "",
|
|
3155
|
+
placeholder: row.id,
|
|
3156
|
+
"aria-label": `${row.name} ${t("ollamaDisplayName")}`,
|
|
3157
|
+
onChange: (event) => rename(row.id, event.target.value),
|
|
3158
|
+
onBlur: (event) => rename(row.id, event.target.value.trim())
|
|
3159
|
+
})]
|
|
3160
|
+
}),
|
|
3161
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3162
|
+
type: "button",
|
|
3163
|
+
disabled: index === 0,
|
|
3164
|
+
"aria-label": `${row.name} ${t("ollamaMoveUp")}`,
|
|
3165
|
+
onClick: () => edit({
|
|
3166
|
+
...draft,
|
|
3167
|
+
models: moveModel(draft.models ?? [], row.id, -1)
|
|
3168
|
+
}),
|
|
3169
|
+
children: t("ollamaMoveUp")
|
|
3170
|
+
}),
|
|
3171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3172
|
+
type: "button",
|
|
3173
|
+
disabled: index === (draft.models ?? []).length - 1,
|
|
3174
|
+
"aria-label": `${row.name} ${t("ollamaMoveDown")}`,
|
|
3175
|
+
onClick: () => edit({
|
|
3176
|
+
...draft,
|
|
3177
|
+
models: moveModel(draft.models ?? [], row.id, 1)
|
|
3178
|
+
}),
|
|
3179
|
+
children: t("ollamaMoveDown")
|
|
3180
|
+
})
|
|
3181
|
+
]
|
|
3182
|
+
})
|
|
3183
|
+
]
|
|
3184
|
+
}, row.id);
|
|
3185
|
+
}), shown.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3186
|
+
className: "dsh-subscription-hint",
|
|
3187
|
+
children: query.trim() ? t("modelDefaultsFilterEmpty", { query }) : t("ollamaModelsEmpty")
|
|
3188
|
+
})]
|
|
3189
|
+
}),
|
|
3190
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("modelsContextHint") })
|
|
3191
|
+
]
|
|
3192
|
+
}),
|
|
3193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3194
|
+
style: {
|
|
3195
|
+
...stack,
|
|
3196
|
+
gap: 6
|
|
3197
|
+
},
|
|
3198
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3199
|
+
className: "dsh-subscription-label",
|
|
3200
|
+
children: t("ollamaBaseURL")
|
|
3201
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3202
|
+
value: draft.baseURL ?? "",
|
|
3203
|
+
placeholder: view.effective.baseURL,
|
|
3204
|
+
onChange: (event) => editOverride("baseURL", event.target.value)
|
|
3205
|
+
})]
|
|
3206
|
+
}),
|
|
3207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3208
|
+
style: {
|
|
3209
|
+
...stack,
|
|
3210
|
+
gap: 6
|
|
3211
|
+
},
|
|
3212
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3213
|
+
className: "dsh-subscription-label",
|
|
3214
|
+
children: t("ollamaApiKeyRef")
|
|
3215
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3216
|
+
value: draft.apiKeyRef ?? "",
|
|
3217
|
+
placeholder: view.effective.apiKeyRef,
|
|
3218
|
+
onChange: (event) => editOverride("apiKeyRef", event.target.value)
|
|
3219
|
+
})]
|
|
3220
|
+
}),
|
|
3221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3222
|
+
className: "dsh-subscription-hint",
|
|
3223
|
+
children: t("ollamaConnectionHint")
|
|
3224
|
+
}),
|
|
3225
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
3226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3227
|
+
type: "checkbox",
|
|
3228
|
+
checked: (draft.web ?? view.effective.web) === true,
|
|
3229
|
+
onChange: (event) => edit({
|
|
3230
|
+
...draft,
|
|
3231
|
+
web: event.target.checked
|
|
3232
|
+
})
|
|
3233
|
+
}),
|
|
3234
|
+
" ",
|
|
3235
|
+
t("modelsWebSearch")
|
|
3236
|
+
] }),
|
|
3237
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3238
|
+
className: "dsh-subscription-hint",
|
|
3239
|
+
children: t("ollamaWebHint")
|
|
3240
|
+
})
|
|
3241
|
+
]
|
|
3242
|
+
}),
|
|
3243
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
3244
|
+
style: {
|
|
3245
|
+
...stack,
|
|
3246
|
+
gap: 8,
|
|
3247
|
+
borderTop: border,
|
|
3248
|
+
padding: "14px 0 8px",
|
|
3249
|
+
position: "sticky",
|
|
3250
|
+
bottom: 0,
|
|
3251
|
+
background: "var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))"
|
|
3252
|
+
},
|
|
3253
|
+
children: [saveError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3254
|
+
role: "alert",
|
|
3255
|
+
children: saveError
|
|
3256
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3257
|
+
style: {
|
|
3258
|
+
...actions,
|
|
3259
|
+
justifyContent: "flex-end"
|
|
3260
|
+
},
|
|
3261
|
+
children: [
|
|
3262
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3263
|
+
type: "button",
|
|
3264
|
+
style: { marginRight: "auto" },
|
|
3265
|
+
disabled: loading || saving,
|
|
3266
|
+
onClick: () => {
|
|
3267
|
+
resetDefaults();
|
|
3268
|
+
},
|
|
3269
|
+
children: t("ollamaResetDefaults")
|
|
3270
|
+
}),
|
|
3271
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3272
|
+
type: "button",
|
|
3273
|
+
disabled: saving,
|
|
3274
|
+
onClick: onClose,
|
|
3275
|
+
children: t("cancel")
|
|
1987
3276
|
}),
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
1997
|
-
window: window$1,
|
|
1998
|
-
t,
|
|
1999
|
-
observedAt: observedAt[id],
|
|
2000
|
-
stale: errors[id] !== void 0 || snapshot?.stale === true
|
|
2001
|
-
})]
|
|
2002
|
-
}, index);
|
|
3277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3278
|
+
type: "button",
|
|
3279
|
+
className: "dsh-subscription-primary",
|
|
3280
|
+
disabled: loading || saving || !dirty || invalidContext.length > 0,
|
|
3281
|
+
onClick: () => {
|
|
3282
|
+
save();
|
|
3283
|
+
},
|
|
3284
|
+
children: saving ? t("modelDefaultsSaving") : t("modelsSave")
|
|
2003
3285
|
})
|
|
2004
3286
|
]
|
|
2005
3287
|
})]
|
|
2006
|
-
}),
|
|
2007
|
-
!configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2008
|
-
style: subscriptionCardStyles.actions,
|
|
2009
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2010
|
-
type: "button",
|
|
2011
|
-
style: subscriptionCardStyles.button,
|
|
2012
|
-
onClick: () => {
|
|
2013
|
-
refreshStatus();
|
|
2014
|
-
},
|
|
2015
|
-
children: t("usageRefresh")
|
|
2016
|
-
})
|
|
2017
3288
|
})
|
|
2018
3289
|
]
|
|
2019
|
-
}
|
|
2020
|
-
})
|
|
3290
|
+
})]
|
|
3291
|
+
});
|
|
2021
3292
|
}
|
|
2022
3293
|
|
|
2023
3294
|
//#endregion
|
|
2024
|
-
//#region src/client/
|
|
3295
|
+
//#region src/client/OllamaCard.tsx
|
|
2025
3296
|
function message(error) {
|
|
2026
3297
|
return error instanceof Error ? error.message : String(error);
|
|
2027
3298
|
}
|
|
2028
|
-
/**
|
|
2029
|
-
|
|
2030
|
-
|
|
3299
|
+
/**
|
|
3300
|
+
* Ollama Cloud route card: the catalog the model picker shows, the web
|
|
3301
|
+
* capability toggle, and the API-key usage row. The settings dialog owns
|
|
3302
|
+
* every write; the card only reads, so a save is reflected by re-reading.
|
|
3303
|
+
*/
|
|
3304
|
+
function OllamaCard({ rpc, t }) {
|
|
3305
|
+
const [view, setView] = (0, react.useState)();
|
|
3306
|
+
const [configured, setConfigured] = (0, react.useState)();
|
|
2031
3307
|
const [usage, setUsage] = (0, react.useState)();
|
|
2032
3308
|
const [observedAt, setObservedAt] = (0, react.useState)();
|
|
2033
3309
|
const [usageStale, setUsageStale] = (0, react.useState)(false);
|
|
2034
|
-
const [authorizeUrl, setAuthorizeUrl] = (0, react.useState)();
|
|
2035
|
-
const [error, setError] = (0, react.useState)();
|
|
2036
3310
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
3311
|
+
const [statusError, setStatusError] = (0, react.useState)();
|
|
3312
|
+
const [usageError, setUsageError] = (0, react.useState)();
|
|
2037
3313
|
const [manage, setManage] = (0, react.useState)(false);
|
|
2038
|
-
const authenticated = (0, react.useRef)();
|
|
2039
|
-
const refreshStatus = (0, react.useCallback)(async () => {
|
|
2040
|
-
try {
|
|
2041
|
-
const value = await callSubscriptionsAuth(rpc, "cursorStatus", {});
|
|
2042
|
-
setStatus(value);
|
|
2043
|
-
if (authenticated.current !== value.authenticated) {
|
|
2044
|
-
authenticated.current = value.authenticated;
|
|
2045
|
-
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
2046
|
-
}
|
|
2047
|
-
if (value.authenticated) setAuthorizeUrl(void 0);
|
|
2048
|
-
if (value.error) setError(value.error);
|
|
2049
|
-
} catch (failure) {
|
|
2050
|
-
setError(message(failure));
|
|
2051
|
-
}
|
|
2052
|
-
}, [rpc]);
|
|
2053
3314
|
const refreshUsage = (0, react.useCallback)(async () => {
|
|
2054
3315
|
setLoading(true);
|
|
2055
3316
|
try {
|
|
2056
|
-
const value = await callSubscriptionsAuth(rpc, "
|
|
3317
|
+
const value = await callSubscriptionsAuth(rpc, "externalUsage", { source: "ollama-cloud" });
|
|
2057
3318
|
setUsage(value);
|
|
2058
3319
|
setObservedAt(value.observedAt ?? Date.now());
|
|
2059
3320
|
setUsageStale(value.stale === true);
|
|
2060
|
-
|
|
3321
|
+
setUsageError(void 0);
|
|
2061
3322
|
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
2062
3323
|
} catch (failure) {
|
|
2063
|
-
|
|
3324
|
+
setUsageError(message(failure));
|
|
2064
3325
|
setUsageStale(true);
|
|
2065
3326
|
} finally {
|
|
2066
3327
|
setLoading(false);
|
|
2067
3328
|
}
|
|
2068
3329
|
}, [rpc]);
|
|
2069
|
-
(0, react.
|
|
2070
|
-
refreshStatus();
|
|
2071
|
-
}, [refreshStatus]);
|
|
2072
|
-
(0, react.useEffect)(() => {
|
|
2073
|
-
if (status?.busy !== true) return;
|
|
2074
|
-
const timer = setInterval(() => {
|
|
2075
|
-
refreshStatus();
|
|
2076
|
-
}, 2e3);
|
|
2077
|
-
return () => clearInterval(timer);
|
|
2078
|
-
}, [status?.busy, refreshStatus]);
|
|
2079
|
-
(0, react.useEffect)(() => {
|
|
2080
|
-
if (status?.authenticated === true) refreshUsage();
|
|
2081
|
-
}, [status?.authenticated, refreshUsage]);
|
|
2082
|
-
const login = async () => {
|
|
2083
|
-
setError(void 0);
|
|
2084
|
-
try {
|
|
2085
|
-
const value = await callSubscriptionsAuth(rpc, "cursorLogin", {});
|
|
2086
|
-
setAuthorizeUrl(value.authorizeUrl);
|
|
2087
|
-
window.open(value.authorizeUrl, "_blank", "noopener");
|
|
2088
|
-
await refreshStatus();
|
|
2089
|
-
} catch (failure) {
|
|
2090
|
-
setError(message(failure));
|
|
2091
|
-
}
|
|
2092
|
-
};
|
|
2093
|
-
const cancel = async () => {
|
|
2094
|
-
try {
|
|
2095
|
-
await callSubscriptionsAuth(rpc, "cursorCancel", {});
|
|
2096
|
-
setAuthorizeUrl(void 0);
|
|
2097
|
-
await refreshStatus();
|
|
2098
|
-
} catch (failure) {
|
|
2099
|
-
setError(message(failure));
|
|
2100
|
-
}
|
|
2101
|
-
};
|
|
2102
|
-
const logout = async () => {
|
|
2103
|
-
if (!window.confirm(t("logoutAccountConfirm", {
|
|
2104
|
-
account: "Cursor",
|
|
2105
|
-
provider: "Cursor"
|
|
2106
|
-
}))) return;
|
|
3330
|
+
const refreshStatus = (0, react.useCallback)(async () => {
|
|
2107
3331
|
try {
|
|
2108
|
-
await callSubscriptionsAuth(rpc, "
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
3332
|
+
const [settings, status] = await Promise.all([callSubscriptionsAuth(rpc, "ollamaSettings", {}), callSubscriptionsAuth(rpc, "externalStatus", {})]);
|
|
3333
|
+
setView(settings);
|
|
3334
|
+
setStatusError(void 0);
|
|
3335
|
+
const isConfigured = status["ollama-cloud"]?.configured === true;
|
|
3336
|
+
setConfigured(isConfigured);
|
|
3337
|
+
if (isConfigured) refreshUsage();
|
|
2113
3338
|
} catch (failure) {
|
|
2114
|
-
|
|
3339
|
+
setStatusError(message(failure));
|
|
2115
3340
|
}
|
|
2116
|
-
};
|
|
2117
|
-
|
|
3341
|
+
}, [rpc, refreshUsage]);
|
|
3342
|
+
(0, react.useEffect)(() => {
|
|
3343
|
+
refreshStatus();
|
|
3344
|
+
}, [refreshStatus]);
|
|
2118
3345
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2119
3346
|
style: subscriptionCardStyles.card,
|
|
2120
3347
|
children: [
|
|
@@ -2122,51 +3349,40 @@ function CursorCard({ rpc, t }) {
|
|
|
2122
3349
|
style: subscriptionCardStyles.header,
|
|
2123
3350
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
2124
3351
|
...subscriptionCardStyles.dot,
|
|
2125
|
-
background:
|
|
3352
|
+
background: configured === true ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-label-dimmed)"
|
|
2126
3353
|
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2127
3354
|
style: subscriptionCardStyles.name,
|
|
2128
|
-
children: t("
|
|
3355
|
+
children: t("ollamaTitle")
|
|
2129
3356
|
})]
|
|
2130
3357
|
}),
|
|
2131
3358
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2132
3359
|
style: subscriptionCardStyles.status,
|
|
2133
|
-
children:
|
|
3360
|
+
children: view === void 0 ? t("checking") : view.models.length > 0 ? t("ollamaStatus", { count: view.models.length }) : t("ollamaStatusNone")
|
|
2134
3361
|
}),
|
|
2135
|
-
|
|
3362
|
+
view !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3363
|
+
style: subscriptionCardStyles.status,
|
|
3364
|
+
children: view.effective.web ? t("ollamaWebOn") : t("ollamaWebOff")
|
|
3365
|
+
}),
|
|
3366
|
+
statusError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2136
3367
|
style: subscriptionCardStyles.error,
|
|
2137
|
-
children:
|
|
3368
|
+
children: t("ollamaModelsLoadFailed", { message: statusError })
|
|
2138
3369
|
}),
|
|
2139
|
-
|
|
3370
|
+
view !== void 0 && configured === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3371
|
+
style: subscriptionCardStyles.status,
|
|
3372
|
+
children: t("ollamaConfigureHint", { ref: view.effective.apiKeyRef })
|
|
3373
|
+
}),
|
|
3374
|
+
configured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2140
3375
|
style: subscriptionCardStyles.account,
|
|
2141
3376
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2142
3377
|
style: subscriptionCardStyles.accountHeader,
|
|
2143
|
-
children: [
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
children: "Cursor"
|
|
2152
|
-
}),
|
|
2153
|
-
status.expiresAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2154
|
-
style: subscriptionCardStyles.status,
|
|
2155
|
-
children: t("accountExpires", { date: new Date(status.expiresAt).toLocaleString() })
|
|
2156
|
-
}),
|
|
2157
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2158
|
-
type: "button",
|
|
2159
|
-
style: {
|
|
2160
|
-
...subscriptionCardStyles.button,
|
|
2161
|
-
marginLeft: "auto",
|
|
2162
|
-
flexShrink: 0
|
|
2163
|
-
},
|
|
2164
|
-
onClick: () => {
|
|
2165
|
-
logout();
|
|
2166
|
-
},
|
|
2167
|
-
children: t("logout")
|
|
2168
|
-
})
|
|
2169
|
-
]
|
|
3378
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3379
|
+
style: subscriptionCardStyles.defaultStar,
|
|
3380
|
+
title: t("defaultBadge"),
|
|
3381
|
+
children: "★"
|
|
3382
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3383
|
+
style: subscriptionCardStyles.accountName,
|
|
3384
|
+
children: t("externalUsageApiKey")
|
|
3385
|
+
})]
|
|
2170
3386
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2171
3387
|
style: subscriptionCardStyles.usage,
|
|
2172
3388
|
children: [
|
|
@@ -2178,7 +3394,7 @@ function CursorCard({ rpc, t }) {
|
|
|
2178
3394
|
children: t("usageTitle")
|
|
2179
3395
|
}),
|
|
2180
3396
|
usage?.plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2181
|
-
style: subscriptionCardStyles.
|
|
3397
|
+
style: subscriptionCardStyles.usagePlan,
|
|
2182
3398
|
children: t("usagePlan", { plan: usage.plan })
|
|
2183
3399
|
}),
|
|
2184
3400
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -2196,13 +3412,21 @@ function CursorCard({ rpc, t }) {
|
|
|
2196
3412
|
style: subscriptionCardStyles.status,
|
|
2197
3413
|
children: t("usageLoading")
|
|
2198
3414
|
}),
|
|
3415
|
+
usageError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3416
|
+
style: subscriptionCardStyles.error,
|
|
3417
|
+
children: t("usageError", { message: usageError })
|
|
3418
|
+
}),
|
|
3419
|
+
usage !== void 0 && usage.supported === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3420
|
+
style: subscriptionCardStyles.status,
|
|
3421
|
+
children: t("usageUnsupported")
|
|
3422
|
+
}),
|
|
2199
3423
|
usage?.windows?.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2200
3424
|
style: subscriptionCardStyles.status,
|
|
2201
3425
|
children: t("usageEmpty")
|
|
2202
3426
|
}),
|
|
2203
3427
|
usage?.windows?.map((window$1, index) => {
|
|
2204
3428
|
const percent = displayUsedPercent(window$1.usedPercent);
|
|
2205
|
-
const label =
|
|
3429
|
+
const label = externalUsageWindowLabel(t, window$1);
|
|
2206
3430
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2207
3431
|
style: subscriptionCardStyles.usageRow,
|
|
2208
3432
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2219,46 +3443,34 @@ function CursorCard({ rpc, t }) {
|
|
|
2219
3443
|
]
|
|
2220
3444
|
})]
|
|
2221
3445
|
}),
|
|
3446
|
+
view?.configOverridesModels === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3447
|
+
style: subscriptionCardStyles.status,
|
|
3448
|
+
children: t("ollamaCatalogOverride")
|
|
3449
|
+
}),
|
|
2222
3450
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2223
3451
|
style: subscriptionCardStyles.actions,
|
|
2224
|
-
children: [
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
children: t("accountsManage")
|
|
2239
|
-
}),
|
|
2240
|
-
status?.busy === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2241
|
-
type: "button",
|
|
2242
|
-
style: subscriptionCardStyles.button,
|
|
2243
|
-
onClick: () => {
|
|
2244
|
-
cancel();
|
|
2245
|
-
},
|
|
2246
|
-
children: t("cancel")
|
|
2247
|
-
})
|
|
2248
|
-
]
|
|
2249
|
-
}),
|
|
2250
|
-
status?.busy === true && authorizeUrl !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2251
|
-
href: authorizeUrl,
|
|
2252
|
-
target: "_blank",
|
|
2253
|
-
rel: "noopener noreferrer",
|
|
2254
|
-
children: t("cursorOpenLogin")
|
|
3452
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3453
|
+
type: "button",
|
|
3454
|
+
style: subscriptionCardStyles.button,
|
|
3455
|
+
"aria-haspopup": "dialog",
|
|
3456
|
+
onClick: () => setManage(true),
|
|
3457
|
+
children: t("accountsManage")
|
|
3458
|
+
}), configured !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3459
|
+
type: "button",
|
|
3460
|
+
style: subscriptionCardStyles.button,
|
|
3461
|
+
onClick: () => {
|
|
3462
|
+
refreshStatus();
|
|
3463
|
+
},
|
|
3464
|
+
children: t("usageRefresh")
|
|
3465
|
+
})]
|
|
2255
3466
|
}),
|
|
2256
|
-
manage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2257
|
-
provider: "cursor-subscription",
|
|
2258
|
-
name: "Cursor",
|
|
3467
|
+
manage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OllamaSettingsManager, {
|
|
2259
3468
|
rpc,
|
|
2260
3469
|
t,
|
|
2261
|
-
onClose: () => setManage(false)
|
|
3470
|
+
onClose: () => setManage(false),
|
|
3471
|
+
onSaved: () => {
|
|
3472
|
+
refreshStatus();
|
|
3473
|
+
}
|
|
2262
3474
|
})
|
|
2263
3475
|
]
|
|
2264
3476
|
});
|
|
@@ -2333,15 +3545,15 @@ function showsResetCredits(provider, credits) {
|
|
|
2333
3545
|
return (credits?.length ?? 0) > 0;
|
|
2334
3546
|
}
|
|
2335
3547
|
/** The error line the settings card mounts. Claude never interpolates provider text. */
|
|
2336
|
-
function ResetCreditsErrorLine({ provider, message: message$
|
|
3548
|
+
function ResetCreditsErrorLine({ provider, message: message$3, t }) {
|
|
2337
3549
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2338
3550
|
style: subscriptionCardStyles.error,
|
|
2339
|
-
children: resetCreditsErrorText(provider, message$
|
|
3551
|
+
children: resetCreditsErrorText(provider, message$3, t)
|
|
2340
3552
|
});
|
|
2341
3553
|
}
|
|
2342
3554
|
/** Fixed sentence for Claude. Codex may include the lookup message; Claude must not. */
|
|
2343
|
-
function resetCreditsErrorText(provider, message$
|
|
2344
|
-
return provider === "claude" ? t("resetCreditsErrorFixed") : t("resetCreditsError", { message: message$
|
|
3555
|
+
function resetCreditsErrorText(provider, message$3, t) {
|
|
3556
|
+
return provider === "claude" ? t("resetCreditsErrorFixed") : t("resetCreditsError", { message: message$3 });
|
|
2345
3557
|
}
|
|
2346
3558
|
const summaryStyle = {
|
|
2347
3559
|
...subscriptionCardStyles.usageMeta,
|
|
@@ -2453,7 +3665,12 @@ function ResetCreditsDisclosure({ mode, credits, t, now = Date.now(), nextCredit
|
|
|
2453
3665
|
}),
|
|
2454
3666
|
mode === "grants" && state === "notUsable" && credit.cooldownUntil !== void 0 && credit.cooldownUntil > now && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2455
3667
|
"aria-hidden": "true",
|
|
2456
|
-
children: [
|
|
3668
|
+
children: [
|
|
3669
|
+
t("resetCreditCooldownUntil", { date: new Date(credit.cooldownUntil).toLocaleString() }),
|
|
3670
|
+
" ",
|
|
3671
|
+
"·",
|
|
3672
|
+
" "
|
|
3673
|
+
]
|
|
2457
3674
|
}),
|
|
2458
3675
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2459
3676
|
"aria-hidden": mode === "grants" ? true : void 0,
|
|
@@ -2599,7 +3816,7 @@ const styles$6 = {
|
|
|
2599
3816
|
//#endregion
|
|
2600
3817
|
//#region src/client/SubscriptionsSection.tsx
|
|
2601
3818
|
/** Poll cadence while a provider login attempt is busy. */
|
|
2602
|
-
const POLL_INTERVAL_MS
|
|
3819
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
2603
3820
|
const CLIENT_VERSION_SOURCES = {
|
|
2604
3821
|
npm: "clientVersionNpm",
|
|
2605
3822
|
local: "clientVersionLocal",
|
|
@@ -2872,12 +4089,12 @@ function SubscriptionsSection(props) {
|
|
|
2872
4089
|
const usageInflightRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
2873
4090
|
const usageRosterSignatureRef = (0, react.useRef)();
|
|
2874
4091
|
const [managedProvider, setManagedProvider] = (0, react.useState)();
|
|
2875
|
-
const setProviderError = (0, react.useCallback)((provider, message$
|
|
4092
|
+
const setProviderError = (0, react.useCallback)((provider, message$3) => {
|
|
2876
4093
|
if (!mountedRef.current) return;
|
|
2877
4094
|
setErrors((prev) => {
|
|
2878
4095
|
const next = { ...prev };
|
|
2879
|
-
if (message$
|
|
2880
|
-
else next[provider] = message$
|
|
4096
|
+
if (message$3 === void 0) delete next[provider];
|
|
4097
|
+
else next[provider] = message$3;
|
|
2881
4098
|
return next;
|
|
2882
4099
|
});
|
|
2883
4100
|
}, []);
|
|
@@ -2895,8 +4112,8 @@ function SubscriptionsSection(props) {
|
|
|
2895
4112
|
try {
|
|
2896
4113
|
response = await callSubscriptionsAuth(rpc, "status", {});
|
|
2897
4114
|
} catch (error) {
|
|
2898
|
-
const message$
|
|
2899
|
-
for (const { id } of PROVIDERS) setProviderError(id, message$
|
|
4115
|
+
const message$3 = error instanceof Error ? error.message : String(error);
|
|
4116
|
+
for (const { id } of PROVIDERS) setProviderError(id, message$3);
|
|
2900
4117
|
return;
|
|
2901
4118
|
}
|
|
2902
4119
|
if (!mountedRef.current) return;
|
|
@@ -2928,7 +4145,7 @@ function SubscriptionsSection(props) {
|
|
|
2928
4145
|
if (pollersRef.current.has(provider)) return;
|
|
2929
4146
|
pollersRef.current.set(provider, setInterval(() => {
|
|
2930
4147
|
refresh();
|
|
2931
|
-
}, POLL_INTERVAL_MS
|
|
4148
|
+
}, POLL_INTERVAL_MS));
|
|
2932
4149
|
}, [refresh]);
|
|
2933
4150
|
(0, react.useEffect)(() => {
|
|
2934
4151
|
mountedRef.current = true;
|
|
@@ -3090,7 +4307,8 @@ function SubscriptionsSection(props) {
|
|
|
3090
4307
|
rpc,
|
|
3091
4308
|
t,
|
|
3092
4309
|
setProviderError,
|
|
3093
|
-
startPolling
|
|
4310
|
+
startPolling,
|
|
4311
|
+
refresh
|
|
3094
4312
|
]);
|
|
3095
4313
|
const cancel = (0, react.useCallback)(async (provider) => {
|
|
3096
4314
|
if (rpc === void 0) return;
|
|
@@ -3466,6 +4684,10 @@ function SubscriptionsSection(props) {
|
|
|
3466
4684
|
rpc,
|
|
3467
4685
|
t
|
|
3468
4686
|
}),
|
|
4687
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OllamaCard, {
|
|
4688
|
+
rpc,
|
|
4689
|
+
t
|
|
4690
|
+
}),
|
|
3469
4691
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalUsageCards, {
|
|
3470
4692
|
rpc,
|
|
3471
4693
|
t
|
|
@@ -3833,7 +5055,8 @@ const HOST_CONTRACT = {
|
|
|
3833
5055
|
slots: "@deepseek-ai/dsh-client-ui-renderer"
|
|
3834
5056
|
},
|
|
3835
5057
|
markers: { composerStats: {
|
|
3836
|
-
|
|
5058
|
+
row: "data-composer-stats",
|
|
5059
|
+
dock: "data-composer-stat",
|
|
3837
5060
|
package: "@deepseek-ai/dsh-client-ui-chat"
|
|
3838
5061
|
} },
|
|
3839
5062
|
diagnostics: {
|
|
@@ -3886,6 +5109,10 @@ const HOST_CONTRACT = {
|
|
|
3886
5109
|
]
|
|
3887
5110
|
}
|
|
3888
5111
|
};
|
|
5112
|
+
/** Attribute selector for a host DOM marker (`data-composer-stats` → `[data-composer-stats]`). */
|
|
5113
|
+
function markerSelector(attribute) {
|
|
5114
|
+
return `[${attribute}]`;
|
|
5115
|
+
}
|
|
3889
5116
|
|
|
3890
5117
|
//#endregion
|
|
3891
5118
|
//#region src/client/host-icons.ts
|
|
@@ -4296,9 +5523,10 @@ function VideoGenerateToolview(props) {
|
|
|
4296
5523
|
//#region src/client/SpeedSelect.tsx
|
|
4297
5524
|
/**
|
|
4298
5525
|
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
4299
|
-
* the host's current model selection (the visibility gate). A
|
|
4300
|
-
*
|
|
4301
|
-
*
|
|
5526
|
+
* the host's current model selection (the visibility gate). A session whose
|
|
5527
|
+
* model selection is unavailable (an addressed subagent) answers hidden.
|
|
5528
|
+
* Any other model-directory failure throws, so the caller keeps its last
|
|
5529
|
+
* known state instead of locking the toggle away.
|
|
4302
5530
|
*
|
|
4303
5531
|
* `sessionId` is a plain string: slot and command contexts brand it through
|
|
4304
5532
|
* different dsh-session copies, and only the service boundary needs one.
|
|
@@ -4315,13 +5543,126 @@ function createSpeedLoader(connection, models, sessionId) {
|
|
|
4315
5543
|
visible: false,
|
|
4316
5544
|
tier: state.tier
|
|
4317
5545
|
};
|
|
4318
|
-
|
|
5546
|
+
let current;
|
|
5547
|
+
try {
|
|
5548
|
+
current = (await directories.directoryFor(sessionId).load()).current;
|
|
5549
|
+
} catch (error) {
|
|
5550
|
+
if (!modelSelectionUnavailable(error)) throw error;
|
|
5551
|
+
return {
|
|
5552
|
+
visible: false,
|
|
5553
|
+
tier: state.tier
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
4319
5556
|
return {
|
|
4320
5557
|
visible: current !== null && current.provider === "codex" && state.fastModels.includes(current.model),
|
|
4321
5558
|
tier: state.tier
|
|
4322
5559
|
};
|
|
4323
5560
|
};
|
|
4324
5561
|
}
|
|
5562
|
+
/** Host copy for a session that has no model seat (an addressed subagent). */
|
|
5563
|
+
const MODEL_SELECTION_UNAVAILABLE = "model selection is unavailable";
|
|
5564
|
+
function modelSelectionUnavailable(error) {
|
|
5565
|
+
return (error instanceof Error ? error.message : String(error)).includes(MODEL_SELECTION_UNAVAILABLE);
|
|
5566
|
+
}
|
|
5567
|
+
/** Stable identity of a model selection. Empty when nothing is selected. */
|
|
5568
|
+
function modelSelectionKey(current) {
|
|
5569
|
+
if (current == null) return "";
|
|
5570
|
+
return JSON.stringify([current.provider, current.model]);
|
|
5571
|
+
}
|
|
5572
|
+
/**
|
|
5573
|
+
* How often a host without a directory store is re-read, and how often a
|
|
5574
|
+
* missing directory service is retried. Neither interval calls
|
|
5575
|
+
* `subscriptions-auth.speed`.
|
|
5576
|
+
*/
|
|
5577
|
+
const MODEL_DIRECTORY_POLL_INTERVAL_MS = 3e3;
|
|
5578
|
+
/**
|
|
5579
|
+
* Mount reads of `speed`, including the first attempt. Further failures stop
|
|
5580
|
+
* instead of polling for the life of the composer.
|
|
5581
|
+
*/
|
|
5582
|
+
const SPEED_MOUNT_READ_ATTEMPTS = 4;
|
|
5583
|
+
/** Fired after `/fast` writes a tier, so an open composer re-reads `speed`. */
|
|
5584
|
+
const SPEED_CHANGED_EVENT = "dsh:subscriptions:speed-changed";
|
|
5585
|
+
/** Tell mounted Speed controls that this page changed a session tier. */
|
|
5586
|
+
function notifySpeedChanged() {
|
|
5587
|
+
if (typeof window === "undefined") return;
|
|
5588
|
+
window.dispatchEvent(new Event(SPEED_CHANGED_EVENT));
|
|
5589
|
+
}
|
|
5590
|
+
/**
|
|
5591
|
+
* Subscribe to the session's selected model. A directory store notifies the
|
|
5592
|
+
* listener only when `provider` or `model` changes. A directory with no store
|
|
5593
|
+
* is re-read on {@link MODEL_DIRECTORY_POLL_INTERVAL_MS}; a service that is
|
|
5594
|
+
* not registered yet, or whose `directoryFor` throws, is retried on that same
|
|
5595
|
+
* interval. The first successful attach after a miss notifies once, so a
|
|
5596
|
+
* mount that ran before the service existed reads `speed` again.
|
|
5597
|
+
*/
|
|
5598
|
+
function createModelSelectionWatch(models, sessionId) {
|
|
5599
|
+
return (listener) => {
|
|
5600
|
+
let stopped = false;
|
|
5601
|
+
let unsubscribeStore;
|
|
5602
|
+
let timer;
|
|
5603
|
+
let mode = "seek";
|
|
5604
|
+
let lastKey;
|
|
5605
|
+
const stopTimer = () => {
|
|
5606
|
+
if (timer === void 0) return;
|
|
5607
|
+
clearInterval(timer);
|
|
5608
|
+
timer = void 0;
|
|
5609
|
+
};
|
|
5610
|
+
const changed = (current) => {
|
|
5611
|
+
const next = modelSelectionKey(current);
|
|
5612
|
+
if (lastKey === void 0) {
|
|
5613
|
+
lastKey = next;
|
|
5614
|
+
return false;
|
|
5615
|
+
}
|
|
5616
|
+
if (next === lastKey) return false;
|
|
5617
|
+
lastKey = next;
|
|
5618
|
+
return true;
|
|
5619
|
+
};
|
|
5620
|
+
const attach = (late) => {
|
|
5621
|
+
if (stopped || mode !== "seek") return;
|
|
5622
|
+
let directory;
|
|
5623
|
+
try {
|
|
5624
|
+
directory = models()?.directoryFor(sessionId);
|
|
5625
|
+
} catch {
|
|
5626
|
+
return;
|
|
5627
|
+
}
|
|
5628
|
+
if (directory === void 0) return;
|
|
5629
|
+
const store = directory.store;
|
|
5630
|
+
if (store !== void 0) {
|
|
5631
|
+
mode = "store";
|
|
5632
|
+
stopTimer();
|
|
5633
|
+
lastKey = modelSelectionKey(store.getSnapshot().current);
|
|
5634
|
+
unsubscribeStore = store.subscribe(() => {
|
|
5635
|
+
if (!stopped && changed(store.getSnapshot().current)) listener();
|
|
5636
|
+
});
|
|
5637
|
+
if (late) listener();
|
|
5638
|
+
return;
|
|
5639
|
+
}
|
|
5640
|
+
mode = "poll";
|
|
5641
|
+
stopTimer();
|
|
5642
|
+
let force = late;
|
|
5643
|
+
const seed = () => {
|
|
5644
|
+
directory.load().then((state) => {
|
|
5645
|
+
if (stopped || mode !== "poll") return;
|
|
5646
|
+
const identityChanged = changed(state.current);
|
|
5647
|
+
const forced = force;
|
|
5648
|
+
force = false;
|
|
5649
|
+
if (identityChanged || forced) listener();
|
|
5650
|
+
}, () => {});
|
|
5651
|
+
};
|
|
5652
|
+
seed();
|
|
5653
|
+
timer = setInterval(seed, MODEL_DIRECTORY_POLL_INTERVAL_MS);
|
|
5654
|
+
};
|
|
5655
|
+
attach(false);
|
|
5656
|
+
if (mode === "seek") timer = setInterval(() => {
|
|
5657
|
+
attach(true);
|
|
5658
|
+
}, MODEL_DIRECTORY_POLL_INTERVAL_MS);
|
|
5659
|
+
return () => {
|
|
5660
|
+
stopped = true;
|
|
5661
|
+
unsubscribeStore?.();
|
|
5662
|
+
stopTimer();
|
|
5663
|
+
};
|
|
5664
|
+
};
|
|
5665
|
+
}
|
|
4325
5666
|
/** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
|
|
4326
5667
|
function createSpeedSetter(connection, sessionId) {
|
|
4327
5668
|
return (tier) => callSubscriptionsAuth(connection.rpc, "setSpeed", {
|
|
@@ -4337,20 +5678,15 @@ const TIERS = ["standard", "fast"];
|
|
|
4337
5678
|
/**
|
|
4338
5679
|
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
4339
5680
|
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
4340
|
-
* the current tier).
|
|
4341
|
-
*
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
*
|
|
4347
|
-
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
4348
|
-
* the current tier). The host pushes nothing on a model switch, so the
|
|
4349
|
-
* control re-reads on a slow poll with a single-flight guard; a failed read
|
|
4350
|
-
* keeps the last known state, so a transient RPC failure can never lock the
|
|
4351
|
-
* toggle away (the earlier mount-only load had no recovery path).
|
|
5681
|
+
* the current tier). It reads `subscriptions-auth.speed` on mount, when the
|
|
5682
|
+
* menu opens, when {@link SpeedSelectInjected.watchModel} reports a new
|
|
5683
|
+
* selected model, and when {@link notifySpeedChanged} fires. Until the first
|
|
5684
|
+
* read succeeds, a failure is retried on
|
|
5685
|
+
* {@link MODEL_DIRECTORY_POLL_INTERVAL_MS}, at most
|
|
5686
|
+
* {@link SPEED_MOUNT_READ_ATTEMPTS} times; after that, a failed read keeps
|
|
5687
|
+
* the last known state.
|
|
4352
5688
|
*/
|
|
4353
|
-
function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
5689
|
+
function SpeedSelect({ loadSpeed, setSpeed, watchModel, t }) {
|
|
4354
5690
|
const translate = t ?? fallbackTranslate$1;
|
|
4355
5691
|
const [state, setState] = (0, react.useState)(null);
|
|
4356
5692
|
const [open, setOpen] = (0, react.useState)(false);
|
|
@@ -4358,25 +5694,58 @@ function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
|
4358
5694
|
const rootRef = (0, react.useRef)(null);
|
|
4359
5695
|
const loadRef = (0, react.useRef)(loadSpeed);
|
|
4360
5696
|
loadRef.current = loadSpeed;
|
|
5697
|
+
const watchRef = (0, react.useRef)(watchModel);
|
|
5698
|
+
watchRef.current = watchModel;
|
|
4361
5699
|
(0, react.useEffect)(() => {
|
|
4362
5700
|
if (loadRef.current === void 0) return;
|
|
4363
5701
|
let cancelled = false;
|
|
4364
5702
|
let inflight = false;
|
|
5703
|
+
let again = false;
|
|
5704
|
+
let loadedOnce = false;
|
|
5705
|
+
let failures = 0;
|
|
5706
|
+
let retry;
|
|
5707
|
+
const stopRetry = () => {
|
|
5708
|
+
if (retry === void 0) return;
|
|
5709
|
+
clearInterval(retry);
|
|
5710
|
+
retry = void 0;
|
|
5711
|
+
};
|
|
4365
5712
|
const reload = () => {
|
|
4366
5713
|
const load = loadRef.current;
|
|
4367
|
-
if (load === void 0
|
|
5714
|
+
if (load === void 0) return;
|
|
5715
|
+
if (inflight) {
|
|
5716
|
+
again = true;
|
|
5717
|
+
return;
|
|
5718
|
+
}
|
|
4368
5719
|
inflight = true;
|
|
4369
5720
|
load().then((loaded) => {
|
|
4370
|
-
if (
|
|
4371
|
-
|
|
5721
|
+
if (cancelled) return;
|
|
5722
|
+
loadedOnce = true;
|
|
5723
|
+
stopRetry();
|
|
5724
|
+
setState(loaded);
|
|
5725
|
+
}, () => {
|
|
5726
|
+
failures += 1;
|
|
5727
|
+
if (failures >= SPEED_MOUNT_READ_ATTEMPTS) stopRetry();
|
|
5728
|
+
}).finally(() => {
|
|
4372
5729
|
inflight = false;
|
|
5730
|
+
if (cancelled || !again) return;
|
|
5731
|
+
again = false;
|
|
5732
|
+
reload();
|
|
4373
5733
|
});
|
|
4374
5734
|
};
|
|
4375
5735
|
reload();
|
|
4376
|
-
|
|
5736
|
+
retry = setInterval(() => {
|
|
5737
|
+
if (!loadedOnce) reload();
|
|
5738
|
+
}, MODEL_DIRECTORY_POLL_INTERVAL_MS);
|
|
5739
|
+
const unsubscribe = watchRef.current?.(reload);
|
|
5740
|
+
const onSpeedChanged = () => {
|
|
5741
|
+
reload();
|
|
5742
|
+
};
|
|
5743
|
+
window.addEventListener(SPEED_CHANGED_EVENT, onSpeedChanged);
|
|
4377
5744
|
return () => {
|
|
4378
5745
|
cancelled = true;
|
|
4379
|
-
|
|
5746
|
+
unsubscribe?.();
|
|
5747
|
+
stopRetry();
|
|
5748
|
+
window.removeEventListener(SPEED_CHANGED_EVENT, onSpeedChanged);
|
|
4380
5749
|
};
|
|
4381
5750
|
}, []);
|
|
4382
5751
|
(0, react.useEffect)(() => {
|
|
@@ -4572,8 +5941,9 @@ const PROVIDER_NAMES = {
|
|
|
4572
5941
|
"cursor-subscription": "Cursor",
|
|
4573
5942
|
"opencode-go": "OpenCode Go",
|
|
4574
5943
|
"kimi-coding": "Kimi Code",
|
|
4575
|
-
|
|
4576
|
-
"minimax-cn": "MiniMax CN"
|
|
5944
|
+
minimax: "MiniMax",
|
|
5945
|
+
"minimax-cn": "MiniMax CN",
|
|
5946
|
+
"ollama-cloud": "Ollama Cloud"
|
|
4577
5947
|
};
|
|
4578
5948
|
/** Key of one account's cached windows: `provider:accountKey`. */
|
|
4579
5949
|
function usageKeyOf(provider, account) {
|
|
@@ -4649,7 +6019,8 @@ async function loadBadgeRoster(rpc) {
|
|
|
4649
6019
|
["opencode-go", "opencode-go"],
|
|
4650
6020
|
["kimi-code", "kimi-coding"],
|
|
4651
6021
|
["minimax", "minimax"],
|
|
4652
|
-
["minimax-cn", "minimax-cn"]
|
|
6022
|
+
["minimax-cn", "minimax-cn"],
|
|
6023
|
+
["ollama-cloud", "ollama-cloud"]
|
|
4653
6024
|
]) {
|
|
4654
6025
|
refreshed.add(provider);
|
|
4655
6026
|
if (external.value[source]?.configured) roster.push({
|
|
@@ -4667,7 +6038,7 @@ async function loadBadgeRoster(rpc) {
|
|
|
4667
6038
|
}
|
|
4668
6039
|
async function usageOf(rpc, { provider, account }) {
|
|
4669
6040
|
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 });
|
|
6041
|
+
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
6042
|
return callSubscriptionsAuth(rpc, "usage", {
|
|
4672
6043
|
provider,
|
|
4673
6044
|
account: account.key
|
|
@@ -4805,6 +6176,8 @@ function usageWindowLabel(t, window$1) {
|
|
|
4805
6176
|
* every provider's accounts and their windows. Returns null when no data is
|
|
4806
6177
|
* available.
|
|
4807
6178
|
*/
|
|
6179
|
+
const STATS_ROW_SELECTOR = markerSelector(HOST_CONTRACT.markers.composerStats.row);
|
|
6180
|
+
const STATS_DOCK_SELECTOR = markerSelector(HOST_CONTRACT.markers.composerStats.dock);
|
|
4808
6181
|
function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
4809
6182
|
const translate = t ?? fallbackTranslate;
|
|
4810
6183
|
const [displays, setDisplays] = (0, react.useState)([]);
|
|
@@ -4964,15 +6337,18 @@ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
4964
6337
|
if (seat$1 === null) return;
|
|
4965
6338
|
const scope = statsScopeOf(seat$1);
|
|
4966
6339
|
if (scope === null) return;
|
|
4967
|
-
const find = () => scope.querySelector(
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
setStatsRow(
|
|
4971
|
-
|
|
6340
|
+
const find = () => scope.querySelector(STATS_ROW_SELECTOR);
|
|
6341
|
+
const settle = () => {
|
|
6342
|
+
const row = find();
|
|
6343
|
+
setStatsRow(row);
|
|
6344
|
+
if (row === null && scope.querySelector(STATS_DOCK_SELECTOR) !== null) observer.disconnect();
|
|
6345
|
+
};
|
|
6346
|
+
const observer = new MutationObserver(settle);
|
|
4972
6347
|
observer.observe(scope, {
|
|
4973
6348
|
childList: true,
|
|
4974
6349
|
subtree: true
|
|
4975
6350
|
});
|
|
6351
|
+
settle();
|
|
4976
6352
|
return () => {
|
|
4977
6353
|
observer.disconnect();
|
|
4978
6354
|
};
|
|
@@ -5096,15 +6472,19 @@ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
|
5096
6472
|
});
|
|
5097
6473
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [seat, statsRow !== null && statsRow.isConnected ? (0, react_dom.createPortal)(pill, statsRow) : pill] });
|
|
5098
6474
|
}
|
|
6475
|
+
/** How far statsScopeOf walks looking for `composerStats.row`. */
|
|
6476
|
+
const LEGACY_STATS_SCOPE_DEPTH = 4;
|
|
5099
6477
|
/**
|
|
5100
|
-
*
|
|
5101
|
-
*
|
|
5102
|
-
*
|
|
6478
|
+
* Scope for the legacy stats row. The nearest ancestor within
|
|
6479
|
+
* LEGACY_STATS_SCOPE_DEPTH that contains `composerStats.row`, so a badge
|
|
6480
|
+
* does not adopt another composer's pills. When none does, the seat's
|
|
6481
|
+
* parent: the slot outlet, which holds sibling `composerStats.dock` anchors
|
|
6482
|
+
* on hosts whose pills are dock entries.
|
|
5103
6483
|
*/
|
|
5104
6484
|
function statsScopeOf(seat) {
|
|
5105
6485
|
let node = seat.parentElement;
|
|
5106
|
-
for (let depth = 0; node !== null && depth <
|
|
5107
|
-
if (node.querySelector(
|
|
6486
|
+
for (let depth = 0; node !== null && depth < LEGACY_STATS_SCOPE_DEPTH; depth++) {
|
|
6487
|
+
if (node.querySelector(STATS_ROW_SELECTOR) !== null) return node;
|
|
5108
6488
|
node = node.parentElement;
|
|
5109
6489
|
}
|
|
5110
6490
|
return seat.parentElement;
|
|
@@ -5119,10 +6499,10 @@ function AccountMeta({ account, translate }) {
|
|
|
5119
6499
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5120
6500
|
style: styles.providerMeta,
|
|
5121
6501
|
title: account.account,
|
|
5122
|
-
children: [account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
6502
|
+
children: [account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5123
6503
|
style: styles.defaultStar,
|
|
5124
6504
|
"aria-label": "default",
|
|
5125
|
-
children: "★ "
|
|
6505
|
+
children: ["★", " "]
|
|
5126
6506
|
}), parts.join(" · ")]
|
|
5127
6507
|
});
|
|
5128
6508
|
}
|
|
@@ -5404,7 +6784,8 @@ function apply(ctx) {
|
|
|
5404
6784
|
locale: NS,
|
|
5405
6785
|
inject: (sessionId) => ({
|
|
5406
6786
|
loadSpeed: createSpeedLoader(connection, models, sessionId),
|
|
5407
|
-
setSpeed: createSpeedSetter(connection, sessionId)
|
|
6787
|
+
setSpeed: createSpeedSetter(connection, sessionId),
|
|
6788
|
+
watchModel: createModelSelectionWatch(models, sessionId)
|
|
5408
6789
|
})
|
|
5409
6790
|
}, SpeedSelect));
|
|
5410
6791
|
ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
|
|
@@ -5442,7 +6823,7 @@ function apply(ctx) {
|
|
|
5442
6823
|
}));
|
|
5443
6824
|
},
|
|
5444
6825
|
onSelect: async (option, session) => {
|
|
5445
|
-
await createSpeedSetter(connection, session.sessionId)(option.id);
|
|
6826
|
+
if (await createSpeedSetter(connection, session.sessionId)(option.id)) notifySpeedChanged();
|
|
5446
6827
|
}
|
|
5447
6828
|
}
|
|
5448
6829
|
}), "dsh-subscriptions: /fast contribution");
|