dsh-provider-qoder 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,6 +5,7 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
9
  let react_jsx_runtime = require("react/jsx-runtime");
9
10
  //#region src/dsh/credential-contract.ts
10
11
  /** Shared DSH host/browser contract for the one managed Qoder credential. */
@@ -14,6 +15,172 @@ window.__ModuleLoader__.load({
14
15
  return source === "env" || source?.endsWith("-env") === true;
15
16
  }
16
17
  //#endregion
18
+ //#region src/client/queue-status.ts
19
+ /** Match DSH's running status clock: whole hours, minutes and seconds. */
20
+ function formatQueueDuration(ms, t) {
21
+ const total = Math.max(0, Math.floor(ms / 1e3));
22
+ const hours = Math.floor(total / 3600);
23
+ const minutes = Math.floor(total / 60) % 60;
24
+ const seconds = total % 60;
25
+ return (hours > 0 ? `${hours}${t("queueHourUnit")}` : "") + (total >= 60 ? `${minutes}${t("queueMinuteUnit")}` : "") + `${seconds}${t("queueSecondUnit")}`;
26
+ }
27
+ function parseQueueSnapshots(value, sessionId) {
28
+ if (!Array.isArray(value) || value.length > 256) throw new Error("Invalid queue snapshots");
29
+ return value.map((item) => {
30
+ if (!item || typeof item !== "object" || item.sessionId !== sessionId || typeof item.callId !== "string" || !item.callId || typeof item.model !== "string" || item.modelName !== void 0 && (typeof item.modelName !== "string" || !item.modelName.trim()) || ![
31
+ "waitElapsedMs",
32
+ "maxWaitMs",
33
+ "updatedAt"
34
+ ].every((key) => typeof item[key] === "number" && Number.isFinite(item[key]) && item[key] >= 0) || !["queueCount", "waitTimeMs"].every((key) => item[key] === void 0 || typeof item[key] === "number" && Number.isFinite(item[key]) && item[key] >= 0)) throw new Error("Invalid queue snapshot");
35
+ return item;
36
+ });
37
+ }
38
+ function watchQueueStatus(rpc, sessionId, visibility, changed) {
39
+ let disposed = false;
40
+ let generation = 0;
41
+ let timer;
42
+ let active;
43
+ let items = [];
44
+ const poll = async () => {
45
+ if (disposed || visibility.hidden || active) return;
46
+ const revision = generation;
47
+ const controller = new AbortController();
48
+ active = controller;
49
+ const timeout = setTimeout(() => controller.abort(), 5e3);
50
+ try {
51
+ const result = await rpc.call("queue", { sessionId }, controller.signal);
52
+ if (disposed || revision !== generation) return;
53
+ if (!result.ok) throw new Error("Queue status unavailable");
54
+ items = parseQueueSnapshots(result.value, sessionId);
55
+ changed({
56
+ items,
57
+ unavailable: false
58
+ });
59
+ } catch {
60
+ if (!disposed && revision === generation) changed({
61
+ items,
62
+ unavailable: true
63
+ });
64
+ } finally {
65
+ clearTimeout(timeout);
66
+ if (active === controller) active = void 0;
67
+ if (!disposed && revision === generation && !visibility.hidden) timer = setTimeout(() => {
68
+ poll();
69
+ }, 1e3);
70
+ }
71
+ };
72
+ const stop = () => {
73
+ generation++;
74
+ clearTimeout(timer);
75
+ active?.abort();
76
+ active = void 0;
77
+ };
78
+ const onVisibility = () => {
79
+ stop();
80
+ if (!visibility.hidden) poll();
81
+ };
82
+ visibility.addEventListener("visibilitychange", onVisibility);
83
+ poll();
84
+ return () => {
85
+ disposed = true;
86
+ stop();
87
+ visibility.removeEventListener("visibilitychange", onVisibility);
88
+ };
89
+ }
90
+ //#endregion
91
+ //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderQueueStatus.module.css.mjs
92
+ const css$2 = "._8MdOdG_queues{box-sizing:border-box;width:calc(100% - 2 * var(--dsh-composer-side-clearance,16px) - 2 * var(--dsh-composer-dock-inset,8px));max-width:calc(var(--dsh-composer-card-max-width,952px) - 2 * var(--dsh-composer-dock-inset,8px));padding:0 var(--dsh-composer-dock-inset,8px);--dsw-alias-label-shimmer:var(--dsw-alias-label-deep-diving-shimmer);color:var(--dsw-alias-label-deep-diving,#4d6bfe);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(22px + var(--dsh-content-font-delta,0px));flex:none;margin:0 auto}._8MdOdG_panel{isolation:isolate;border-radius:var(--dsw-radius-lg,16px);width:100%;padding:2px 0;position:relative;overflow:hidden}._8MdOdG_panel:before{z-index:-1;border-radius:inherit;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-1,#242629));backdrop-filter:var(--dsw-menu-backdrop-filter,none);content:\"\";pointer-events:none;position:absolute;inset:0}._8MdOdG_panel:after{border:.5px solid var(--dsw-alias-border-l1,#42454a);border-radius:inherit;content:\"\";pointer-events:none;position:absolute;inset:0}._8MdOdG_row{min-width:0}._8MdOdG_row+._8MdOdG_row{box-shadow:inset 0 1px 0 var(--dsw-alias-border-l1,#42454a)}._8MdOdG_row[data-unavailable]{color:var(--dsw-alias-label-secondary)}._8MdOdG_summary{box-sizing:border-box;cursor:pointer;align-items:center;gap:10px;width:100%;min-width:0;min-height:36px;padding:7px 12px;list-style:none;display:flex}._8MdOdG_summary::-webkit-details-marker{display:none}._8MdOdG_summary:focus-visible{outline-offset:-2px;border-radius:var(--dsw-radius-md,8px);outline:2px solid}._8MdOdG_icon{flex:none;width:14px;height:14px}._8MdOdG_text{font-variant-numeric:tabular-nums;overflow-wrap:anywhere;flex:1;min-width:0}._8MdOdG_chevron{width:6px;height:6px;color:var(--dsw-alias-label-tertiary);border-bottom:1.5px solid;border-right:1.5px solid;flex:none;margin:0 3px 3px;transform:rotate(45deg)}._8MdOdG_row>details[open] ._8MdOdG_chevron{margin-bottom:-3px;transform:rotate(225deg)}._8MdOdG_details{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:2px 16px;padding:0 12px 10px 36px;display:flex}._8MdOdG_visuallyHidden{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;padding:0;position:absolute;overflow:hidden}";
93
+ const tagId$2 = "dsh-provider-qoder/QoderQueueStatus.module.css";
94
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
95
+ const tag = document.createElement("style");
96
+ tag.dataset.plugin = "dsh-provider-qoder";
97
+ tag.dataset.pluginCss = tagId$2;
98
+ tag.textContent = css$2;
99
+ document.head.appendChild(tag);
100
+ }
101
+ var QoderQueueStatus_module_css_default = {
102
+ "chevron": "_8MdOdG_chevron",
103
+ "details": "_8MdOdG_details",
104
+ "icon": "_8MdOdG_icon",
105
+ "panel": "_8MdOdG_panel",
106
+ "queues": "_8MdOdG_queues",
107
+ "row": "_8MdOdG_row",
108
+ "summary": "_8MdOdG_summary",
109
+ "text": "_8MdOdG_text",
110
+ "visuallyHidden": "_8MdOdG_visuallyHidden"
111
+ };
112
+ //#endregion
113
+ //#region src/client/QoderQueueStatus.tsx
114
+ function QueueRow({ item, unavailable, t }) {
115
+ const elapsed = Math.min(item.maxWaitMs, item.waitElapsedMs + Math.max(0, Date.now() - item.updatedAt));
116
+ const name = item.modelName ?? item.model;
117
+ const status = unavailable ? t("queueUnavailable") : t("queueWaiting", { model: name });
118
+ const label = unavailable ? status : t("queueRunningFor", {
119
+ model: name,
120
+ duration: formatQueueDuration(elapsed, t)
121
+ });
122
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
123
+ className: QoderQueueStatus_module_css_default.row,
124
+ "data-unavailable": unavailable || void 0,
125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
126
+ className: QoderQueueStatus_module_css_default.visuallyHidden,
127
+ role: "status",
128
+ "aria-live": "polite",
129
+ "aria-atomic": "true",
130
+ children: status
131
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
132
+ className: QoderQueueStatus_module_css_default.summary,
133
+ "aria-label": status,
134
+ children: [
135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutlineRegular, {
136
+ className: QoderQueueStatus_module_css_default.icon,
137
+ "aria-hidden": "true"
138
+ }),
139
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
140
+ active: !unavailable,
141
+ className: QoderQueueStatus_module_css_default.text,
142
+ "aria-hidden": "true",
143
+ children: label
144
+ }),
145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
146
+ className: QoderQueueStatus_module_css_default.chevron,
147
+ "aria-hidden": "true"
148
+ })
149
+ ]
150
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
151
+ className: QoderQueueStatus_module_css_default.details,
152
+ children: [
153
+ item.queueCount !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("queueCount", { count: item.queueCount }) }),
154
+ item.waitTimeMs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("queueEstimate", { seconds: Math.ceil(item.waitTimeMs / 1e3) }) }),
155
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("queueBudget", { seconds: Math.ceil(item.maxWaitMs / 1e3) }) }),
156
+ item.serviceAvailable === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("queueServiceUnavailable") })
157
+ ]
158
+ })] })]
159
+ });
160
+ }
161
+ /** A provider contribution in DSH's input dock, using its shared process-row primitives. */
162
+ function QoderQueueStatus({ sessionId, rpc, t }) {
163
+ const [view, setView] = (0, react.useState)();
164
+ (0, react.useEffect)(() => watchQueueStatus(rpc, sessionId, document, (state) => setView({
165
+ sessionId,
166
+ state
167
+ })), [rpc, sessionId]);
168
+ const state = view?.sessionId === sessionId ? view.state : void 0;
169
+ if (!state?.items.length) return null;
170
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
171
+ className: QoderQueueStatus_module_css_default.queues,
172
+ "aria-label": t("queueTitle"),
173
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
174
+ className: QoderQueueStatus_module_css_default.panel,
175
+ children: state.items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QueueRow, {
176
+ item,
177
+ unavailable: state.unavailable,
178
+ t
179
+ }, item.callId))
180
+ })
181
+ });
182
+ }
183
+ //#endregion
17
184
  //#region src/qoder/catalog.ts
18
185
  function positiveNumber(value) {
19
186
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
@@ -170,6 +337,61 @@ window.__ModuleLoader__.load({
170
337
  //#endregion
171
338
  //#region src/client/locales.ts
172
339
  const zh = {
340
+ accountConnectionTitle: "Qoder 账号",
341
+ signInDescription: "登录 Qoder,使用当前区域的订阅和模型。",
342
+ manageAccount: "管理账号",
343
+ manageCredential: "管理凭据",
344
+ changeAccount: "更换账号",
345
+ editPat: "编辑 PAT",
346
+ patActive: "正在使用 PAT",
347
+ saveAndUsePat: "保存并使用 PAT",
348
+ useSavedPat: "使用已保存的 PAT",
349
+ signInAgain: "重新登录",
350
+ reopenLogin: "重新打开官网",
351
+ loginLinkCopied: "链接已复制",
352
+ loginTimeRemaining: "链接剩余有效时间:{seconds} 秒",
353
+ regionChanged: "已切换服务区域;原区域如有未完成的登录,已取消。",
354
+ regionChangeFailed: "服务区域切换失败,请重试。",
355
+ browserRejected: "Qoder 未完成授权,请重新登录。",
356
+ browserStorageFailed: "官网授权已完成,但凭据保存失败。请检查存储后重新登录。",
357
+ authorizationConnectionFailed: "暂时无法连接或读取登录状态,请稍后重试。",
358
+ patSaveFailed: "PAT 保存或启用失败,请重试。",
359
+ browserRemoveFailed: "浏览器授权移除失败,请检查凭据存储是否可写后重试。",
360
+ browserLoginTitle: "Qoder 官网登录",
361
+ browserMethod: "浏览器授权",
362
+ savedBrowserAccount: "已保存的浏览器账号",
363
+ patMethod: "PAT 鉴权",
364
+ signedOut: "未连接",
365
+ sharedAuthorization: "当前区域的账号由所有连接此 DSH 实例的客户端共享。",
366
+ signInQoder: "通过 Qoder 登录",
367
+ signOutQoder: "移除浏览器授权",
368
+ usePat: "使用 PAT",
369
+ useBrowser: "使用已保存的浏览器授权",
370
+ cancelLogin: "取消登录",
371
+ openLogin: "打开登录页面",
372
+ copyLogin: "复制登录链接",
373
+ copyLoginFailed: "无法复制,请使用“打开登录页面”。",
374
+ browserWaiting: "等待在 Qoder 官网完成验证与授权…",
375
+ browserSaving: "正在保存授权…",
376
+ browserComplete: "已登录。",
377
+ browserCancelled: "登录已取消。",
378
+ browserTimeout: "登录链接已过期,请重新发起登录。",
379
+ browserFailed: "登录未完成,或授权保存失败。请检查账号状态后重试。",
380
+ browserExpired: "浏览器授权已失效,请重新登录。",
381
+ browserUnsaved: "续期凭据尚未保存,重启后可能需要重新登录;下次使用时将重试保存。",
382
+ browserOperationFailed: "无法更新登录状态,请重试。",
383
+ queueTitle: "Qoder 模型排队",
384
+ queueWaiting: "{model} 正在排队",
385
+ queueRunningFor: "{model} 正在排队,用时 {duration} ···",
386
+ queueHourUnit: "小时",
387
+ queueMinuteUnit: "分",
388
+ queueSecondUnit: "秒",
389
+ queueCount: "排队数量:{count}",
390
+ queueEstimate: "预计等待 {seconds} 秒",
391
+ queueElapsed: "已等待 {seconds} 秒",
392
+ queueUnavailable: "Qoder 排队状态暂不可用",
393
+ queueServiceUnavailable: "服务暂不可用,继续等待",
394
+ queueBudget: "等待上限 {seconds} 秒",
173
395
  nav: "Qoder",
174
396
  title: "Qoder 凭据",
175
397
  configured: "已配置",
@@ -183,7 +405,7 @@ window.__ModuleLoader__.load({
183
405
  regionGlobal: "国际版 (Global)",
184
406
  regionChina: "中国区 (CN)",
185
407
  regionHint: "请确保 PAT 与所选服务区域匹配。",
186
- tokenLabel: "API 密钥",
408
+ tokenLabel: "Personal Access Token (PAT)",
187
409
  tokenPlaceholder: "输入新的 Personal Access Token",
188
410
  configuredHint: "已配置——输入新值可替换",
189
411
  readOnly: "当前凭据存储不可写。",
@@ -218,11 +440,12 @@ window.__ModuleLoader__.load({
218
440
  accountTitle: "Qoder",
219
441
  accountLoading: "正在读取账号与额度…",
220
442
  accountFailed: "无法读取账号信息与额度。",
221
- accountCredentialHint: "请先在“模型”设置中配置 Qoder Personal Access Token。",
443
+ accountCredentialHint: "请先在“模型”设置中登录 Qoder 或选择已配置的 PAT。",
222
444
  refresh: "刷新",
223
445
  refreshing: "正在刷新…",
224
446
  userQuotaTitle: "个人额度",
225
447
  orgResourceTitle: "组织资源包",
448
+ addOnQuotaTitle: "增值额度",
226
449
  dedicatedResourceTitle: "专属资源包",
227
450
  dedicatedResourceExpiresAt: "有效期至:{value}",
228
451
  quotaRemaining: "剩余 {value}%",
@@ -241,6 +464,61 @@ window.__ModuleLoader__.load({
241
464
  searchModeHint: "智能路由模式下,使用 Qoder 模型时走 Qoder 搜索,使用其他模型时自动回退给备用搜索。"
242
465
  };
243
466
  const en = {
467
+ accountConnectionTitle: "Qoder account",
468
+ signInDescription: "Sign in to use your subscription and models for this region.",
469
+ manageAccount: "Manage account",
470
+ manageCredential: "Manage credentials",
471
+ changeAccount: "Change account",
472
+ editPat: "Edit PAT",
473
+ patActive: "Using PAT",
474
+ saveAndUsePat: "Save and use PAT",
475
+ useSavedPat: "Use saved PAT",
476
+ signInAgain: "Sign in again",
477
+ reopenLogin: "Reopen Qoder website",
478
+ loginLinkCopied: "Link copied",
479
+ loginTimeRemaining: "Link expires in {seconds} seconds",
480
+ regionChanged: "Region changed; any unfinished sign-in for the previous region was cancelled.",
481
+ regionChangeFailed: "Could not change region. Please retry.",
482
+ browserRejected: "Qoder authorization was not completed. Sign in again.",
483
+ browserStorageFailed: "Website authorization completed, but credentials could not be saved. Check storage and sign in again.",
484
+ authorizationConnectionFailed: "Could not connect or read sign-in status. Please retry shortly.",
485
+ patSaveFailed: "Could not save or activate the PAT. Please retry.",
486
+ browserRemoveFailed: "Could not remove browser authorization. Check that credential storage is writable and retry.",
487
+ browserLoginTitle: "Qoder website sign-in",
488
+ browserMethod: "Browser authorization",
489
+ savedBrowserAccount: "Saved browser account",
490
+ patMethod: "PAT authentication",
491
+ signedOut: "Disconnected",
492
+ sharedAuthorization: "The account for this region is shared by all clients connected to this DSH instance.",
493
+ signInQoder: "Sign in with Qoder",
494
+ signOutQoder: "Remove browser authorization",
495
+ usePat: "Use PAT",
496
+ useBrowser: "Use saved browser authorization",
497
+ cancelLogin: "Cancel sign-in",
498
+ openLogin: "Open sign-in page",
499
+ copyLogin: "Copy sign-in link",
500
+ copyLoginFailed: "Could not copy. Use Open sign-in page.",
501
+ browserWaiting: "Complete verification and authorization on the Qoder website…",
502
+ browserSaving: "Saving authorization…",
503
+ browserComplete: "Signed in.",
504
+ browserCancelled: "Sign-in cancelled.",
505
+ browserTimeout: "Sign-in link expired. Start a new sign-in.",
506
+ browserFailed: "Sign-in or saving authorization failed. Check account status and retry.",
507
+ browserExpired: "Browser authorization expired. Sign in again.",
508
+ browserUnsaved: "Renewed credentials have not been saved. Restarting may require sign-in; saving will be retried on next use.",
509
+ browserOperationFailed: "Could not update sign-in status. Please retry.",
510
+ queueTitle: "Qoder model queue",
511
+ queueWaiting: "{model} is queued",
512
+ queueRunningFor: "{model} queued for {duration} ···",
513
+ queueHourUnit: "h",
514
+ queueMinuteUnit: "m",
515
+ queueSecondUnit: "s",
516
+ queueCount: "Queue count: {count}",
517
+ queueEstimate: "Estimated wait: {seconds}s",
518
+ queueElapsed: "Waited {seconds}s",
519
+ queueUnavailable: "Qoder queue status is temporarily unavailable",
520
+ queueServiceUnavailable: "Service unavailable; waiting",
521
+ queueBudget: "Wait limit: {seconds}s",
244
522
  nav: "Qoder",
245
523
  title: "Qoder credential",
246
524
  configured: "Configured",
@@ -254,7 +532,7 @@ window.__ModuleLoader__.load({
254
532
  regionGlobal: "Global",
255
533
  regionChina: "China (CN)",
256
534
  regionHint: "Ensure your PAT matches the selected service region.",
257
- tokenLabel: "API Key",
535
+ tokenLabel: "Personal Access Token (PAT)",
258
536
  tokenPlaceholder: "Enter a new Personal Access Token",
259
537
  configuredHint: "Configured — enter a new value to replace",
260
538
  readOnly: "The current credential store is read-only.",
@@ -289,11 +567,12 @@ window.__ModuleLoader__.load({
289
567
  accountTitle: "Qoder",
290
568
  accountLoading: "Reading account and quota…",
291
569
  accountFailed: "Could not read account details or quota.",
292
- accountCredentialHint: "Configure the Qoder Personal Access Token in Models settings first.",
570
+ accountCredentialHint: "Sign in to Qoder or select a configured PAT in Models settings first.",
293
571
  refresh: "Refresh",
294
572
  refreshing: "Refreshing…",
295
573
  userQuotaTitle: "Personal Quota",
296
574
  orgResourceTitle: "Org Resource Package",
575
+ addOnQuotaTitle: "Add-on Credits",
297
576
  dedicatedResourceTitle: "Dedicated Resource Package",
298
577
  dedicatedResourceExpiresAt: "Expires at: {value}",
299
578
  quotaRemaining: "{value}% remaining",
@@ -362,8 +641,148 @@ window.__ModuleLoader__.load({
362
641
  }
363
642
  }
364
643
  //#endregion
644
+ //#region src/client/model-catalog-editor.ts
645
+ /** One mounted region owns its directory and asynchronous edits. */
646
+ var QoderModelCatalogEditor = class {
647
+ region;
648
+ operations;
649
+ active = true;
650
+ confirmed;
651
+ snapshot;
652
+ listeners = /* @__PURE__ */ new Set();
653
+ constructor(region, models, operations) {
654
+ this.region = region;
655
+ this.operations = operations;
656
+ this.confirmed = models.map(cloneCatalogModel);
657
+ this.snapshot = {
658
+ models: this.confirmed,
659
+ catalog: this.confirmed,
660
+ unavailableIds: /* @__PURE__ */ new Set()
661
+ };
662
+ }
663
+ getSnapshot = () => this.snapshot;
664
+ subscribe = (listener) => {
665
+ this.listeners.add(listener);
666
+ return () => {
667
+ this.listeners.delete(listener);
668
+ };
669
+ };
670
+ /** ConfigForms supplies accepted values, including its recovery read after refusal. */
671
+ replaceModels(models) {
672
+ const followsConfig = this.snapshot.catalog === this.confirmed;
673
+ this.confirmed = models.map(cloneCatalogModel);
674
+ const selected = new Map(this.confirmed.map((model) => [model.id, model]));
675
+ this.publish({
676
+ ...this.snapshot,
677
+ models: this.confirmed,
678
+ catalog: followsConfig ? this.confirmed : this.snapshot.catalog.map((model) => selected.get(model.id) ?? model)
679
+ });
680
+ }
681
+ activate() {
682
+ this.active = true;
683
+ }
684
+ dispose() {
685
+ this.active = false;
686
+ this.listeners.clear();
687
+ }
688
+ publish(snapshot) {
689
+ if (!this.active) return;
690
+ this.snapshot = snapshot;
691
+ for (const listener of this.listeners) listener();
692
+ }
693
+ async discover() {
694
+ if (!this.active || this.snapshot.pending) return;
695
+ this.publish({
696
+ ...this.snapshot,
697
+ pending: "discovering",
698
+ failure: void 0,
699
+ failureMessage: void 0
700
+ });
701
+ try {
702
+ const result = await this.operations.discoverModels();
703
+ if (!this.active) return;
704
+ if (!result.ok) {
705
+ this.publish({
706
+ ...this.snapshot,
707
+ pending: void 0,
708
+ failure: "modelsFetchFailed",
709
+ failureMessage: result.error.message
710
+ });
711
+ return;
712
+ }
713
+ const reconciled = reconcileQoderModels(this.snapshot.catalog, result.value);
714
+ await this.save(reconciled.selected, reconciled.catalog, reconciled.unavailableIds);
715
+ } catch (error) {
716
+ this.publish({
717
+ ...this.snapshot,
718
+ pending: void 0,
719
+ failure: "modelsFetchFailed",
720
+ failureMessage: error instanceof Error ? error.message : void 0
721
+ });
722
+ }
723
+ }
724
+ async toggle(id, enabled) {
725
+ if (!this.active || this.snapshot.pending || this.snapshot.unavailableIds.has(id)) return;
726
+ const selected = new Set(this.snapshot.models.map((model) => model.id));
727
+ if (enabled) selected.add(id);
728
+ else selected.delete(id);
729
+ await this.save(this.snapshot.catalog.filter((model) => selected.has(model.id) && !this.snapshot.unavailableIds.has(model.id)));
730
+ }
731
+ async selectTier(id, tierKey) {
732
+ if (!this.active || this.snapshot.pending || !this.snapshot.models.some((model) => model.id === id)) return;
733
+ const tokenCount = this.snapshot.catalog.find((model) => model.id === id)?.contextOptions?.[tierKey]?.tokenCount;
734
+ if (typeof tokenCount !== "number" || !Number.isFinite(tokenCount) || tokenCount <= 0) return;
735
+ const apply = (candidate) => candidate.id === id ? {
736
+ ...candidate,
737
+ contextTier: tierKey,
738
+ contextWindow: tokenCount
739
+ } : candidate;
740
+ await this.save(this.snapshot.models.map(apply), this.snapshot.catalog.map(apply));
741
+ }
742
+ async save(models, catalog = this.snapshot.catalog, unavailableIds = this.snapshot.unavailableIds) {
743
+ if (models.length === 0) {
744
+ this.publish({
745
+ ...this.snapshot,
746
+ pending: void 0,
747
+ failure: "modelsRequired",
748
+ failureMessage: void 0
749
+ });
750
+ return;
751
+ }
752
+ const submitted = models.map(cloneCatalogModel);
753
+ const confirmedAtSubmission = this.confirmed;
754
+ this.publish({
755
+ ...this.snapshot,
756
+ pending: "saving",
757
+ failure: void 0,
758
+ failureMessage: void 0
759
+ });
760
+ let saved = false;
761
+ try {
762
+ saved = await this.operations.storeModels(this.region, submitted);
763
+ } catch {}
764
+ if (!this.active) return;
765
+ if (saved) {
766
+ if (this.confirmed === confirmedAtSubmission) this.confirmed = submitted;
767
+ const accepted = new Map(this.confirmed.map((model) => [model.id, model]));
768
+ const directory = new Map(catalog.map((model) => [model.id, model]));
769
+ for (const [id, model] of accepted) directory.set(id, model);
770
+ this.publish({
771
+ models: this.confirmed,
772
+ catalog: [...directory.values()],
773
+ unavailableIds: new Set([...unavailableIds].filter((id) => !accepted.has(id)))
774
+ });
775
+ } else this.publish({
776
+ models: this.confirmed,
777
+ catalog: this.confirmed,
778
+ unavailableIds: /* @__PURE__ */ new Set(),
779
+ failure: "saveFailed"
780
+ });
781
+ }
782
+ };
783
+ //#endregion
365
784
  //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderCredentialCard.module.css.mjs
366
- const css$1 = ".SRqrhW_credential{color:var(--dsw-alias-label-primary);border-radius:10px;margin-top:12px}.SRqrhW_credential p{margin:8px 0 0}.SRqrhW_credentialEditor{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);flex-direction:column;gap:12px;padding:12px 14px;display:flex}.SRqrhW_credentialRowHead{align-items:center;gap:10px;display:flex}.SRqrhW_credentialIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.SRqrhW_credentialName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.SRqrhW_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.SRqrhW_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.SRqrhW_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.SRqrhW_regionBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:500;line-height:16px;display:inline-flex}.SRqrhW_regionSelector{flex-direction:column;gap:8px;margin-bottom:14px;display:flex}.SRqrhW_regionGroup{gap:8px;display:flex}.SRqrhW_regionOption{border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);user-select:none;border-radius:8px;align-items:center;gap:6px;padding:6px 12px;font-size:12px;line-height:18px;transition:all .12s;display:inline-flex}.SRqrhW_regionOption:hover:not(.SRqrhW_regionOptionDisabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.SRqrhW_credential button.SRqrhW_regionOptionActive{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-selected);color:var(--dsw-alias-brand-primary);font-weight:500}.SRqrhW_regionOptionDisabled{cursor:not-allowed;opacity:.5}.SRqrhW_editButton{box-sizing:border-box;font:inherit;background:0 0;margin-left:auto;font-size:12px;line-height:18px;border:1px solid var(--dsw-alias-border-l2)!important;border-radius:14px!important;height:28px!important;padding:0 10px!important}.SRqrhW_editButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.SRqrhW_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;padding:14px 16px}.SRqrhW_customized{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);margin-top:12px;transition:all .12s;overflow:hidden}.SRqrhW_customizedSummary{box-sizing:border-box;border-radius:var(--dsw-radius-xl,12px);cursor:pointer;width:100%;color:var(--dsw-alias-label-primary);user-select:none;align-items:center;gap:8px;margin:0;padding:12px 14px;font-size:13px;font-weight:600;line-height:18px;list-style:none;transition:background .12s;display:flex}.SRqrhW_customizedSummary:hover{background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelSummaryBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:400;line-height:16px;display:inline-flex}.SRqrhW_customizedSummary::-webkit-details-marker{display:none}.SRqrhW_customizedSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.SRqrhW_customized[open]>.SRqrhW_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.SRqrhW_customized[open]>.SRqrhW_customizedSummary{border-bottom:.5px solid var(--dsw-alias-border-l2);border-bottom-right-radius:0;border-bottom-left-radius:0}.SRqrhW_customizedBody{padding:12px 14px 14px}.SRqrhW_modelCatalog{flex-direction:column;gap:10px;display:flex}.SRqrhW_modelList{flex-direction:column;gap:10px;max-height:360px;padding-right:4px;display:flex;overflow-y:auto}.SRqrhW_modelList::-webkit-scrollbar{width:4px}.SRqrhW_modelList::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);border-radius:2px}.SRqrhW_modelCatalogHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.SRqrhW_modelCatalogHeadCompact{justify-content:flex-end;align-items:center;gap:12px;display:flex}.SRqrhW_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.SRqrhW_modelCatalogMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:2px 0 0!important}.SRqrhW_linkButton{box-sizing:border-box;font:inherit;background:0 0;font-size:12px;line-height:18px;border:none!important;border-radius:14px!important;height:28px!important;padding:0 10px!important}.SRqrhW_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelChoice{border-radius:6px;flex-direction:column;gap:4px;padding:6px 8px;font-size:13px;display:flex}.SRqrhW_modelChoice:hover{background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelChoiceHead{cursor:pointer;grid-template-columns:auto minmax(0,1fr) auto auto auto;align-items:center;gap:8px;display:grid}.SRqrhW_modelTier{align-items:center;gap:8px;padding-left:24px;display:flex}.SRqrhW_modelTierLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.SRqrhW_modelTierSelect{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);height:24px;color:inherit;font:inherit;cursor:pointer;border:0;border-radius:6px;padding:0 6px;font-size:12px}.SRqrhW_modelTierHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px;margin:0!important}.SRqrhW_modelChoiceName{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.SRqrhW_modelChoice code{color:var(--dsw-alias-label-tertiary);font-size:11px}.SRqrhW_modelRate{min-width:34px;color:var(--dsw-alias-brand-primary);text-align:right;font-size:12px;font-weight:600}.SRqrhW_modelUnavailable{cursor:not-allowed;opacity:.55}.SRqrhW_modelBadge{color:var(--dsw-alias-state-error-primary);font-size:11px}@media (prefers-reduced-motion:reduce){.SRqrhW_customizedSummary:before{transition:none}}.SRqrhW_head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:20px;display:flex}.SRqrhW_heading{margin:0;font-size:18px;font-weight:600}.SRqrhW_status{color:var(--dsw-alias-label-secondary);font-size:12px}.SRqrhW_configured,.SRqrhW_success{color:var(--dsw-alias-state-success-primary)}.SRqrhW_label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;display:block}.SRqrhW_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;height:32px;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;margin-top:6px;padding:0 10px}.SRqrhW_input:focus{border-color:var(--dsw-alias-brand-primary)}.SRqrhW_input::placeholder{color:var(--dsw-alias-label-dimmed)}.SRqrhW_input:disabled{cursor:not-allowed;opacity:.4}.SRqrhW_hint{color:var(--dsw-alias-label-secondary);font-size:12px}.SRqrhW_actions{justify-content:flex-end;gap:8px;margin-top:14px;display:flex}.SRqrhW_credential button{cursor:pointer;height:32px;color:var(--dsw-alias-label-primary);border:1px solid #0000;border-radius:16px;padding:0 12px}.SRqrhW_credential button:disabled{cursor:not-allowed;opacity:.4}.SRqrhW_primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)!important}.SRqrhW_primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.SRqrhW_secondary{background:0 0;border-color:var(--dsw-alias-border-l2)!important}.SRqrhW_secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_danger{background:0 0;border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.SRqrhW_danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.SRqrhW_error{color:var(--dsw-alias-state-error-primary);font-size:12px}.SRqrhW_success{font-size:12px}.SRqrhW_accountSection{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:10px;margin-top:12px;padding:12px 14px;display:flex}.SRqrhW_accountHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.SRqrhW_userInfo{align-items:center;gap:10px;min-width:0;display:flex}.SRqrhW_avatar{background:var(--dsw-alias-brand-primary);width:32px;height:32px;color:var(--dsw-alias-label-primary-foreground);text-transform:uppercase;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;font-size:14px;font-weight:600;display:flex}.SRqrhW_userDetails{flex-direction:column;gap:2px;min-width:0;display:flex}.SRqrhW_userName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;overflow:hidden}.SRqrhW_userEmail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.SRqrhW_refreshBtn{border:1px solid var(--dsw-alias-border-l2)!important;height:26px!important;color:var(--dsw-alias-label-secondary)!important;background:0 0!important;border-radius:13px!important;padding:0 8px!important;font-size:12px!important}.SRqrhW_refreshBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)!important}.SRqrhW_quotaBlock{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:6px;padding-top:8px;display:flex}.SRqrhW_quotaHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.SRqrhW_quotaLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.SRqrhW_quotaTop{justify-content:space-between;align-items:baseline;gap:8px;font-size:12px;display:flex}.SRqrhW_quotaTop strong{color:var(--dsw-alias-brand-primary);font-size:13px}.SRqrhW_quotaDescription{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0!important}.SRqrhW_quotaProgress{background:var(--dsw-alias-border-l3);width:100%;height:6px;accent-color:var(--dsw-alias-brand-primary);-webkit-appearance:none;appearance:none;border:0;border-radius:999px;overflow:hidden}.SRqrhW_quotaProgress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.SRqrhW_quotaProgress::-webkit-progress-value{background:var(--dsw-alias-brand-primary);border-radius:999px}.SRqrhW_quotaProgress::-moz-progress-bar{background:var(--dsw-alias-brand-primary);border-radius:999px}.SRqrhW_quotaMeta{color:var(--dsw-alias-label-tertiary);justify-content:space-between;align-items:center;gap:8px;font-size:11px;display:flex}.SRqrhW_accountLoadingText,.SRqrhW_accountEmptyText{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}.SRqrhW_userNameRow{align-items:center;gap:8px;display:flex}.SRqrhW_planBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-brand-primary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:600;line-height:16px;display:inline-flex}.SRqrhW_orgTag{color:var(--dsw-alias-label-tertiary);font-size:11px}.SRqrhW_suspendedBanner{background:var(--dsw-alias-state-error-subtle,#ff4d4f1a);color:var(--dsw-alias-state-error-primary);border-radius:6px;align-items:center;gap:6px;margin-bottom:8px;padding:6px 10px;font-size:12px;display:flex}.SRqrhW_searchModeSection{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:10px;margin-top:12px;padding:12px 14px;display:flex}.SRqrhW_searchModeHintText{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:12px;line-height:18px}";
785
+ const css$1 = ".SRqrhW_credential{color:var(--dsw-alias-label-primary);border-radius:10px;margin-top:12px}.SRqrhW_connectionBody{flex-direction:column;gap:10px;display:flex}.SRqrhW_connectionHead,.SRqrhW_subscriberRow{justify-content:space-between;align-items:center;gap:16px;display:flex}.SRqrhW_connectionHead{flex-wrap:wrap}.SRqrhW_subscriberRow{padding:10px 0}.SRqrhW_subscriberAvatar{width:42px;height:42px;color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-selected);border-radius:50%;flex:none;place-items:center;font-size:18px;font-weight:600;display:grid}.SRqrhW_subscriberIdentity{overflow-wrap:anywhere;flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.SRqrhW_subscriberIdentity>span{color:var(--dsw-alias-label-secondary);font-size:12px}.SRqrhW_connectionActions{flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px;display:flex}.SRqrhW_loginProgress{background:var(--dsw-alias-interactive-bg-hover);border-radius:10px;flex-direction:column;gap:8px;padding:14px;display:flex}.SRqrhW_managementPanel{background:var(--dsw-alias-bg-module-platform);border-radius:10px;flex-wrap:wrap;align-items:center;gap:10px;padding:12px;display:flex}.SRqrhW_credential .SRqrhW_textButton{color:var(--dsw-alias-brand-primary);background:0 0;border:0!important;padding:4px 2px!important}.SRqrhW_primaryLink{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-radius:18px;padding:8px 14px;font-size:12px;text-decoration:none;display:inline-flex}.SRqrhW_credential p.SRqrhW_sharedNote{border-top:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);margin-top:6px;padding-top:12px;font-size:12px}@media (width<=480px){.SRqrhW_subscriberRow{flex-wrap:wrap}.SRqrhW_subscriberIdentity{flex-basis:calc(100% - 60px)}}.SRqrhW_credential p{margin:8px 0 0}.SRqrhW_credentialEditor{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);flex-direction:column;gap:12px;padding:12px 14px;display:flex}.SRqrhW_credentialRowHead{align-items:center;gap:10px;display:flex}.SRqrhW_credentialIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.SRqrhW_credentialName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.SRqrhW_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.SRqrhW_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.SRqrhW_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.SRqrhW_regionBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:500;line-height:16px;display:inline-flex}.SRqrhW_regionSelector{flex-direction:column;gap:8px;margin-bottom:14px;display:flex}.SRqrhW_regionGroup{gap:8px;display:flex}.SRqrhW_regionOption{border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);user-select:none;border-radius:8px;align-items:center;gap:6px;padding:6px 12px;font-size:12px;line-height:18px;transition:all .12s;display:inline-flex}.SRqrhW_regionOption:hover:not(.SRqrhW_regionOptionDisabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.SRqrhW_credential button.SRqrhW_regionOptionActive{border-color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-selected);color:var(--dsw-alias-brand-primary);font-weight:500}.SRqrhW_regionOptionDisabled{cursor:not-allowed;opacity:.5}.SRqrhW_editButton{box-sizing:border-box;font:inherit;background:0 0;margin-left:auto;font-size:12px;line-height:18px;border:1px solid var(--dsw-alias-border-l2)!important;border-radius:14px!important;height:28px!important;padding:0 10px!important}.SRqrhW_editButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.SRqrhW_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;padding:14px 16px}.SRqrhW_customized{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);margin-top:12px;transition:all .12s;overflow:hidden}.SRqrhW_customizedSummary{box-sizing:border-box;border-radius:var(--dsw-radius-xl,12px);cursor:pointer;width:100%;color:var(--dsw-alias-label-primary);user-select:none;align-items:center;gap:8px;margin:0;padding:12px 14px;font-size:13px;font-weight:600;line-height:18px;list-style:none;transition:background .12s;display:flex}.SRqrhW_customizedSummary:hover{background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelSummaryBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:400;line-height:16px;display:inline-flex}.SRqrhW_customizedSummary::-webkit-details-marker{display:none}.SRqrhW_customizedSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.SRqrhW_customized[open]>.SRqrhW_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.SRqrhW_customized[open]>.SRqrhW_customizedSummary{border-bottom:.5px solid var(--dsw-alias-border-l2);border-bottom-right-radius:0;border-bottom-left-radius:0}.SRqrhW_customizedBody{padding:12px 14px 14px}.SRqrhW_modelCatalog{flex-direction:column;gap:10px;display:flex}.SRqrhW_modelList{flex-direction:column;gap:10px;max-height:360px;padding-right:4px;display:flex;overflow-y:auto}.SRqrhW_modelList::-webkit-scrollbar{width:4px}.SRqrhW_modelList::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);border-radius:2px}.SRqrhW_modelCatalogHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.SRqrhW_modelCatalogHeadCompact{justify-content:flex-end;align-items:center;gap:12px;display:flex}.SRqrhW_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.SRqrhW_modelCatalogMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:2px 0 0!important}.SRqrhW_linkButton{box-sizing:border-box;font:inherit;background:0 0;font-size:12px;line-height:18px;border:none!important;border-radius:14px!important;height:28px!important;padding:0 10px!important}.SRqrhW_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelChoice{border-radius:6px;flex-direction:column;gap:4px;padding:6px 8px;font-size:13px;display:flex}.SRqrhW_modelChoice:hover{background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_modelChoiceHead{cursor:pointer;grid-template-columns:auto minmax(0,1fr) auto auto auto;align-items:center;gap:8px;display:grid}.SRqrhW_modelTier{align-items:center;gap:8px;padding-left:24px;display:flex}.SRqrhW_modelTierLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.SRqrhW_modelTierSelect{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);height:24px;color:inherit;font:inherit;cursor:pointer;border:0;border-radius:6px;padding:0 6px;font-size:12px}.SRqrhW_modelTierHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px;margin:0!important}.SRqrhW_modelChoiceName{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.SRqrhW_modelChoice code{color:var(--dsw-alias-label-tertiary);font-size:11px}.SRqrhW_modelRate{min-width:34px;color:var(--dsw-alias-brand-primary);text-align:right;font-size:12px;font-weight:600}.SRqrhW_modelUnavailable{cursor:not-allowed;opacity:.55}.SRqrhW_modelBadge{color:var(--dsw-alias-state-error-primary);font-size:11px}@media (prefers-reduced-motion:reduce){.SRqrhW_customizedSummary:before{transition:none}}.SRqrhW_head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:20px;display:flex}.SRqrhW_heading{margin:0;font-size:18px;font-weight:600}.SRqrhW_status{color:var(--dsw-alias-label-secondary);font-size:12px}.SRqrhW_configured,.SRqrhW_success{color:var(--dsw-alias-state-success-primary)}.SRqrhW_label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;display:block}.SRqrhW_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;height:32px;color:var(--dsw-alias-label-primary);border-radius:8px;outline:none;margin-top:6px;padding:0 10px}.SRqrhW_input:focus{border-color:var(--dsw-alias-brand-primary)}.SRqrhW_input::placeholder{color:var(--dsw-alias-label-dimmed)}.SRqrhW_input:disabled{cursor:not-allowed;opacity:.4}.SRqrhW_hint{color:var(--dsw-alias-label-secondary);font-size:12px}.SRqrhW_actions{justify-content:flex-end;gap:8px;margin-top:14px;display:flex}.SRqrhW_credential button{cursor:pointer;height:32px;color:var(--dsw-alias-label-primary);border:1px solid #0000;border-radius:16px;padding:0 12px}.SRqrhW_credential button:disabled{cursor:not-allowed;opacity:.4}.SRqrhW_primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)!important}.SRqrhW_primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.SRqrhW_secondary{background:0 0;border-color:var(--dsw-alias-border-l2)!important}.SRqrhW_secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.SRqrhW_danger{background:0 0;border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.SRqrhW_danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.SRqrhW_error{color:var(--dsw-alias-state-error-primary);font-size:12px}.SRqrhW_success{font-size:12px}.SRqrhW_accountSection{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:10px;margin-top:12px;padding:12px 14px;display:flex}.SRqrhW_accountHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.SRqrhW_userInfo{align-items:center;gap:10px;min-width:0;display:flex}.SRqrhW_avatar{background:var(--dsw-alias-brand-primary);width:32px;height:32px;color:var(--dsw-alias-label-primary-foreground);text-transform:uppercase;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;font-size:14px;font-weight:600;display:flex}.SRqrhW_userDetails{flex-direction:column;gap:2px;min-width:0;display:flex}.SRqrhW_userName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;overflow:hidden}.SRqrhW_userEmail{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.SRqrhW_refreshBtn{border:1px solid var(--dsw-alias-border-l2)!important;height:26px!important;color:var(--dsw-alias-label-secondary)!important;background:0 0!important;border-radius:13px!important;padding:0 8px!important;font-size:12px!important}.SRqrhW_refreshBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)!important}.SRqrhW_quotaBlock{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:6px;padding-top:8px;display:flex}.SRqrhW_quotaHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.SRqrhW_quotaLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.SRqrhW_quotaTop{justify-content:space-between;align-items:baseline;gap:8px;font-size:12px;display:flex}.SRqrhW_quotaTop strong{color:var(--dsw-alias-brand-primary);font-size:13px}.SRqrhW_quotaDescription{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0!important}.SRqrhW_quotaProgress{background:var(--dsw-alias-border-l3);width:100%;height:6px;accent-color:var(--dsw-alias-brand-primary);-webkit-appearance:none;appearance:none;border:0;border-radius:999px;overflow:hidden}.SRqrhW_quotaProgress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.SRqrhW_quotaProgress::-webkit-progress-value{background:var(--dsw-alias-brand-primary);border-radius:999px}.SRqrhW_quotaProgress::-moz-progress-bar{background:var(--dsw-alias-brand-primary);border-radius:999px}.SRqrhW_quotaMeta{color:var(--dsw-alias-label-tertiary);justify-content:space-between;align-items:center;gap:8px;font-size:11px;display:flex}.SRqrhW_accountLoadingText,.SRqrhW_accountEmptyText{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}.SRqrhW_userNameRow{align-items:center;gap:8px;display:flex}.SRqrhW_planBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-brand-primary);border-radius:4px;align-items:center;padding:1px 6px;font-size:11px;font-weight:600;line-height:16px;display:inline-flex}.SRqrhW_orgTag{color:var(--dsw-alias-label-tertiary);font-size:11px}.SRqrhW_suspendedBanner{background:var(--dsw-alias-state-error-subtle,#ff4d4f1a);color:var(--dsw-alias-state-error-primary);border-radius:6px;align-items:center;gap:6px;margin-bottom:8px;padding:6px 10px;font-size:12px;display:flex}.SRqrhW_searchModeSection{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-xl,12px);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:10px;margin-top:12px;padding:12px 14px;display:flex}.SRqrhW_searchModeHintText{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:12px;line-height:18px}";
367
786
  const tagId$1 = "dsh-provider-qoder/QoderCredentialCard.module.css";
368
787
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
369
788
  const tag = document.createElement("style");
@@ -380,6 +799,9 @@ window.__ModuleLoader__.load({
380
799
  "actions": "SRqrhW_actions",
381
800
  "avatar": "SRqrhW_avatar",
382
801
  "configured": "SRqrhW_configured",
802
+ "connectionActions": "SRqrhW_connectionActions",
803
+ "connectionBody": "SRqrhW_connectionBody",
804
+ "connectionHead": "SRqrhW_connectionHead",
383
805
  "credential": "SRqrhW_credential",
384
806
  "credentialDot": "SRqrhW_credentialDot",
385
807
  "credentialDotConfigured": "SRqrhW_credentialDotConfigured",
@@ -401,6 +823,8 @@ window.__ModuleLoader__.load({
401
823
  "input": "SRqrhW_input",
402
824
  "label": "SRqrhW_label",
403
825
  "linkButton": "SRqrhW_linkButton",
826
+ "loginProgress": "SRqrhW_loginProgress",
827
+ "managementPanel": "SRqrhW_managementPanel",
404
828
  "modelBadge": "SRqrhW_modelBadge",
405
829
  "modelCatalog": "SRqrhW_modelCatalog",
406
830
  "modelCatalogHead": "SRqrhW_modelCatalogHead",
@@ -421,6 +845,7 @@ window.__ModuleLoader__.load({
421
845
  "orgTag": "SRqrhW_orgTag",
422
846
  "planBadge": "SRqrhW_planBadge",
423
847
  "primary": "SRqrhW_primary",
848
+ "primaryLink": "SRqrhW_primaryLink",
424
849
  "quotaBlock": "SRqrhW_quotaBlock",
425
850
  "quotaDescription": "SRqrhW_quotaDescription",
426
851
  "quotaHeader": "SRqrhW_quotaHeader",
@@ -438,9 +863,14 @@ window.__ModuleLoader__.load({
438
863
  "searchModeHintText": "SRqrhW_searchModeHintText",
439
864
  "searchModeSection": "SRqrhW_searchModeSection",
440
865
  "secondary": "SRqrhW_secondary",
866
+ "sharedNote": "SRqrhW_sharedNote",
441
867
  "status": "SRqrhW_status",
868
+ "subscriberAvatar": "SRqrhW_subscriberAvatar",
869
+ "subscriberIdentity": "SRqrhW_subscriberIdentity",
870
+ "subscriberRow": "SRqrhW_subscriberRow",
442
871
  "success": "SRqrhW_success",
443
872
  "suspendedBanner": "SRqrhW_suspendedBanner",
873
+ "textButton": "SRqrhW_textButton",
444
874
  "userDetails": "SRqrhW_userDetails",
445
875
  "userEmail": "SRqrhW_userEmail",
446
876
  "userInfo": "SRqrhW_userInfo",
@@ -449,59 +879,28 @@ window.__ModuleLoader__.load({
449
879
  };
450
880
  //#endregion
451
881
  //#region src/client/QoderModelCatalog.tsx
452
- function validateModelCatalog(models) {
453
- return models.length === 0 ? "modelsRequired" : void 0;
454
- }
882
+ /** The key resets directory state whenever the active Qoder service region changes. */
455
883
  function QoderModelCatalog(props) {
456
- const { operations, models, disabled, fetchDisabled, hideTitle, onChange, t } = props;
457
- const [fetching, setFetching] = (0, react.useState)(false);
458
- const [failure, setFailure] = (0, react.useState)();
459
- const [catalog, setCatalog] = (0, react.useState)();
460
- const [unavailableIds, setUnavailableIds] = (0, react.useState)(/* @__PURE__ */ new Set());
461
- const displayedModels = catalog ?? models;
462
- const selectedIds = (0, react.useMemo)(() => new Set(models.map((model) => model.id)), [models]);
463
- const fetchModels = async () => {
464
- setFetching(true);
465
- setFailure(void 0);
466
- const result = await operations.discoverModels();
467
- setFetching(false);
468
- if (!result.ok) {
469
- setFailure(result.error.message || t("modelsFetchFailed"));
470
- return;
471
- }
472
- const reconciled = reconcileQoderModels(catalog ?? models, result.value);
473
- setCatalog(reconciled.catalog);
474
- setUnavailableIds(reconciled.unavailableIds);
475
- onChange(reconciled.selected);
476
- };
477
- const toggleModel = (id, enabled) => {
478
- if (unavailableIds.has(id)) return;
479
- const source = catalog ?? models;
480
- if (catalog === void 0) setCatalog(source);
481
- const nextSelected = new Set(selectedIds);
482
- if (enabled) nextSelected.add(id);
483
- else nextSelected.delete(id);
484
- onChange(source.filter((model) => nextSelected.has(model.id) && !unavailableIds.has(model.id)));
485
- };
486
- const changeContextTier = (id, tierKey) => {
487
- const source = catalog ?? models;
488
- if (catalog === void 0) setCatalog(source);
489
- const apply = (model) => {
490
- if (model.id !== id) return model;
491
- const tokenCount = model.contextOptions?.[tierKey]?.tokenCount;
492
- if (typeof tokenCount !== "number" || !Number.isFinite(tokenCount) || tokenCount <= 0) return model;
493
- return {
494
- ...model,
495
- contextTier: tierKey,
496
- contextWindow: tokenCount
497
- };
498
- };
499
- const nextCatalog = source.map(apply);
500
- setCatalog(nextCatalog);
501
- onChange(models.map(apply));
502
- };
884
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegionModelCatalog, { ...props }, props.region);
885
+ }
886
+ function RegionModelCatalog({ operations, models, region, disabled, fetchDisabled, hideTitle, t }) {
887
+ const [editor] = (0, react.useState)(() => new QoderModelCatalogEditor(region, models, operations));
888
+ const snapshot = (0, react.useSyncExternalStore)(editor.subscribe, editor.getSnapshot, editor.getSnapshot);
889
+ (0, react.useLayoutEffect)(() => {
890
+ editor.activate();
891
+ return () => editor.dispose();
892
+ }, [editor]);
893
+ (0, react.useLayoutEffect)(() => {
894
+ editor.replaceModels(models);
895
+ }, [editor, models]);
896
+ const displayedModels = snapshot.catalog;
897
+ const unavailableIds = snapshot.unavailableIds;
898
+ const selectedIds = new Set(snapshot.models.map((model) => model.id));
899
+ const busy = snapshot.pending !== void 0;
900
+ const fetching = snapshot.pending === "discovering";
901
+ const failure = snapshot.failureMessage || (snapshot.failure ? t(snapshot.failure) : void 0);
503
902
  const renderRate = (model) => {
504
- if (model.isFree || model.priceFactor === 0) return t("modelRateFree");
903
+ if (model.priceFactor === 0 || model.priceFactor === void 0 && model.isFree) return t("modelRateFree");
505
904
  if (model.priceFactor === void 0) return t("modelRateUnknown");
506
905
  const current = formatRateFactor(model.priceFactor);
507
906
  if (model.originalPriceFactor !== void 0 && model.originalPriceFactor > model.priceFactor) return t("modelRateDiscount", {
@@ -522,13 +921,13 @@ window.__ModuleLoader__.load({
522
921
  children: t("modelsTitle")
523
922
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
524
923
  className: QoderCredentialCard_module_css_default.modelCatalogMeta,
525
- children: t("modelsEnabled", { count: models.length })
924
+ children: t("modelsEnabled", { count: snapshot.models.length })
526
925
  })] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
527
926
  type: "button",
528
927
  className: QoderCredentialCard_module_css_default.linkButton,
529
- disabled: disabled || fetching || Boolean(fetchDisabled),
928
+ disabled: disabled || busy || Boolean(fetchDisabled),
530
929
  onClick: () => {
531
- fetchModels();
930
+ if (!disabled && !fetchDisabled) editor.discover();
532
931
  },
533
932
  children: fetching ? t("modelsFetching") : t("modelsFetch")
534
933
  })]
@@ -553,9 +952,9 @@ window.__ModuleLoader__.load({
553
952
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
554
953
  type: "checkbox",
555
954
  checked: !unavailable && selectedIds.has(model.id),
556
- disabled: disabled || unavailable,
955
+ disabled: disabled || busy || unavailable,
557
956
  onChange: (event) => {
558
- toggleModel(model.id, event.currentTarget.checked);
957
+ if (!disabled) editor.toggle(model.id, event.currentTarget.checked);
559
958
  }
560
959
  }),
561
960
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -580,9 +979,9 @@ window.__ModuleLoader__.load({
580
979
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
581
980
  className: QoderCredentialCard_module_css_default.modelTierSelect,
582
981
  value: selectedTier ?? "",
583
- disabled: disabled || unavailable || !selectedIds.has(model.id),
982
+ disabled: disabled || busy || unavailable || !selectedIds.has(model.id),
584
983
  onChange: (event) => {
585
- changeContextTier(model.id, event.currentTarget.value);
984
+ if (!disabled) editor.selectTier(model.id, event.currentTarget.value);
586
985
  },
587
986
  children: tiers.map((tier) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
588
987
  value: tier.key,
@@ -615,18 +1014,31 @@ window.__ModuleLoader__.load({
615
1014
  }
616
1015
  /**
617
1016
  * Settings card for the Qoder subscription: credential state, subscriber
618
- * profile, and every quota block the provider reports.
1017
+ * profile, and every quota block the provider reports. Region changes remount
1018
+ * the account state before any old profile can be displayed.
619
1019
  */
620
- function QoderAccountCard({ operations, t, activeLocale }) {
1020
+ function QoderAccountCard(props) {
1021
+ const modelSnapshot = (0, react.useSyncExternalStore)(props.operations.subscribeModels, props.operations.getModelSnapshot, props.operations.getModelSnapshot);
1022
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegionAccountCard, {
1023
+ ...props,
1024
+ modelSnapshot
1025
+ }, regionOf(modelSnapshot.value));
1026
+ }
1027
+ function RegionAccountCard({ operations, t, activeLocale, modelSnapshot }) {
621
1028
  const [credentialState, setCredentialState] = (0, react.useState)({ status: "loading" });
622
1029
  const [accountState, setAccountState] = (0, react.useState)({ status: "idle" });
623
1030
  const [refreshing, setRefreshing] = (0, react.useState)(false);
624
1031
  const [credentialRevision, setCredentialRevision] = (0, react.useState)(0);
625
- const [modelSaveError, setModelSaveError] = (0, react.useState)();
626
1032
  const latestAccountRequest = (0, react.useRef)(0);
627
- const modelSnapshot = (0, react.useSyncExternalStore)(operations.subscribeModels, operations.getModelSnapshot, operations.getModelSnapshot);
1033
+ const latestCredentialRequest = (0, react.useRef)(0);
1034
+ (0, react.useLayoutEffect)(() => () => {
1035
+ latestAccountRequest.current++;
1036
+ latestCredentialRequest.current++;
1037
+ }, []);
628
1038
  const loadCredential = (0, react.useCallback)(async () => {
1039
+ const requestId = ++latestCredentialRequest.current;
629
1040
  const info = await operations.describe();
1041
+ if (requestId !== latestCredentialRequest.current) return;
630
1042
  setCredentialState(info === void 0 ? { status: "failed" } : {
631
1043
  status: "ready",
632
1044
  info
@@ -745,7 +1157,7 @@ window.__ModuleLoader__.load({
745
1157
  const renderAccount = () => {
746
1158
  if (!configured) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
747
1159
  className: QoderCredentialCard_module_css_default.accountSection,
748
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1160
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
749
1161
  className: QoderCredentialCard_module_css_default.accountEmptyText,
750
1162
  children: t("accountCredentialHint")
751
1163
  })
@@ -781,13 +1193,14 @@ window.__ModuleLoader__.load({
781
1193
  const { profile, usage, plan } = accountState.account;
782
1194
  const userQuota = usage?.userQuota;
783
1195
  const orgPackage = usage?.orgResourcePackage;
1196
+ const addOnQuota = usage?.addOnQuota;
784
1197
  const dedicatedPackages = usage?.dedicatedResourcePackages ?? [];
785
1198
  const locale = activeLocale();
786
1199
  const avatarInitial = (profile.name || profile.email || "Q").charAt(0).toUpperCase();
787
1200
  const resetDate = formatResetDate(usage?.expiresAt);
788
1201
  const planExpiry = plan?.endDate ? formatResetDate(plan.endDate) : void 0;
789
1202
  const isSuspended = plan?.organization?.isSuspended === true;
790
- const hasQuota = (userQuota?.total ?? 0) > 0 || (orgPackage?.total ?? 0) > 0 || dedicatedPackages.some((pkg) => pkg.total > 0);
1203
+ const hasQuota = (userQuota?.total ?? 0) > 0 || (orgPackage?.total ?? 0) > 0 || (addOnQuota?.total ?? 0) > 0 || dedicatedPackages.some((pkg) => pkg.total > 0);
791
1204
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
792
1205
  className: QoderCredentialCard_module_css_default.accountSection,
793
1206
  children: [
@@ -844,6 +1257,7 @@ window.__ModuleLoader__.load({
844
1257
  }) : null,
845
1258
  renderQuota("userQuota", t("userQuotaTitle"), userQuota, { resetDate }),
846
1259
  renderQuota("orgResource", t("orgResourceTitle"), orgPackage, { resetDate }),
1260
+ renderQuota("addOnQuota", t("addOnQuotaTitle"), addOnQuota),
847
1261
  dedicatedPackages.map((pkg, index) => renderQuota(`dedicatedResource-${pkg.id ?? index}`, resolveLocalizedText(pkg.title, locale) ?? t("dedicatedResourceTitle"), pkg, {
848
1262
  expiresAt: formatResetDate(pkg.expiresAt),
849
1263
  description: resolveLocalizedText(pkg.description, locale)
@@ -922,16 +1336,6 @@ window.__ModuleLoader__.load({
922
1336
  const currentRegion = regionOf(modelSnapshot.value);
923
1337
  const currentModels = modelsOf(modelSnapshot.value, currentRegion);
924
1338
  const canModifyModels = modelSnapshot.status === "ready" && modelSnapshot.writable;
925
- const handleModelsChange = async (models) => {
926
- if (!canModifyModels) return;
927
- setModelSaveError(void 0);
928
- const failure = validateModelCatalog(models);
929
- if (failure) {
930
- setModelSaveError(t(failure));
931
- return;
932
- }
933
- if (!await operations.storeModels(currentRegion, models)) setModelSaveError(t("saveFailed"));
934
- };
935
1339
  const renderModelCatalogSection = () => {
936
1340
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
937
1341
  className: QoderCredentialCard_module_css_default.customized,
@@ -941,29 +1345,23 @@ window.__ModuleLoader__.load({
941
1345
  className: QoderCredentialCard_module_css_default.modelSummaryBadge,
942
1346
  children: t("modelsEnabled", { count: currentModels.length })
943
1347
  })]
944
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1348
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
945
1349
  className: QoderCredentialCard_module_css_default.customizedBody,
946
- children: [modelSnapshot.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1350
+ children: modelSnapshot.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
947
1351
  className: QoderCredentialCard_module_css_default.hint,
948
1352
  children: t("modelsLoading")
949
1353
  }) : modelSnapshot.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
950
1354
  className: QoderCredentialCard_module_css_default.error,
951
1355
  children: t("modelsUnavailable")
952
1356
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QoderModelCatalog, {
1357
+ region: currentRegion,
953
1358
  operations,
954
1359
  models: currentModels,
955
1360
  disabled: !canModifyModels,
956
1361
  fetchDisabled: !configured,
957
1362
  hideTitle: true,
958
- t,
959
- onChange: (models) => {
960
- handleModelsChange(models);
961
- }
962
- }), modelSaveError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
963
- className: QoderCredentialCard_module_css_default.error,
964
- role: "alert",
965
- children: modelSaveError
966
- }) : null]
1363
+ t
1364
+ })
967
1365
  })]
968
1366
  });
969
1367
  };
@@ -988,228 +1386,539 @@ window.__ModuleLoader__.load({
988
1386
  });
989
1387
  }
990
1388
  //#endregion
991
- //#region src/client/QoderCredentialCard.tsx
992
- function QoderCredentialCard({ operations, t }) {
993
- const [state, setState] = (0, react.useState)({ status: "loading" });
1389
+ //#region src/client/QoderBrowserLogin.tsx
1390
+ /** A single current identity, with browser and PAT management revealed only when needed. */
1391
+ function QoderBrowserLogin({ region, operations, credentials, onBusyChange, t }) {
1392
+ const [view, setView] = (0, react.useState)();
1393
+ const [pat, setPat] = (0, react.useState)();
1394
+ const [profile, setProfile] = (0, react.useState)();
1395
+ const [busy, setBusy] = (0, react.useState)(false);
1396
+ const [error, setError] = (0, react.useState)();
1397
+ const [readFailed, setReadFailed] = (0, react.useState)(false);
994
1398
  const [editing, setEditing] = (0, react.useState)(false);
1399
+ const [menu, setMenu] = (0, react.useState)(false);
995
1400
  const [draft, setDraft] = (0, react.useState)("");
996
- const [regionDraft, setRegionDraft] = (0, react.useState)("global");
997
- const [regionDirty, setRegionDirty] = (0, react.useState)(false);
998
- const [mutation, setMutation] = (0, react.useState)();
999
- const [notice, setNotice] = (0, react.useState)();
1000
- const modelSnapshot = (0, react.useSyncExternalStore)(operations.subscribeModels, operations.getModelSnapshot, operations.getModelSnapshot);
1401
+ const [copyFailed, setCopyFailed] = (0, react.useState)(false);
1402
+ const [copied, setCopied] = (0, react.useState)(false);
1403
+ const [now, setNow] = (0, react.useState)(Date.now());
1404
+ const [accountRevision, setAccountRevision] = (0, react.useState)(0);
1405
+ const mounted = (0, react.useRef)(false);
1406
+ const revision = (0, react.useRef)(0);
1407
+ const mutating = (0, react.useRef)(false);
1408
+ const waitingRef = (0, react.useRef)(false);
1409
+ const wakeRef = (0, react.useRef)(void 0);
1410
+ const attempt = view?.attempt;
1411
+ const pending = attempt !== void 0 && [
1412
+ "waiting",
1413
+ "verifying",
1414
+ "saving"
1415
+ ].includes(attempt.phase);
1416
+ const connected = view?.configured === true && !view.expired;
1417
+ const identity = view?.method === "browser" ? view.profile : profile;
1418
+ const patConfigured = pat?.configured === true && !isEnvironmentCredentialSource(pat.source);
1419
+ const remaining = Math.max(0, Math.ceil(((attempt?.expiresAt ?? now) - now) / 1e3));
1420
+ const read = async () => {
1421
+ const info = await (credentials.describePat ?? credentials.describe)();
1422
+ const result = operations ? await operations.read(region) : void 0;
1423
+ if (result && !result.ok) throw new Error("Authorization status unavailable");
1424
+ if (!result && !info) throw new Error("Credential status unavailable");
1425
+ return {
1426
+ info,
1427
+ value: result?.value ?? {
1428
+ region,
1429
+ method: "pat",
1430
+ configured: info?.configured === true && !isEnvironmentCredentialSource(info.source),
1431
+ writable: false
1432
+ }
1433
+ };
1434
+ };
1001
1435
  (0, react.useEffect)(() => {
1002
- if (modelSnapshot.status === "ready" && !regionDirty) setRegionDraft(regionOf(modelSnapshot.value));
1003
- }, [modelSnapshot, regionDirty]);
1004
- const load = (0, react.useCallback)(async () => {
1005
- const info = await operations.describe();
1006
- setState(info === void 0 ? { status: "failed" } : {
1007
- status: "ready",
1008
- info
1436
+ mounted.current = true;
1437
+ let active = true;
1438
+ let timer;
1439
+ const poll = async (identityChanged = false) => {
1440
+ const version = revision.current;
1441
+ try {
1442
+ const result = await read();
1443
+ if (active && mounted.current && version === revision.current && !mutating.current) {
1444
+ waitingRef.current = result.value.attempt !== void 0 && [
1445
+ "waiting",
1446
+ "verifying",
1447
+ "saving"
1448
+ ].includes(result.value.attempt.phase);
1449
+ setView(result.value);
1450
+ setPat(result.info);
1451
+ setReadFailed(false);
1452
+ setNow(Date.now());
1453
+ if (identityChanged) setAccountRevision((value) => value + 1);
1454
+ }
1455
+ } catch {
1456
+ if (mounted.current && version === revision.current) setReadFailed(true);
1457
+ }
1458
+ if (active && mounted.current) {
1459
+ clearTimeout(timer);
1460
+ timer = setTimeout(() => {
1461
+ poll();
1462
+ }, waitingRef.current ? 1e3 : 1e4);
1463
+ }
1464
+ };
1465
+ poll();
1466
+ wakeRef.current = () => {
1467
+ clearTimeout(timer);
1468
+ timer = setTimeout(() => {
1469
+ poll();
1470
+ }, 1e3);
1471
+ };
1472
+ const unsubscribe = credentials.subscribe?.(() => {
1473
+ if (mutating.current || !active) return;
1474
+ revision.current++;
1475
+ setProfile(void 0);
1476
+ poll(true);
1009
1477
  });
1010
- }, [operations]);
1478
+ return () => {
1479
+ active = false;
1480
+ mounted.current = false;
1481
+ wakeRef.current = void 0;
1482
+ clearTimeout(timer);
1483
+ unsubscribe?.();
1484
+ revision.current++;
1485
+ };
1486
+ }, [
1487
+ operations,
1488
+ credentials,
1489
+ region
1490
+ ]);
1011
1491
  (0, react.useEffect)(() => {
1012
- load();
1013
- return operations.subscribe(() => {
1014
- load();
1015
- });
1016
- }, [load, operations]);
1017
- if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
1018
- className: QoderCredentialCard_module_css_default.credential,
1019
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("loading") })
1020
- });
1021
- if (state.status === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1022
- className: QoderCredentialCard_module_css_default.credential,
1023
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1024
- className: QoderCredentialCard_module_css_default.error,
1025
- role: "alert",
1026
- children: t("loadFailed")
1027
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1028
- type: "button",
1029
- className: QoderCredentialCard_module_css_default.secondary,
1030
- onClick: () => {
1031
- load();
1032
- },
1033
- children: t("retry")
1034
- })]
1035
- });
1036
- const configured = state.info.configured && !isEnvironmentCredentialSource(state.info.source);
1037
- const writable = state.info.writable;
1038
- const busy = mutation !== void 0;
1039
- const normalized = draft.trim();
1040
- const canModifyRegion = modelSnapshot.status === "ready" && modelSnapshot.writable;
1041
- const currentRegion = regionOf(modelSnapshot.value);
1042
- const closeEditor = () => {
1043
- setDraft("");
1044
- setRegionDraft(regionOf(modelSnapshot.value));
1045
- setRegionDirty(false);
1046
- setNotice(void 0);
1047
- setEditing(false);
1048
- };
1049
- const save = async () => {
1050
- if (busy || !regionDirty && normalized.length === 0 || regionDirty && !canModifyRegion) return;
1051
- setMutation("saving");
1052
- setNotice(void 0);
1053
- if (regionDirty) {
1054
- if (!await operations.storeRegion(regionDraft)) {
1055
- setNotice("saveFailed");
1056
- setMutation(void 0);
1492
+ let active = true;
1493
+ setProfile(void 0);
1494
+ if (view?.method === "pat" && view.configured) {
1495
+ const version = revision.current;
1496
+ credentials.getAccount().then((result) => {
1497
+ if (active && version === revision.current && result?.ok) setProfile(result.value.profile);
1498
+ }).catch(() => {});
1499
+ }
1500
+ return () => {
1501
+ active = false;
1502
+ };
1503
+ }, [
1504
+ credentials,
1505
+ view?.method,
1506
+ view?.configured,
1507
+ accountRevision
1508
+ ]);
1509
+ const mutate = async (action) => {
1510
+ if (mutating.current) return;
1511
+ const popup = action === "begin" ? window.open("about:blank", "_blank") : null;
1512
+ if (popup) popup.opener = null;
1513
+ const version = ++revision.current;
1514
+ mutating.current = true;
1515
+ onBusyChange?.(true);
1516
+ setBusy(true);
1517
+ setError(void 0);
1518
+ setMenu(false);
1519
+ try {
1520
+ let result;
1521
+ if (action === "savePat") if (draft.trim()) {
1522
+ if (!await credentials.store(draft.trim())) throw new Error("PAT save failed");
1523
+ } else if (patConfigured) {
1524
+ if (operations) result = await operations.select(region, "pat");
1525
+ } else throw new Error("A PAT is required");
1526
+ else if (action === "removePat") {
1527
+ if (!await credentials.remove()) throw new Error("PAT removal failed");
1528
+ } else if (operations) result = action === "begin" ? await operations.begin(region) : action === "cancel" ? await operations.cancel(region) : await operations.select(region, action);
1529
+ if (result && !result.ok) {
1530
+ if (mounted.current && version === revision.current) {
1531
+ const code = result.error.code;
1532
+ setError(action === "none" ? "browserRemoveFailed" : code === "TIMEOUT" ? "browserTimeout" : code === "UNAUTHENTICATED" ? "browserRejected" : "authorizationConnectionFailed");
1533
+ }
1534
+ popup?.close();
1057
1535
  return;
1058
1536
  }
1059
- setRegionDirty(false);
1537
+ const current = result?.value;
1538
+ if (!mounted.current || version !== revision.current) {
1539
+ popup?.close();
1540
+ return;
1541
+ }
1542
+ if (popup && current?.attempt?.url) popup.location.replace(current.attempt.url);
1543
+ else popup?.close();
1544
+ const refreshed = current ? {
1545
+ value: current,
1546
+ info: pat
1547
+ } : await read();
1548
+ if (!mounted.current || version !== revision.current) {
1549
+ popup?.close();
1550
+ return;
1551
+ }
1552
+ setView(refreshed.value);
1553
+ setPat(refreshed.info);
1554
+ setReadFailed(false);
1555
+ setNow(Date.now());
1556
+ waitingRef.current = refreshed.value.attempt !== void 0 && [
1557
+ "waiting",
1558
+ "verifying",
1559
+ "saving"
1560
+ ].includes(refreshed.value.attempt.phase);
1561
+ if (action === "savePat" || action === "removePat") {
1562
+ setDraft("");
1563
+ setEditing(false);
1564
+ setProfile(void 0);
1565
+ setAccountRevision((value) => value + 1);
1566
+ }
1567
+ } catch {
1568
+ popup?.close();
1569
+ if (mounted.current && version === revision.current) setError(action === "savePat" ? "patSaveFailed" : action === "removePat" ? "removeFailed" : action === "none" ? "browserRemoveFailed" : "authorizationConnectionFailed");
1570
+ } finally {
1571
+ mutating.current = false;
1572
+ onBusyChange?.(false);
1573
+ if (mounted.current && version === revision.current) {
1574
+ revision.current++;
1575
+ setBusy(false);
1576
+ }
1577
+ if (mounted.current) wakeRef.current?.();
1060
1578
  }
1061
- if (normalized.length === 0 || await operations.store(normalized)) {
1062
- setDraft("");
1063
- setNotice("saved");
1064
- if (normalized.length > 0) await load();
1065
- setEditing(false);
1066
- } else setNotice("saveFailed");
1067
- setMutation(void 0);
1068
1579
  };
1069
- const remove = async () => {
1070
- if (!configured || !writable || busy || !window.confirm(t("confirmRemove"))) return;
1071
- setMutation("removing");
1072
- setNotice(void 0);
1073
- if (await operations.remove()) {
1074
- setDraft("");
1075
- await load();
1076
- } else setNotice("removeFailed");
1077
- setMutation(void 0);
1580
+ const openPat = () => {
1581
+ setMenu(false);
1582
+ setDraft("");
1583
+ setError(void 0);
1584
+ setEditing(true);
1078
1585
  };
1079
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1080
- className: `${QoderCredentialCard_module_css_default.credential} ${QoderCredentialCard_module_css_default.credentialEditor}`,
1081
- "aria-label": t("title"),
1586
+ const initial = (identity?.name || identity?.email || "Q").slice(0, 1).toUpperCase();
1587
+ const failure = attempt?.phase === "failed" ? attempt.failure === "storage" ? "browserStorageFailed" : attempt.failure === "connection" ? "authorizationConnectionFailed" : "browserRejected" : void 0;
1588
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1589
+ className: QoderCredentialCard_module_css_default.connectionBody,
1590
+ "aria-label": t("accountConnectionTitle"),
1082
1591
  children: [
1083
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1084
- className: QoderCredentialCard_module_css_default.credentialRowHead,
1085
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1086
- className: QoderCredentialCard_module_css_default.credentialIdentity,
1592
+ !view ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1593
+ role: "status",
1594
+ children: t(readFailed ? "authorizationConnectionFailed" : "loading")
1595
+ }) : null,
1596
+ readFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1597
+ type: "button",
1598
+ className: QoderCredentialCard_module_css_default.secondary,
1599
+ onClick: () => wakeRef.current?.(),
1600
+ children: t("retry")
1601
+ }) : null,
1602
+ view ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1603
+ connected || view.expired ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1604
+ className: QoderCredentialCard_module_css_default.subscriberRow,
1087
1605
  children: [
1088
1606
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1089
- className: QoderCredentialCard_module_css_default.credentialName,
1090
- children: t("nav")
1607
+ className: QoderCredentialCard_module_css_default.subscriberAvatar,
1608
+ "aria-hidden": "true",
1609
+ children: initial
1091
1610
  }),
1092
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1093
- className: QoderCredentialCard_module_css_default.regionBadge,
1094
- children: currentRegion === "china" ? t("regionChina") : t("regionGlobal")
1611
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1612
+ className: QoderCredentialCard_module_css_default.subscriberIdentity,
1613
+ children: [
1614
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: identity?.name || t(view.method === "pat" ? "patActive" : "browserMethod") }),
1615
+ identity?.email ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: identity.email }) : null,
1616
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1617
+ className: QoderCredentialCard_module_css_default.label,
1618
+ children: t(view.method === "browser" ? "browserMethod" : "patMethod")
1619
+ })
1620
+ ]
1095
1621
  }),
1622
+ !pending && !editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1623
+ type: "button",
1624
+ className: QoderCredentialCard_module_css_default.secondary,
1625
+ "aria-expanded": menu,
1626
+ "aria-controls": "qoder-account-management",
1627
+ disabled: busy,
1628
+ onClick: () => setMenu(!menu),
1629
+ children: t(view.method === "pat" ? "manageCredential" : "manageAccount")
1630
+ }) : null
1631
+ ]
1632
+ }) : !pending && !editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1633
+ className: QoderCredentialCard_module_css_default.label,
1634
+ children: t("signInDescription")
1635
+ }) : null,
1636
+ pending ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1637
+ className: QoderCredentialCard_module_css_default.loginProgress,
1638
+ role: "status",
1639
+ children: [
1640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t(attempt.phase === "saving" ? "browserSaving" : "browserWaiting") }),
1096
1641
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1097
- className: `${QoderCredentialCard_module_css_default.credentialDot} ${configured ? QoderCredentialCard_module_css_default.credentialDotConfigured : QoderCredentialCard_module_css_default.credentialDotMissing}`,
1098
- role: "img",
1099
- "aria-label": configured ? t("configured") : t("missing"),
1100
- title: configured ? t("configured") : t("missing")
1642
+ className: QoderCredentialCard_module_css_default.label,
1643
+ children: t("loginTimeRemaining", { seconds: remaining })
1644
+ }),
1645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1646
+ className: QoderCredentialCard_module_css_default.connectionActions,
1647
+ children: [
1648
+ attempt.url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1649
+ className: QoderCredentialCard_module_css_default.primaryLink,
1650
+ href: attempt.url,
1651
+ target: "_blank",
1652
+ rel: "noopener noreferrer",
1653
+ referrerPolicy: "no-referrer",
1654
+ children: t("reopenLogin")
1655
+ }) : null,
1656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1657
+ type: "button",
1658
+ className: QoderCredentialCard_module_css_default.secondary,
1659
+ disabled: busy || !view.writable,
1660
+ onClick: () => {
1661
+ mutate("cancel");
1662
+ },
1663
+ children: t("cancelLogin")
1664
+ }),
1665
+ attempt.url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1666
+ type: "button",
1667
+ className: QoderCredentialCard_module_css_default.textButton,
1668
+ onClick: () => {
1669
+ if (!navigator.clipboard) {
1670
+ setCopyFailed(true);
1671
+ return;
1672
+ }
1673
+ navigator.clipboard.writeText(attempt.url).then(() => {
1674
+ if (mounted.current) {
1675
+ setCopied(true);
1676
+ setCopyFailed(false);
1677
+ }
1678
+ }, () => {
1679
+ if (mounted.current) setCopyFailed(true);
1680
+ });
1681
+ },
1682
+ children: t(copied ? "loginLinkCopied" : "copyLogin")
1683
+ }) : null
1684
+ ]
1101
1685
  })
1102
1686
  ]
1103
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1104
- type: "button",
1105
- className: QoderCredentialCard_module_css_default.editButton,
1106
- "aria-expanded": editing,
1107
- "aria-controls": "qoder-credential-editor",
1108
- onClick: () => {
1109
- if (editing) closeEditor();
1110
- else {
1111
- setNotice(void 0);
1112
- setEditing(true);
1113
- }
1114
- },
1115
- children: t("edit")
1687
+ }) : null,
1688
+ view.expired ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1689
+ role: "status",
1690
+ className: QoderCredentialCard_module_css_default.error,
1691
+ children: t("browserExpired")
1692
+ }) : null,
1693
+ view.unsaved ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1694
+ role: "alert",
1695
+ className: QoderCredentialCard_module_css_default.error,
1696
+ children: t("browserUnsaved")
1697
+ }) : null,
1698
+ !pending && attempt?.phase === "timeout" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1699
+ role: "status",
1700
+ className: QoderCredentialCard_module_css_default.error,
1701
+ children: t("browserTimeout")
1702
+ }) : null,
1703
+ !pending && failure ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1704
+ role: "alert",
1705
+ className: QoderCredentialCard_module_css_default.error,
1706
+ children: t(failure)
1707
+ }) : null,
1708
+ menu && !pending && !editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1709
+ id: "qoder-account-management",
1710
+ className: QoderCredentialCard_module_css_default.managementPanel,
1711
+ children: [
1712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1713
+ type: "button",
1714
+ className: QoderCredentialCard_module_css_default.textButton,
1715
+ disabled: busy || !view.writable,
1716
+ onClick: () => {
1717
+ mutate("begin");
1718
+ },
1719
+ children: t("changeAccount")
1720
+ }),
1721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1722
+ type: "button",
1723
+ className: QoderCredentialCard_module_css_default.textButton,
1724
+ disabled: busy || !pat?.writable,
1725
+ onClick: openPat,
1726
+ children: t(view.method === "pat" ? "editPat" : "usePat")
1727
+ }),
1728
+ view.method !== "browser" && view.profile ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1729
+ type: "button",
1730
+ className: QoderCredentialCard_module_css_default.textButton,
1731
+ disabled: busy || !view.writable,
1732
+ onClick: () => {
1733
+ mutate("browser");
1734
+ },
1735
+ children: t("useBrowser")
1736
+ }) : null,
1737
+ view.profile ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1738
+ type: "button",
1739
+ className: QoderCredentialCard_module_css_default.danger,
1740
+ disabled: busy || !view.writable,
1741
+ onClick: () => {
1742
+ mutate("none");
1743
+ },
1744
+ children: t("signOutQoder")
1745
+ }) : null
1746
+ ]
1747
+ }) : null,
1748
+ !pending && !editing && (!connected || view.expired) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1749
+ className: QoderCredentialCard_module_css_default.connectionActions,
1750
+ children: [
1751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1752
+ type: "button",
1753
+ className: QoderCredentialCard_module_css_default.primary,
1754
+ disabled: busy || !view.writable,
1755
+ onClick: () => {
1756
+ mutate("begin");
1757
+ },
1758
+ children: t(view.expired ? "signInAgain" : "signInQoder")
1759
+ }),
1760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1761
+ type: "button",
1762
+ className: QoderCredentialCard_module_css_default.textButton,
1763
+ disabled: busy || !pat?.writable,
1764
+ onClick: openPat,
1765
+ children: t("usePat")
1766
+ }),
1767
+ view.profile && view.method !== "browser" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1768
+ type: "button",
1769
+ className: QoderCredentialCard_module_css_default.textButton,
1770
+ disabled: busy || !view.writable,
1771
+ onClick: () => {
1772
+ mutate("browser");
1773
+ },
1774
+ children: t("useBrowser")
1775
+ }) : null
1776
+ ]
1777
+ }) : null,
1778
+ editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1779
+ className: QoderCredentialCard_module_css_default.editor,
1780
+ children: [
1781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1782
+ className: QoderCredentialCard_module_css_default.label,
1783
+ htmlFor: "qoder-managed-credential",
1784
+ children: t("tokenLabel")
1785
+ }),
1786
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1787
+ id: "qoder-managed-credential",
1788
+ className: QoderCredentialCard_module_css_default.input,
1789
+ type: "password",
1790
+ autoComplete: "new-password",
1791
+ value: draft,
1792
+ disabled: busy || !pat?.writable,
1793
+ placeholder: patConfigured ? t("configuredHint") : t("tokenPlaceholder"),
1794
+ onChange: (event) => setDraft(event.currentTarget.value)
1795
+ }),
1796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1797
+ className: QoderCredentialCard_module_css_default.connectionActions,
1798
+ children: [
1799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1800
+ type: "button",
1801
+ className: QoderCredentialCard_module_css_default.primary,
1802
+ disabled: busy || !pat?.writable || !draft.trim() && !patConfigured,
1803
+ onClick: () => {
1804
+ mutate("savePat");
1805
+ },
1806
+ children: t(busy ? "saving" : draft.trim() || !patConfigured ? "saveAndUsePat" : "useSavedPat")
1807
+ }),
1808
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1809
+ type: "button",
1810
+ className: QoderCredentialCard_module_css_default.secondary,
1811
+ disabled: busy,
1812
+ onClick: () => {
1813
+ setDraft("");
1814
+ setEditing(false);
1815
+ setError(void 0);
1816
+ },
1817
+ children: t("cancel")
1818
+ }),
1819
+ patConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1820
+ type: "button",
1821
+ className: QoderCredentialCard_module_css_default.danger,
1822
+ disabled: busy || !pat?.writable,
1823
+ onClick: () => {
1824
+ if (window.confirm(t("confirmRemove"))) mutate("removePat");
1825
+ },
1826
+ children: t("remove")
1827
+ }) : null
1828
+ ]
1829
+ })
1830
+ ]
1831
+ }) : null
1832
+ ] }) : null,
1833
+ view && !view.writable && !pat?.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1834
+ className: QoderCredentialCard_module_css_default.label,
1835
+ children: t("readOnly")
1836
+ }) : null,
1837
+ copyFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1838
+ role: "alert",
1839
+ className: QoderCredentialCard_module_css_default.error,
1840
+ children: t("copyLoginFailed")
1841
+ }) : null,
1842
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1843
+ role: "alert",
1844
+ className: QoderCredentialCard_module_css_default.error,
1845
+ children: t(error)
1846
+ }) : null,
1847
+ readFailed && view ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1848
+ role: "alert",
1849
+ className: QoderCredentialCard_module_css_default.error,
1850
+ children: t("authorizationConnectionFailed")
1851
+ }) : null
1852
+ ]
1853
+ });
1854
+ }
1855
+ //#endregion
1856
+ //#region src/client/QoderCredentialCard.tsx
1857
+ /** Region selection stays outside the keyed account state, so old identities never flash. */
1858
+ function QoderCredentialCard({ operations, t }) {
1859
+ const snapshot = (0, react.useSyncExternalStore)(operations.subscribeModels, operations.getModelSnapshot, operations.getModelSnapshot);
1860
+ const region = regionOf(snapshot.value);
1861
+ const [changing, setChanging] = (0, react.useState)(false);
1862
+ const [accountBusy, setAccountBusy] = (0, react.useState)(false);
1863
+ const [notice, setNotice] = (0, react.useState)();
1864
+ const mutating = (0, react.useRef)(false);
1865
+ const canChange = snapshot.status === "ready" && snapshot.writable && !changing && !accountBusy;
1866
+ const changeRegion = async (next) => {
1867
+ if (!canChange || next === region || mutating.current) return;
1868
+ mutating.current = true;
1869
+ setChanging(true);
1870
+ setNotice(void 0);
1871
+ try {
1872
+ setNotice(await operations.storeRegion(next) ? "regionChanged" : "regionChangeFailed");
1873
+ } catch {
1874
+ setNotice("regionChangeFailed");
1875
+ } finally {
1876
+ mutating.current = false;
1877
+ setChanging(false);
1878
+ }
1879
+ };
1880
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1881
+ className: `${QoderCredentialCard_module_css_default.credential} ${QoderCredentialCard_module_css_default.credentialEditor}`,
1882
+ "aria-label": t("accountConnectionTitle"),
1883
+ children: [
1884
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1885
+ className: QoderCredentialCard_module_css_default.connectionHead,
1886
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
1887
+ className: QoderCredentialCard_module_css_default.credentialName,
1888
+ children: t("accountConnectionTitle")
1889
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1890
+ className: QoderCredentialCard_module_css_default.regionGroup,
1891
+ role: "radiogroup",
1892
+ "aria-label": t("regionLabel"),
1893
+ children: ["global", "china"].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1894
+ type: "button",
1895
+ role: "radio",
1896
+ "aria-checked": region === value,
1897
+ disabled: !canChange,
1898
+ className: `${QoderCredentialCard_module_css_default.regionOption} ${region === value ? QoderCredentialCard_module_css_default.regionOptionActive : ""}`,
1899
+ onClick: () => {
1900
+ changeRegion(value);
1901
+ },
1902
+ children: t(value === "china" ? "regionChina" : "regionGlobal")
1903
+ }, value))
1116
1904
  })]
1117
1905
  }),
1118
- notice === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1119
- className: notice === "saved" ? QoderCredentialCard_module_css_default.success : QoderCredentialCard_module_css_default.error,
1906
+ notice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1120
1907
  role: "status",
1908
+ className: notice === "regionChangeFailed" ? QoderCredentialCard_module_css_default.error : QoderCredentialCard_module_css_default.label,
1121
1909
  children: t(notice)
1122
- }),
1123
- editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1124
- id: "qoder-credential-editor",
1125
- className: QoderCredentialCard_module_css_default.editor,
1126
- children: [
1127
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1128
- className: QoderCredentialCard_module_css_default.regionSelector,
1129
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1130
- className: QoderCredentialCard_module_css_default.label,
1131
- children: t("regionLabel")
1132
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1133
- className: QoderCredentialCard_module_css_default.regionGroup,
1134
- role: "radiogroup",
1135
- "aria-label": t("regionLabel"),
1136
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1137
- type: "button",
1138
- role: "radio",
1139
- "aria-checked": regionDraft === "global",
1140
- className: `${QoderCredentialCard_module_css_default.regionOption} ${regionDraft === "global" ? QoderCredentialCard_module_css_default.regionOptionActive : ""}`,
1141
- disabled: !canModifyRegion || busy,
1142
- onClick: () => {
1143
- setRegionDraft("global");
1144
- setRegionDirty(true);
1145
- },
1146
- children: t("regionGlobal")
1147
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1148
- type: "button",
1149
- role: "radio",
1150
- "aria-checked": regionDraft === "china",
1151
- className: `${QoderCredentialCard_module_css_default.regionOption} ${regionDraft === "china" ? QoderCredentialCard_module_css_default.regionOptionActive : ""}`,
1152
- disabled: !canModifyRegion || busy,
1153
- onClick: () => {
1154
- setRegionDraft("china");
1155
- setRegionDirty(true);
1156
- },
1157
- children: t("regionChina")
1158
- })]
1159
- })]
1160
- }),
1161
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1162
- className: QoderCredentialCard_module_css_default.label,
1163
- htmlFor: "qoder-managed-credential",
1164
- children: t("tokenLabel")
1165
- }),
1166
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1167
- id: "qoder-managed-credential",
1168
- className: QoderCredentialCard_module_css_default.input,
1169
- type: "password",
1170
- autoComplete: "new-password",
1171
- value: draft,
1172
- disabled: !writable || busy,
1173
- placeholder: configured ? t("configuredHint") : t("tokenPlaceholder"),
1174
- onChange: (event) => setDraft(event.currentTarget.value)
1175
- }),
1176
- !writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1177
- className: QoderCredentialCard_module_css_default.error,
1178
- role: "alert",
1179
- children: t("readOnly")
1180
- }) : null,
1181
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1182
- className: QoderCredentialCard_module_css_default.actions,
1183
- children: [
1184
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1185
- type: "button",
1186
- className: QoderCredentialCard_module_css_default.secondary,
1187
- disabled: busy,
1188
- onClick: closeEditor,
1189
- children: t("cancel")
1190
- }),
1191
- configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1192
- type: "button",
1193
- className: QoderCredentialCard_module_css_default.danger,
1194
- disabled: !writable || busy,
1195
- onClick: () => {
1196
- remove();
1197
- },
1198
- children: mutation === "removing" ? t("removing") : t("remove")
1199
- }) : null,
1200
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1201
- type: "button",
1202
- className: QoderCredentialCard_module_css_default.primary,
1203
- disabled: busy || !regionDirty && normalized.length === 0 || regionDirty && !canModifyRegion || normalized.length > 0 && !writable,
1204
- onClick: () => {
1205
- save();
1206
- },
1207
- children: mutation === "saving" ? t("saving") : t("save")
1208
- })
1209
- ]
1210
- })
1211
- ]
1212
- }) : null
1910
+ }) : null,
1911
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QoderBrowserLogin, {
1912
+ region,
1913
+ operations: operations.authorization,
1914
+ credentials: operations,
1915
+ onBusyChange: setAccountBusy,
1916
+ t
1917
+ }, region),
1918
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1919
+ className: QoderCredentialCard_module_css_default.sharedNote,
1920
+ children: t("sharedAuthorization")
1921
+ })
1213
1922
  ]
1214
1923
  });
1215
1924
  }
@@ -1227,17 +1936,102 @@ window.__ModuleLoader__.load({
1227
1936
  "qoder-subscription"
1228
1937
  ].includes(provider ?? "");
1229
1938
  }
1230
- function historicalContextWindow(current, lastUsed, contextWindow) {
1231
- return current.provider === lastUsed?.provider && current.model === lastUsed?.model ? contextWindow : void 0;
1232
- }
1233
- /** Composer selections belong to the current session; model defaults live in settings. */
1234
- async function saveContextSelection(operations, sessionId, region, modelId, tierKey) {
1235
- try {
1236
- return await operations.setSessionTier?.(sessionId, modelId, tierKey, region) ? "saved" : "session-failed";
1237
- } catch {
1238
- return "session-failed";
1939
+ //#endregion
1940
+ //#region src/client/session-tier-state.ts
1941
+ /** A mounted view of the host's accepted selection; it owns no manual tier map. */
1942
+ var SessionTierState = class {
1943
+ snapshot = {
1944
+ loading: true,
1945
+ saving: false,
1946
+ error: false
1947
+ };
1948
+ listeners = /* @__PURE__ */ new Set();
1949
+ generation = 0;
1950
+ active = false;
1951
+ lifetime = 0;
1952
+ unsubscribe;
1953
+ scope;
1954
+ operations;
1955
+ constructor(scope, operations) {
1956
+ this.scope = scope;
1957
+ this.operations = operations;
1239
1958
  }
1240
- }
1959
+ getSnapshot = () => this.snapshot;
1960
+ subscribe = (listener) => {
1961
+ this.listeners.add(listener);
1962
+ return () => {
1963
+ this.listeners.delete(listener);
1964
+ };
1965
+ };
1966
+ publish(value) {
1967
+ this.snapshot = {
1968
+ ...this.snapshot,
1969
+ ...value
1970
+ };
1971
+ for (const listener of this.listeners) listener();
1972
+ }
1973
+ activate() {
1974
+ this.active = true;
1975
+ this.lifetime++;
1976
+ this.publish({ saving: false });
1977
+ this.unsubscribe = this.operations.subscribeSessionTiers?.(this.scope, () => {
1978
+ this.refresh();
1979
+ });
1980
+ this.refresh();
1981
+ }
1982
+ dispose() {
1983
+ this.active = false;
1984
+ this.generation++;
1985
+ this.unsubscribe?.();
1986
+ this.unsubscribe = void 0;
1987
+ }
1988
+ matches(value) {
1989
+ return !!value && value.region === this.scope.region && value.sessionId === this.scope.sessionId && value.modelId === this.scope.modelId;
1990
+ }
1991
+ async refresh() {
1992
+ if (!this.active) return;
1993
+ const generation = ++this.generation;
1994
+ this.publish({ loading: true });
1995
+ try {
1996
+ const selection = await this.operations.readSessionTier?.(this.scope);
1997
+ if (this.active && generation === this.generation) this.publish(this.matches(selection) ? {
1998
+ selection,
1999
+ loading: false,
2000
+ error: false
2001
+ } : {
2002
+ loading: false,
2003
+ error: true
2004
+ });
2005
+ } catch {
2006
+ if (this.active && generation === this.generation) this.publish({
2007
+ loading: false,
2008
+ error: true
2009
+ });
2010
+ }
2011
+ }
2012
+ async select(tierKey) {
2013
+ if (!this.active || this.snapshot.loading || this.snapshot.saving || !this.snapshot.selection) return;
2014
+ if (tierKey === this.snapshot.selection.tierKey && !this.snapshot.error) return;
2015
+ const generation = ++this.generation;
2016
+ const lifetime = this.lifetime;
2017
+ this.publish({
2018
+ saving: true,
2019
+ error: false
2020
+ });
2021
+ try {
2022
+ const { sessionId, modelId, region } = this.scope;
2023
+ const selection = await this.operations.setSessionTier?.(sessionId, modelId, tierKey, region);
2024
+ if (this.active && generation === this.generation) this.publish(this.matches(selection) ? {
2025
+ selection,
2026
+ error: false
2027
+ } : { error: true });
2028
+ } catch {
2029
+ if (this.active && generation === this.generation) this.publish({ error: true });
2030
+ } finally {
2031
+ if (this.active && lifetime === this.lifetime) this.publish({ saving: false });
2032
+ }
2033
+ }
2034
+ };
1241
2035
  //#endregion
1242
2036
  //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderContextSelect.module.css.mjs
1243
2037
  const css = ".hiDrJa_root{align-items:center;display:inline-flex;position:relative}.hiDrJa_trigger{height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;user-select:none;background:0 0;border:none;border-radius:24px;outline:none;align-items:center;gap:4px;padding:0 8px;font-size:13px;font-weight:500;line-height:20px;transition:background .12s,color .12s;display:flex}.hiDrJa_trigger:hover,.hiDrJa_triggerActive{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.hiDrJa_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.hiDrJa_icon{color:var(--dsw-alias-label-caption);justify-content:center;align-items:center;display:inline-flex}.hiDrJa_label{white-space:nowrap}.hiDrJa_chevron{color:var(--dsw-alias-label-caption);align-items:center;transition:transform .12s;display:inline-flex}.hiDrJa_chevronOpen{transform:rotate(180deg)}.hiDrJa_menu{z-index:1100;background:var(--dsw-menu-surface-fill);min-width:180px;backdrop-filter:var(--dsw-menu-backdrop-filter);border:1px solid var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;padding:4px;animation:.12s hiDrJa_fadeIn;display:flex;position:absolute;bottom:calc(100% + 8px);right:0}@keyframes hiDrJa_fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.hiDrJa_menuHeader{text-transform:uppercase;letter-spacing:.5px;color:var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-border-l1);margin-bottom:4px;padding:6px 10px 4px;font-size:11px;font-weight:600}.hiDrJa_option{box-sizing:border-box;width:100%;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;border-radius:8px;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;font-size:13px;line-height:20px;transition:background .1s;display:flex}.hiDrJa_option:hover{background:var(--dsw-alias-interactive-bg-hover)}.hiDrJa_optionSelected{color:var(--dsw-alias-state-brand);font-weight:600}.hiDrJa_optionInfo{align-items:center;gap:6px;display:flex}.hiDrJa_tag{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-caption);border-radius:4px;padding:1px 5px;font-size:11px;font-weight:500}.hiDrJa_check{color:var(--dsw-alias-state-brand);align-items:center;font-size:12px;display:inline-flex}.hiDrJa_error{color:var(--dsw-alias-label-primary);max-width:240px;font-size:12px}";
@@ -1270,30 +2064,9 @@ window.__ModuleLoader__.load({
1270
2064
  //#endregion
1271
2065
  //#region src/client/QoderContextSelect.tsx
1272
2066
  function QoderContextSelect(props) {
1273
- const { directory, operations, t, sessionId, useProjection } = props;
1274
- const [open, setOpen] = (0, react.useState)(false);
1275
- const [manualTiers, setManualTiers] = (0, react.useState)({});
1276
- const [pending, setPending] = (0, react.useState)(false);
1277
- const [error, setError] = (0, react.useState)(null);
1278
- const saving = (0, react.useRef)(false);
1279
- const rootRef = (0, react.useRef)(null);
2067
+ const { directory, operations, sessionId, useProjection } = props;
1280
2068
  const directoryState = (0, react.useSyncExternalStore)((fn) => directory ? directory.subscribe(fn) : () => () => {}, () => directory ? directory.getSnapshot() : null);
1281
2069
  const modelSnapshot = (0, react.useSyncExternalStore)((fn) => operations ? operations.subscribeModels(fn) : () => () => {}, () => operations ? operations.getModelSnapshot() : null);
1282
- (0, react.useEffect)(() => {
1283
- if (!open) return;
1284
- const handleClickOutside = (e) => {
1285
- if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
1286
- };
1287
- const handleKeyDown = (e) => {
1288
- if (e.key === "Escape") setOpen(false);
1289
- };
1290
- document.addEventListener("mousedown", handleClickOutside);
1291
- document.addEventListener("keydown", handleKeyDown);
1292
- return () => {
1293
- document.removeEventListener("mousedown", handleClickOutside);
1294
- document.removeEventListener("keydown", handleKeyDown);
1295
- };
1296
- }, [open]);
1297
2070
  const modelProj = useProjection ? useProjection("modelSelection") : void 0;
1298
2071
  const pressure = useProjection ? useProjection("contextPressure") : void 0;
1299
2072
  if (!operations) return null;
@@ -1309,34 +2082,71 @@ window.__ModuleLoader__.load({
1309
2082
  const currentProviderId = current.provider ?? (current.model.includes("/") ? current.model.split("/")[0] : void 0);
1310
2083
  const activeModel = models.find((m) => m.id === currentModelId || m.id === current.model);
1311
2084
  if (!isQoderProvider(currentProviderId) || !activeModel) return null;
1312
- const tiers = contextTiersOf(activeModel);
1313
- if (tiers.length <= 1) return null;
1314
- const sessionKey = sessionId ? JSON.stringify([
2085
+ if (contextTiersOf(activeModel).length <= 1) return null;
2086
+ if (!sessionId) return null;
2087
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionContextSelect, {
2088
+ ...props,
2089
+ sessionId,
2090
+ operations,
2091
+ region,
2092
+ activeModel,
2093
+ historicalWindow: pressure?.contextWindow,
2094
+ lastUsed: modelProj?.lastUsed
2095
+ }, JSON.stringify([
1315
2096
  region,
1316
2097
  sessionId,
1317
2098
  activeModel.id
1318
- ]) : void 0;
1319
- const historicalWindow = operations.canRestoreContextHistory?.() === false ? void 0 : historicalContextWindow(current, modelProj?.lastUsed, pressure?.contextWindow);
1320
- const currentTier = resolveContextTier(activeModel, sessionKey ? manualTiers[sessionKey] : void 0, historicalWindow);
1321
- if (!currentTier) return null;
1322
- const selectedTierKey = currentTier.key;
1323
- const selectTier = async (tierKey) => {
2099
+ ]));
2100
+ }
2101
+ function SessionContextSelect({ operations, sessionId, region, activeModel, t, historicalWindow, lastUsed }) {
2102
+ const [open, setOpen] = (0, react.useState)(false);
2103
+ const rootRef = (0, react.useRef)(null);
2104
+ const state = (0, react.useMemo)(() => new SessionTierState({
2105
+ region,
2106
+ sessionId,
2107
+ modelId: activeModel.id
2108
+ }, operations), [
2109
+ region,
2110
+ sessionId,
2111
+ activeModel.id,
2112
+ operations
2113
+ ]);
2114
+ const snapshot = (0, react.useSyncExternalStore)(state.subscribe, state.getSnapshot);
2115
+ (0, react.useLayoutEffect)(() => {
2116
+ state.activate();
2117
+ return () => state.dispose();
2118
+ }, [state]);
2119
+ (0, react.useEffect)(() => {
2120
+ state.refresh();
2121
+ }, [
2122
+ state,
2123
+ JSON.stringify(activeModel),
2124
+ historicalWindow,
2125
+ lastUsed?.provider,
2126
+ lastUsed?.model
2127
+ ]);
2128
+ (0, react.useEffect)(() => {
2129
+ if (!open) return;
2130
+ const handleClickOutside = (e) => {
2131
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
2132
+ };
2133
+ const handleKeyDown = (e) => {
2134
+ if (e.key === "Escape") setOpen(false);
2135
+ };
2136
+ document.addEventListener("mousedown", handleClickOutside);
2137
+ document.addEventListener("keydown", handleKeyDown);
2138
+ return () => {
2139
+ document.removeEventListener("mousedown", handleClickOutside);
2140
+ document.removeEventListener("keydown", handleKeyDown);
2141
+ };
2142
+ }, [open]);
2143
+ const tiers = contextTiersOf(activeModel);
2144
+ const currentTier = snapshot.selection;
2145
+ const selectedTierKey = currentTier?.tierKey;
2146
+ const pending = snapshot.loading || snapshot.saving;
2147
+ const selectTier = (tierKey) => {
1324
2148
  setOpen(false);
1325
- if (!sessionId || !sessionKey || saving.current) return;
1326
- if (tierKey === selectedTierKey && error?.key !== sessionKey) return;
1327
- saving.current = true;
1328
- setPending(true);
1329
- setError(null);
1330
- try {
1331
- if (await saveContextSelection(operations, sessionId, region, activeModel.id, tierKey) === "saved") setManualTiers((prev) => ({
1332
- ...prev,
1333
- [sessionKey]: tierKey
1334
- }));
1335
- else setError({ key: sessionKey });
1336
- } finally {
1337
- saving.current = false;
1338
- setPending(false);
1339
- }
2149
+ state.select(tierKey);
1340
2150
  };
1341
2151
  const defaultSuffix = t ? t("contextTierDefaultSuffix").replace(/[()()]/g, "") : "默认";
1342
2152
  const selectTitle = t ? t("contextSelectTitle") : "选择上下文大小";
@@ -1348,10 +2158,10 @@ window.__ModuleLoader__.load({
1348
2158
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1349
2159
  type: "button",
1350
2160
  className: `${QoderContextSelect_module_css_default.trigger} ${open ? QoderContextSelect_module_css_default.triggerActive : ""}`,
1351
- disabled: pending || !sessionId,
2161
+ disabled: pending || !currentTier,
1352
2162
  "aria-expanded": open,
1353
2163
  "aria-label": selectTitle,
1354
- title: `${selectTitle}: ${formatContextTokens(currentTier.tokenCount)}`,
2164
+ title: `${selectTitle}: ${currentTier ? formatContextTokens(currentTier.tokenCount) : "…"}`,
1355
2165
  onClick: () => setOpen((prev) => !prev),
1356
2166
  children: [
1357
2167
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1366,7 +2176,7 @@ window.__ModuleLoader__.load({
1366
2176
  }),
1367
2177
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1368
2178
  className: QoderContextSelect_module_css_default.label,
1369
- children: formatContextTokens(currentTier.tokenCount)
2179
+ children: currentTier ? formatContextTokens(currentTier.tokenCount) : "…"
1370
2180
  }),
1371
2181
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1372
2182
  className: `${QoderContextSelect_module_css_default.chevron} ${open ? QoderContextSelect_module_css_default.chevronOpen : ""}`,
@@ -1384,10 +2194,21 @@ window.__ModuleLoader__.load({
1384
2194
  })
1385
2195
  ]
1386
2196
  }),
1387
- error && error.key === sessionKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2197
+ snapshot.error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1388
2198
  role: "alert",
1389
2199
  className: QoderContextSelect_module_css_default.error,
1390
- children: t ? t("contextSelectFailed") : "contextSelectFailed"
2200
+ children: [
2201
+ t ? t("contextSelectFailed") : "contextSelectFailed",
2202
+ " ",
2203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2204
+ type: "button",
2205
+ disabled: pending,
2206
+ onClick: () => {
2207
+ state.refresh();
2208
+ },
2209
+ children: t ? t("refresh") : "refresh"
2210
+ })
2211
+ ]
1391
2212
  }),
1392
2213
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1393
2214
  className: QoderContextSelect_module_css_default.menu,
@@ -1401,6 +2222,7 @@ window.__ModuleLoader__.load({
1401
2222
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1402
2223
  type: "button",
1403
2224
  role: "menuitemradio",
2225
+ disabled: pending,
1404
2226
  "aria-checked": isSelected,
1405
2227
  className: `${QoderContextSelect_module_css_default.option} ${isSelected ? QoderContextSelect_module_css_default.optionSelected : ""}`,
1406
2228
  onClick: () => {
@@ -1456,6 +2278,97 @@ window.__ModuleLoader__.load({
1456
2278
  return typeof value === "string" && qoderRpcErrorCodes.includes(value);
1457
2279
  }
1458
2280
  //#endregion
2281
+ //#region src/client/session-tier-events.ts
2282
+ /** One authenticated host change stream shared by the mounted composer controls. */
2283
+ function createSessionTierEvents(send = (...args) => globalThis.fetch(...args)) {
2284
+ const listeners = /* @__PURE__ */ new Set();
2285
+ let active;
2286
+ let disposed = false;
2287
+ const notify = (scope) => {
2288
+ for (const entry of listeners) if (!scope || entry.scope.region === scope.region && entry.scope.sessionId === scope.sessionId && entry.scope.modelId === scope.modelId) try {
2289
+ entry.listener();
2290
+ } catch {}
2291
+ };
2292
+ const start = () => {
2293
+ if (active || disposed || !listeners.size) return;
2294
+ const controller = new AbortController();
2295
+ active = controller;
2296
+ follow(controller).finally(() => {
2297
+ if (active === controller) active = void 0;
2298
+ });
2299
+ };
2300
+ async function follow(controller) {
2301
+ let delay = 1e3;
2302
+ while (!controller.signal.aborted) {
2303
+ try {
2304
+ const response = await send(qoderRpcPath("sessionTierEvents"), {
2305
+ method: "POST",
2306
+ credentials: "include",
2307
+ signal: controller.signal,
2308
+ headers: { "content-type": "application/json" },
2309
+ body: "{}"
2310
+ });
2311
+ if (!response.ok || !response.body) throw new Error("Context tier stream unavailable");
2312
+ const reader = response.body.getReader();
2313
+ const decoder = new TextDecoder();
2314
+ let buffered = "";
2315
+ try {
2316
+ while (!controller.signal.aborted) {
2317
+ const { value, done } = await reader.read();
2318
+ if (done) break;
2319
+ buffered += decoder.decode(value, { stream: true });
2320
+ let newline;
2321
+ while ((newline = buffered.indexOf("\n")) >= 0) {
2322
+ const item = JSON.parse(buffered.slice(0, newline));
2323
+ buffered = buffered.slice(newline + 1);
2324
+ delay = 1e3;
2325
+ if (item.ready === true) notify();
2326
+ else if ((item.region === "global" || item.region === "china") && typeof item.sessionId === "string" && typeof item.modelId === "string") notify(item);
2327
+ }
2328
+ }
2329
+ } finally {
2330
+ await reader.cancel().catch(() => {});
2331
+ reader.releaseLock();
2332
+ }
2333
+ } catch {}
2334
+ if (controller.signal.aborted) break;
2335
+ await new Promise((resolve) => {
2336
+ const finish = () => {
2337
+ clearTimeout(timer);
2338
+ controller.signal.removeEventListener("abort", finish);
2339
+ resolve();
2340
+ };
2341
+ const timer = setTimeout(finish, delay);
2342
+ controller.signal.addEventListener("abort", finish, { once: true });
2343
+ });
2344
+ delay = Math.min(delay * 2, 3e4);
2345
+ }
2346
+ }
2347
+ return {
2348
+ subscribe(scope, listener) {
2349
+ const entry = {
2350
+ scope,
2351
+ listener
2352
+ };
2353
+ listeners.add(entry);
2354
+ start();
2355
+ return () => {
2356
+ listeners.delete(entry);
2357
+ if (!listeners.size) {
2358
+ active?.abort();
2359
+ active = void 0;
2360
+ }
2361
+ };
2362
+ },
2363
+ dispose() {
2364
+ disposed = true;
2365
+ listeners.clear();
2366
+ active?.abort();
2367
+ active = void 0;
2368
+ }
2369
+ };
2370
+ }
2371
+ //#endregion
1459
2372
  //#region src/client/rpc-client.ts
1460
2373
  /**
1461
2374
  * Browser caller for the Qoder settings RPC.
@@ -1551,23 +2464,29 @@ window.__ModuleLoader__.load({
1551
2464
  }), "provider-qoder: credential copy");
1552
2465
  const credentials = ctx.remote.credentials;
1553
2466
  const rpc = createQoderRpcCaller();
1554
- let historyRegion;
1555
- let historyRegionChanged = false;
1556
- const readModelSnapshot = () => {
1557
- const snapshot = modelScope.getSnapshot();
1558
- if (snapshot.value) {
1559
- const region = snapshot.value.region ?? "global";
1560
- if (historyRegion !== void 0 && historyRegion !== region) historyRegionChanged = true;
1561
- historyRegion = region;
1562
- }
1563
- return snapshot;
2467
+ const tierEvents = createSessionTierEvents();
2468
+ ctx.effect(() => () => tierEvents.dispose(), "provider-qoder: session context tier events");
2469
+ const readModelSnapshot = () => modelScope.getSnapshot();
2470
+ const authorizationOperations = {
2471
+ read: (region) => rpc.call("authorization", { region }),
2472
+ begin: (region) => rpc.call("beginLogin", { region }),
2473
+ cancel: (region) => rpc.call("cancelLogin", { region }),
2474
+ select: (region, method) => rpc.call("selectAuthorization", {
2475
+ region,
2476
+ method
2477
+ })
1564
2478
  };
1565
- readModelSnapshot();
1566
- ctx.effect(() => modelScope.subscribe(() => {
1567
- readModelSnapshot();
1568
- }), "provider-qoder: context history region");
1569
2479
  const operations = {
2480
+ authorization: authorizationOperations,
1570
2481
  describe: async () => {
2482
+ const result = await authorizationOperations.read(regionOf(readModelSnapshot().value));
2483
+ return result.ok ? {
2484
+ configured: result.value.configured && !result.value.expired,
2485
+ source: result.value.method,
2486
+ writable: result.value.writable
2487
+ } : void 0;
2488
+ },
2489
+ describePat: async () => {
1571
2490
  try {
1572
2491
  const response = await credentials.describe([qoderCredentialRef]);
1573
2492
  return response.ok ? response.value[qoderCredentialRef] : void 0;
@@ -1577,7 +2496,13 @@ window.__ModuleLoader__.load({
1577
2496
  },
1578
2497
  store: async (value) => {
1579
2498
  try {
1580
- return (await credentials.set(qoderCredentialRef, value.trim())).ok;
2499
+ const response = await credentials.set(qoderCredentialRef, value.trim());
2500
+ if (response.ok) {
2501
+ const region = regionOf(readModelSnapshot().value);
2502
+ const current = await authorizationOperations.read(region);
2503
+ if (current.ok && current.value.method !== "pat") return (await authorizationOperations.select(region, "pat")).ok;
2504
+ }
2505
+ return response.ok;
1581
2506
  } catch {
1582
2507
  return false;
1583
2508
  }
@@ -1591,16 +2516,15 @@ window.__ModuleLoader__.load({
1591
2516
  },
1592
2517
  getAccount: async (force) => await rpc.call("account", { force }),
1593
2518
  getModelSnapshot: readModelSnapshot,
1594
- canRestoreContextHistory: () => !historyRegionChanged,
1595
2519
  subscribeModels: (listener) => modelScope.subscribe(listener),
1596
2520
  storeModels: async (region, models) => {
1597
2521
  try {
1598
- const current = modelScope.getSnapshot().value;
1599
- const clonedModels = models.map(cloneCatalogModel);
1600
- return await modelScope.set("modelsByRegion", {
1601
- ...current?.modelsByRegion,
1602
- [region]: clonedModels
1603
- }) !== false;
2522
+ const clonedModels = JSON.parse(JSON.stringify(models));
2523
+ return await modelScope.mutate([{
2524
+ op: "set",
2525
+ path: ["modelsByRegion", region],
2526
+ value: clonedModels
2527
+ }]) !== false;
1604
2528
  } catch {
1605
2529
  return false;
1606
2530
  }
@@ -1621,20 +2545,56 @@ window.__ModuleLoader__.load({
1621
2545
  },
1622
2546
  discoverModels: async () => await rpc.call("models", {}),
1623
2547
  setSessionTier: async (sessionId, modelId, tierKey, region) => {
2548
+ const scope = {
2549
+ sessionId,
2550
+ modelId,
2551
+ tierKey,
2552
+ region
2553
+ };
1624
2554
  try {
1625
- return (await rpc.call("sessionTier", {
1626
- sessionId,
1627
- modelId,
1628
- tierKey,
1629
- region
1630
- })).ok;
1631
- } catch {
1632
- return false;
2555
+ const res = await rpc.call("sessionTier", scope);
2556
+ if (!res.ok) console.error("[Qoder context tier] Failed to save selection", {
2557
+ ...scope,
2558
+ error: res.error
2559
+ });
2560
+ return res.ok ? res.value : void 0;
2561
+ } catch (error) {
2562
+ console.error("[Qoder context tier] Failed to save selection", {
2563
+ ...scope,
2564
+ error
2565
+ });
2566
+ return;
1633
2567
  }
1634
2568
  },
1635
- subscribe: (listener) => ctx.remote.$on("credentials/reference-updated", (ref) => {
1636
- if (ref === "QODER_MANAGED_CREDENTIAL") listener();
1637
- })
2569
+ readSessionTier: async (scope) => {
2570
+ try {
2571
+ const result = await rpc.call("readSessionTier", scope);
2572
+ if (!result.ok) console.error("[Qoder context tier] Failed to read selection", {
2573
+ ...scope,
2574
+ error: result.error
2575
+ });
2576
+ return result.ok ? result.value : void 0;
2577
+ } catch (error) {
2578
+ console.error("[Qoder context tier] Failed to read selection", {
2579
+ ...scope,
2580
+ error
2581
+ });
2582
+ return;
2583
+ }
2584
+ },
2585
+ subscribeSessionTiers: (scope, listener) => tierEvents.subscribe(scope, listener),
2586
+ subscribe: (listener) => {
2587
+ const reference = ctx.remote.$on("credentials/reference-updated", (ref) => {
2588
+ if (ref === "QODER_MANAGED_CREDENTIAL") listener();
2589
+ });
2590
+ const record = ctx.remote.$on("credentials/record-updated", (key) => {
2591
+ if (key === `provider-qoder/${regionOf(readModelSnapshot().value)}`) listener();
2592
+ });
2593
+ return () => {
2594
+ reference();
2595
+ record();
2596
+ };
2597
+ }
1638
2598
  };
1639
2599
  const rawT = ctx.locale.bind(localeNamespace);
1640
2600
  const t = (key, values) => {
@@ -1646,6 +2606,17 @@ window.__ModuleLoader__.load({
1646
2606
  * switch re-resolves provider copy instead of reusing a setup-time snapshot.
1647
2607
  */
1648
2608
  const activeLocale = () => ctx.locale.getLocale().active;
2609
+ ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
2610
+ name: "conversation.input.dock",
2611
+ id: "qoder-model-queue",
2612
+ order: 30,
2613
+ locale: localeNamespace,
2614
+ inject: (sessionId) => ({
2615
+ sessionId,
2616
+ rpc,
2617
+ t
2618
+ })
2619
+ }, QoderQueueStatus));
1649
2620
  ctx.slots.inject("settings.section", () => ctx.slots.register({
1650
2621
  name: "settings.section",
1651
2622
  id: "qoder",