dsh-provider-qoder 0.2.0 → 0.3.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
@@ -14,8 +14,186 @@ window.__ModuleLoader__.load({
14
14
  return source === "env" || source?.endsWith("-env") === true;
15
15
  }
16
16
  //#endregion
17
+ //#region src/client/locales.ts
18
+ const zh = {
19
+ nav: "Qoder",
20
+ title: "Qoder 凭据",
21
+ configured: "已配置",
22
+ missing: "未配置",
23
+ loading: "正在读取凭据状态…",
24
+ loadFailed: "无法读取凭据状态。",
25
+ retry: "重试",
26
+ edit: "编辑",
27
+ cancel: "取消",
28
+ regionLabel: "服务区域",
29
+ regionGlobal: "国际版 (Global)",
30
+ regionChina: "中国区 (CN)",
31
+ regionHint: "请确保 PAT 与所选服务区域匹配。",
32
+ tokenLabel: "API 密钥",
33
+ tokenPlaceholder: "输入新的 Personal Access Token",
34
+ configuredHint: "已配置——输入新值可替换",
35
+ readOnly: "当前凭据存储不可写。",
36
+ save: "保存",
37
+ saving: "正在保存…",
38
+ saved: "Qoder 设置已保存。",
39
+ saveFailed: "Qoder 设置保存失败,请重试。",
40
+ remove: "删除",
41
+ removing: "正在删除…",
42
+ removeFailed: "凭据删除失败,请重试。",
43
+ confirmRemove: "删除已保存的 Qoder 凭据?删除后,新的模型请求将无法运行。",
44
+ customized: "自定义设置",
45
+ modelsTitle: "模型目录",
46
+ modelsLoading: "正在读取模型设置…",
47
+ modelsUnavailable: "当前无法读取或保存模型设置。",
48
+ modelsEnabled: "已启用 {count} 个模型",
49
+ modelsFetch: "获取可用模型",
50
+ modelsFetching: "正在获取…",
51
+ modelsFetchFailed: "无法获取可用模型。",
52
+ modelsRequired: "至少需要保留一个模型。",
53
+ modelUnavailable: "不可用",
54
+ modelRate: "{value}x",
55
+ modelRateUnknown: "倍率未知",
56
+ accountTitle: "Qoder",
57
+ accountLoading: "正在读取账号与额度…",
58
+ accountFailed: "无法读取账号信息与额度。",
59
+ accountCredentialHint: "请先在“模型”设置中配置 Qoder Personal Access Token。",
60
+ refresh: "刷新",
61
+ refreshing: "正在刷新…",
62
+ userQuotaTitle: "个人额度",
63
+ orgResourceTitle: "组织资源包",
64
+ dedicatedResourceTitle: "专属资源包",
65
+ dedicatedResourceExpiresAt: "有效期至:{value}",
66
+ quotaRemaining: "剩余 {value}%",
67
+ quotaUsed: "已用 {used} / {total} {unit}",
68
+ resetsAt: "重置时间:{value}",
69
+ noQuota: "暂无可用额度信息",
70
+ quotaExceeded: "当前订阅额度已用尽。",
71
+ plan: "套餐计划",
72
+ planExpiresAt: "有效期至:{value}",
73
+ organization: "组织:{name}",
74
+ accountSuspended: "当前账号或组织已被暂停/冻结。",
75
+ searchModeLabel: "联网搜索模式",
76
+ searchModeAuto: "智能路由 (自动)",
77
+ searchModeAlways: "始终使用 Qoder",
78
+ searchModeDisabled: "禁用",
79
+ searchModeHint: "智能路由模式下,使用 Qoder 模型时走 Qoder 搜索,使用其他模型时自动回退给备用搜索。"
80
+ };
81
+ const en = {
82
+ nav: "Qoder",
83
+ title: "Qoder credential",
84
+ configured: "Configured",
85
+ missing: "Not configured",
86
+ loading: "Reading credential status…",
87
+ loadFailed: "Could not read credential status.",
88
+ retry: "Retry",
89
+ edit: "Edit",
90
+ cancel: "Cancel",
91
+ regionLabel: "Service Region",
92
+ regionGlobal: "Global",
93
+ regionChina: "China (CN)",
94
+ regionHint: "Ensure your PAT matches the selected service region.",
95
+ tokenLabel: "API Key",
96
+ tokenPlaceholder: "Enter a new Personal Access Token",
97
+ configuredHint: "Configured — enter a new value to replace",
98
+ readOnly: "The current credential store is read-only.",
99
+ save: "Save",
100
+ saving: "Saving…",
101
+ saved: "Qoder settings saved.",
102
+ saveFailed: "Could not save the Qoder settings. Try again.",
103
+ remove: "Remove",
104
+ removing: "Removing…",
105
+ removeFailed: "Could not remove the credential. Try again.",
106
+ confirmRemove: "Remove the stored Qoder credential? New model requests will stop working.",
107
+ customized: "Custom settings",
108
+ modelsTitle: "Model catalog",
109
+ modelsLoading: "Reading model settings…",
110
+ modelsUnavailable: "Model settings cannot currently be read or saved.",
111
+ modelsEnabled: "{count} models enabled",
112
+ modelsFetch: "Fetch available models",
113
+ modelsFetching: "Fetching…",
114
+ modelsFetchFailed: "Could not fetch available models.",
115
+ modelsRequired: "Keep at least one model.",
116
+ modelUnavailable: "Unavailable",
117
+ modelRate: "{value}x",
118
+ modelRateUnknown: "Rate unknown",
119
+ accountTitle: "Qoder",
120
+ accountLoading: "Reading account and quota…",
121
+ accountFailed: "Could not read account details or quota.",
122
+ accountCredentialHint: "Configure the Qoder Personal Access Token in Models settings first.",
123
+ refresh: "Refresh",
124
+ refreshing: "Refreshing…",
125
+ userQuotaTitle: "Personal Quota",
126
+ orgResourceTitle: "Org Resource Package",
127
+ dedicatedResourceTitle: "Dedicated Resource Package",
128
+ dedicatedResourceExpiresAt: "Expires at: {value}",
129
+ quotaRemaining: "{value}% remaining",
130
+ quotaUsed: "Used {used} / {total} {unit}",
131
+ resetsAt: "Resets: {value}",
132
+ noQuota: "No quota metrics available",
133
+ quotaExceeded: "The current subscription quota is exhausted.",
134
+ plan: "Plan",
135
+ planExpiresAt: "Expires at: {value}",
136
+ organization: "Org: {name}",
137
+ accountSuspended: "Account or organization is currently suspended.",
138
+ searchModeLabel: "Web Search Mode",
139
+ searchModeAuto: "Smart Routing (Auto)",
140
+ searchModeAlways: "Always use Qoder",
141
+ searchModeDisabled: "Disabled",
142
+ searchModeHint: "In auto mode, searches route to Qoder when using Qoder models, and fall back to ambient providers otherwise."
143
+ };
144
+ /**
145
+ * Compare one provider language tag against the requested locale.
146
+ *
147
+ * `prefix` additionally accepts a shared primary subtag, which is what lets the
148
+ * built-in `zh`/`en` ids reach the provider's `zh-CN`/`en-US` keys.
149
+ */
150
+ function localeMatches(tag, requested, prefix) {
151
+ const normalized = tag.trim().toLowerCase();
152
+ if (normalized === requested) return true;
153
+ if (!prefix) return false;
154
+ const primary = requested.split("-")[0];
155
+ return primary.length > 0 && normalized.split("-")[0] === primary;
156
+ }
157
+ /**
158
+ * Return the first provider translation whose language tag matches the request.
159
+ *
160
+ * `prefix` decides whether a shared primary subtag counts as a match; blank
161
+ * translations are skipped so they cannot shadow a later usable one.
162
+ */
163
+ function pickLocalizedValue(values, requested, prefix) {
164
+ for (const [tag, value] of Object.entries(values)) {
165
+ if (!localeMatches(tag, requested, prefix)) continue;
166
+ const text = typeof value === "string" ? value.trim() : "";
167
+ if (text.length > 0) return text;
168
+ }
169
+ }
170
+ /**
171
+ * Resolve provider copy for the active UI locale.
172
+ *
173
+ * Order: exact language tag, then a shared primary subtag, then the provider's
174
+ * language-neutral fallback, then any translation at all. The client owns this
175
+ * choice because only it knows the locale the user is reading.
176
+ */
177
+ function resolveLocalizedText(text, locale) {
178
+ if (!text) return void 0;
179
+ const values = text.values ?? {};
180
+ const requested = typeof locale === "string" ? locale.trim().toLowerCase() : "";
181
+ if (requested.length > 0) {
182
+ const exact = pickLocalizedValue(values, requested, false);
183
+ if (exact !== void 0) return exact;
184
+ const related = pickLocalizedValue(values, requested, true);
185
+ if (related !== void 0) return related;
186
+ }
187
+ const fallback = typeof text.fallback === "string" ? text.fallback.trim() : "";
188
+ if (fallback.length > 0) return fallback;
189
+ for (const value of Object.values(values)) {
190
+ const candidate = typeof value === "string" ? value.trim() : "";
191
+ if (candidate.length > 0) return candidate;
192
+ }
193
+ }
194
+ //#endregion
17
195
  //#region \0dsh-css:/home/runner/work/dsh-provider-qoder/dsh-provider-qoder/src/client/QoderCredentialCard.module.css.mjs
18
- const css = ".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:1px solid var(--dsw-alias-border-l2);border-radius: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-top:1px solid var(--dsw-alias-border-l2);margin-top:14px;padding-top:10px}.SRqrhW_customizedSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display: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_customizedBody{padding-top:12px}.SRqrhW_modelCatalog,.SRqrhW_modelList{flex-direction:column;gap:10px;display:flex}.SRqrhW_modelCatalogHead{justify-content:space-between;align-items:flex-start;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{cursor:pointer;border-radius:6px;grid-template-columns:auto minmax(0,1fr) auto auto auto;align-items:center;gap:8px;padding:6px 8px;font-size:13px;display:grid}.SRqrhW_modelChoice:hover{background:var(--dsw-alias-interactive-bg-hover)}.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{background:var(--dsw-alias-bg-module-platform);border-radius:8px;flex-direction:column;gap:10px;margin-top:12px;padding:12px;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_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}";
196
+ const css = ".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:1px solid var(--dsw-alias-border-l2);border-radius: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-top:1px solid var(--dsw-alias-border-l2);margin-top:14px;padding-top:10px}.SRqrhW_customizedSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display: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_customizedBody{padding-top:12px}.SRqrhW_modelCatalog,.SRqrhW_modelList{flex-direction:column;gap:10px;display:flex}.SRqrhW_modelCatalogHead{justify-content:space-between;align-items:flex-start;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{cursor:pointer;border-radius:6px;grid-template-columns:auto minmax(0,1fr) auto auto auto;align-items:center;gap:8px;padding:6px 8px;font-size:13px;display:grid}.SRqrhW_modelChoice:hover{background:var(--dsw-alias-interactive-bg-hover)}.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{background:var(--dsw-alias-bg-module-platform);border-radius:8px;flex-direction:column;gap:10px;margin-top:12px;padding:12px;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-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:8px;margin-top:16px;padding-top:14px;display:flex}.SRqrhW_searchModeHintText{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:12px;line-height:18px}";
19
197
  const tagId = "dsh-provider-qoder/QoderCredentialCard.module.css";
20
198
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
21
199
  const tag = document.createElement("style");
@@ -67,6 +245,7 @@ window.__ModuleLoader__.load({
67
245
  "planBadge": "SRqrhW_planBadge",
68
246
  "primary": "SRqrhW_primary",
69
247
  "quotaBlock": "SRqrhW_quotaBlock",
248
+ "quotaDescription": "SRqrhW_quotaDescription",
70
249
  "quotaHeader": "SRqrhW_quotaHeader",
71
250
  "quotaLabel": "SRqrhW_quotaLabel",
72
251
  "quotaMeta": "SRqrhW_quotaMeta",
@@ -79,6 +258,8 @@ window.__ModuleLoader__.load({
79
258
  "regionOptionActive": "SRqrhW_regionOptionActive",
80
259
  "regionOptionDisabled": "SRqrhW_regionOptionDisabled",
81
260
  "regionSelector": "SRqrhW_regionSelector",
261
+ "searchModeHintText": "SRqrhW_searchModeHintText",
262
+ "searchModeSection": "SRqrhW_searchModeSection",
82
263
  "secondary": "SRqrhW_secondary",
83
264
  "status": "SRqrhW_status",
84
265
  "success": "SRqrhW_success",
@@ -102,12 +283,17 @@ window.__ModuleLoader__.load({
102
283
  minute: "2-digit"
103
284
  });
104
285
  }
105
- function QoderAccountCard({ operations, t }) {
286
+ /**
287
+ * Settings card for the Qoder subscription: credential state, subscriber
288
+ * profile, and every quota block the provider reports.
289
+ */
290
+ function QoderAccountCard({ operations, t, activeLocale }) {
106
291
  const [credentialState, setCredentialState] = (0, react.useState)({ status: "loading" });
107
292
  const [accountState, setAccountState] = (0, react.useState)({ status: "idle" });
108
293
  const [refreshing, setRefreshing] = (0, react.useState)(false);
109
294
  const [credentialRevision, setCredentialRevision] = (0, react.useState)(0);
110
295
  const latestAccountRequest = (0, react.useRef)(0);
296
+ const modelSnapshot = (0, react.useSyncExternalStore)(operations.subscribeModels, operations.getModelSnapshot, operations.getModelSnapshot);
111
297
  const loadCredential = (0, react.useCallback)(async () => {
112
298
  const info = await operations.describe();
113
299
  setCredentialState(info === void 0 ? { status: "failed" } : {
@@ -122,13 +308,13 @@ window.__ModuleLoader__.load({
122
308
  try {
123
309
  const result = await operations.getAccount(force);
124
310
  if (requestId !== latestAccountRequest.current) return;
125
- if (result?.ok) setAccountState({
311
+ if (result && result.ok) setAccountState({
126
312
  status: "ready",
127
- account: result.data
313
+ account: result.value
128
314
  });
129
315
  else setAccountState({
130
316
  status: "failed",
131
- error: result?.error
317
+ error: result?.error?.message
132
318
  });
133
319
  } catch (error) {
134
320
  if (requestId !== latestAccountRequest.current) return;
@@ -179,9 +365,17 @@ window.__ModuleLoader__.load({
179
365
  children: t("retry")
180
366
  })]
181
367
  });
182
- const renderQuota = (label, quota, resetDate) => {
368
+ /**
369
+ * Render one quota block.
370
+ *
371
+ * `expiresAt` wins over `resetDate` when both are supplied, because a
372
+ * dedicated package expires on its own date rather than on the subscription
373
+ * reset horizon. A package with no size renders nothing.
374
+ */
375
+ const renderQuota = (key, label, quota, options) => {
183
376
  if (!quota || quota.total <= 0) return null;
184
377
  const remainingPercent = Math.max(0, Math.min(100, Math.round(quota.remaining / quota.total * 100)));
378
+ const meta = options?.expiresAt ? t("dedicatedResourceExpiresAt", { value: options.expiresAt }) : options?.resetDate ? t("resetsAt", { value: options.resetDate }) : void 0;
185
379
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
186
380
  className: QoderCredentialCard_module_css_default.quotaBlock,
187
381
  children: [
@@ -200,18 +394,22 @@ window.__ModuleLoader__.load({
200
394
  unit: quota.unit
201
395
  }) })
202
396
  }),
397
+ options?.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
398
+ className: QoderCredentialCard_module_css_default.quotaDescription,
399
+ children: options.description
400
+ }) : null,
203
401
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
204
402
  className: QoderCredentialCard_module_css_default.quotaProgress,
205
403
  max: quota.total,
206
404
  value: Math.min(quota.total, quota.used),
207
405
  "aria-label": `${label} ${t("quotaRemaining", { value: remainingPercent })}`
208
406
  }),
209
- resetDate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
407
+ meta ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
210
408
  className: QoderCredentialCard_module_css_default.quotaMeta,
211
- children: t("resetsAt", { value: resetDate })
409
+ children: meta
212
410
  }) : null
213
411
  ]
214
- }, label);
412
+ }, key);
215
413
  };
216
414
  const renderAccount = () => {
217
415
  if (!configured) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -249,10 +447,13 @@ window.__ModuleLoader__.load({
249
447
  const { profile, usage, plan } = accountState.account;
250
448
  const userQuota = usage?.userQuota;
251
449
  const orgPackage = usage?.orgResourcePackage;
450
+ const dedicatedPackages = usage?.dedicatedResourcePackages ?? [];
451
+ const locale = activeLocale();
252
452
  const avatarInitial = (profile.name || profile.email || "Q").charAt(0).toUpperCase();
253
453
  const resetDate = formatResetDate(usage?.expiresAt);
254
454
  const planExpiry = plan?.endDate ? formatResetDate(plan.endDate) : void 0;
255
455
  const isSuspended = plan?.organization?.isSuspended === true;
456
+ const hasQuota = (userQuota?.total ?? 0) > 0 || (orgPackage?.total ?? 0) > 0 || dedicatedPackages.some((pkg) => pkg.total > 0);
256
457
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
257
458
  className: QoderCredentialCard_module_css_default.accountSection,
258
459
  children: [
@@ -307,28 +508,100 @@ window.__ModuleLoader__.load({
307
508
  className: QoderCredentialCard_module_css_default.error,
308
509
  children: t("quotaExceeded")
309
510
  }) : null,
310
- renderQuota(t("userQuotaTitle"), userQuota, resetDate),
311
- renderQuota(t("orgResourceTitle"), orgPackage, resetDate),
312
- (!userQuota || userQuota.total <= 0) && (!orgPackage || orgPackage.total <= 0) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
511
+ renderQuota("userQuota", t("userQuotaTitle"), userQuota, { resetDate }),
512
+ renderQuota("orgResource", t("orgResourceTitle"), orgPackage, { resetDate }),
513
+ dedicatedPackages.map((pkg, index) => renderQuota(`dedicatedResource-${pkg.id ?? index}`, resolveLocalizedText(pkg.title, locale) ?? t("dedicatedResourceTitle"), pkg, {
514
+ expiresAt: formatResetDate(pkg.expiresAt),
515
+ description: resolveLocalizedText(pkg.description, locale)
516
+ })),
517
+ hasQuota ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
313
518
  className: QoderCredentialCard_module_css_default.accountEmptyText,
314
519
  children: t("noQuota")
315
- }) : null
520
+ })
521
+ ]
522
+ });
523
+ };
524
+ const webSearchMode = modelSnapshot.value?.webSearchMode ?? "auto";
525
+ const canModifySearchMode = modelSnapshot.status === "ready" && modelSnapshot.writable;
526
+ const handleSearchModeChange = async (mode) => {
527
+ if (!canModifySearchMode || mode === webSearchMode) return;
528
+ await operations.storeWebSearchMode(mode);
529
+ };
530
+ const renderWebSearchSection = () => {
531
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
532
+ className: QoderCredentialCard_module_css_default.searchModeSection,
533
+ children: [
534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
535
+ className: QoderCredentialCard_module_css_default.quotaHeader,
536
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
537
+ className: QoderCredentialCard_module_css_default.quotaLabel,
538
+ children: t("searchModeLabel")
539
+ })
540
+ }),
541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
542
+ className: QoderCredentialCard_module_css_default.regionGroup,
543
+ role: "radiogroup",
544
+ "aria-label": t("searchModeLabel"),
545
+ children: [
546
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
547
+ type: "button",
548
+ role: "radio",
549
+ "aria-checked": webSearchMode === "auto",
550
+ className: `${QoderCredentialCard_module_css_default.regionOption} ${webSearchMode === "auto" ? QoderCredentialCard_module_css_default.regionOptionActive : ""}`,
551
+ disabled: !canModifySearchMode,
552
+ onClick: () => {
553
+ handleSearchModeChange("auto");
554
+ },
555
+ children: t("searchModeAuto")
556
+ }),
557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
558
+ type: "button",
559
+ role: "radio",
560
+ "aria-checked": webSearchMode === "always",
561
+ className: `${QoderCredentialCard_module_css_default.regionOption} ${webSearchMode === "always" ? QoderCredentialCard_module_css_default.regionOptionActive : ""}`,
562
+ disabled: !canModifySearchMode,
563
+ onClick: () => {
564
+ handleSearchModeChange("always");
565
+ },
566
+ children: t("searchModeAlways")
567
+ }),
568
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
569
+ type: "button",
570
+ role: "radio",
571
+ "aria-checked": webSearchMode === "disabled",
572
+ className: `${QoderCredentialCard_module_css_default.regionOption} ${webSearchMode === "disabled" ? QoderCredentialCard_module_css_default.regionOptionActive : ""}`,
573
+ disabled: !canModifySearchMode,
574
+ onClick: () => {
575
+ handleSearchModeChange("disabled");
576
+ },
577
+ children: t("searchModeDisabled")
578
+ })
579
+ ]
580
+ }),
581
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
582
+ className: QoderCredentialCard_module_css_default.searchModeHintText,
583
+ children: t("searchModeHint")
584
+ })
316
585
  ]
317
586
  });
318
587
  };
319
588
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
320
589
  className: QoderCredentialCard_module_css_default.credential,
321
590
  "aria-label": t("accountTitle"),
322
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
323
- className: QoderCredentialCard_module_css_default.head,
324
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
325
- className: QoderCredentialCard_module_css_default.heading,
326
- children: t("accountTitle")
327
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
328
- className: configured ? `${QoderCredentialCard_module_css_default.status} ${QoderCredentialCard_module_css_default.configured}` : QoderCredentialCard_module_css_default.status,
329
- children: configured ? t("configured") : t("missing")
330
- })]
331
- }), renderAccount()]
591
+ children: [
592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
593
+ className: QoderCredentialCard_module_css_default.head,
594
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
595
+ className: QoderCredentialCard_module_css_default.heading,
596
+ children: t("accountTitle")
597
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
598
+ className: configured ? `${QoderCredentialCard_module_css_default.status} ${QoderCredentialCard_module_css_default.configured}` : QoderCredentialCard_module_css_default.status,
599
+ children: configured ? t("configured") : t("missing")
600
+ })]
601
+ }),
602
+ renderAccount(),
603
+ renderWebSearchSection()
604
+ ]
332
605
  });
333
606
  }
334
607
  //#endregion
@@ -421,10 +694,10 @@ window.__ModuleLoader__.load({
421
694
  const result = await operations.discoverModels();
422
695
  setFetching(false);
423
696
  if (!result.ok) {
424
- setFailure(result.error || t("modelsFetchFailed"));
697
+ setFailure(result.error.message || t("modelsFetchFailed"));
425
698
  return;
426
699
  }
427
- const reconciled = reconcileQoderModels(catalog ?? models, result.data);
700
+ const reconciled = reconcileQoderModels(catalog ?? models, result.value);
428
701
  setCatalog(reconciled.catalog);
429
702
  setUnavailableIds(reconciled.unavailableIds);
430
703
  onChange(reconciled.selected);
@@ -791,131 +1064,109 @@ window.__ModuleLoader__.load({
791
1064
  ]
792
1065
  });
793
1066
  }
1067
+ /**
1068
+ * Absolute Fetch-route prefix. Connection mounts its shared, authenticated API
1069
+ * channel at `/api`, and every exact Fetch route lives directly below it.
1070
+ */
1071
+ const qoderRpcApiPath = `/api/qoder-subscription`;
1072
+ /** Absolute Fetch-route path of one endpoint. */
1073
+ function qoderRpcPath(endpoint) {
1074
+ return `${qoderRpcApiPath}/${endpoint}`;
1075
+ }
1076
+ /** Standard diagnostic codes carried in a settings RPC failure envelope. */
1077
+ const qoderRpcErrorCodes = [
1078
+ "NO_CREDENTIALS",
1079
+ "UNAUTHENTICATED",
1080
+ "UPSTREAM_ERROR",
1081
+ "TIMEOUT",
1082
+ "ABORTED",
1083
+ "UNKNOWN_ENDPOINT",
1084
+ "INTERNAL"
1085
+ ];
1086
+ /** Type guard for an untrusted error code. */
1087
+ function isQoderRpcErrorCode(value) {
1088
+ return typeof value === "string" && qoderRpcErrorCodes.includes(value);
1089
+ }
794
1090
  //#endregion
795
- //#region src/client/locales.ts
796
- const zh = {
797
- nav: "Qoder",
798
- title: "Qoder 凭据",
799
- configured: "已配置",
800
- missing: "未配置",
801
- loading: "正在读取凭据状态…",
802
- loadFailed: "无法读取凭据状态。",
803
- retry: "重试",
804
- edit: "编辑",
805
- cancel: "取消",
806
- regionLabel: "服务区域",
807
- regionGlobal: "国际版 (Global)",
808
- regionChina: "中国区 (CN)",
809
- regionHint: "请确保 PAT 与所选服务区域匹配。",
810
- tokenLabel: "API 密钥",
811
- tokenPlaceholder: "输入新的 Personal Access Token",
812
- configuredHint: "已配置——输入新值可替换",
813
- readOnly: "当前凭据存储不可写。",
814
- save: "保存",
815
- saving: "正在保存…",
816
- saved: "Qoder 设置已保存。",
817
- saveFailed: "Qoder 设置保存失败,请重试。",
818
- remove: "删除",
819
- removing: "正在删除…",
820
- removeFailed: "凭据删除失败,请重试。",
821
- confirmRemove: "删除已保存的 Qoder 凭据?删除后,新的模型请求将无法运行。",
822
- customized: "自定义设置",
823
- modelsTitle: "模型目录",
824
- modelsLoading: "正在读取模型设置…",
825
- modelsUnavailable: "当前无法读取或保存模型设置。",
826
- modelsEnabled: "已启用 {count} 个模型",
827
- modelsFetch: "获取可用模型",
828
- modelsFetching: "正在获取…",
829
- modelsFetchFailed: "无法获取可用模型。",
830
- modelsRequired: "至少需要保留一个模型。",
831
- modelUnavailable: "不可用",
832
- modelRate: "{value}x",
833
- modelRateUnknown: "倍率未知",
834
- accountTitle: "Qoder",
835
- accountLoading: "正在读取账号与额度…",
836
- accountFailed: "无法读取账号信息与额度。",
837
- accountCredentialHint: "请先在“模型”设置中配置 Qoder Personal Access Token。",
838
- refresh: "刷新",
839
- refreshing: "正在刷新…",
840
- userQuotaTitle: "个人额度",
841
- orgResourceTitle: "组织资源包",
842
- quotaRemaining: "剩余 {value}%",
843
- quotaUsed: "已用 {used} / {total} {unit}",
844
- resetsAt: "重置时间:{value}",
845
- noQuota: "暂无可用额度信息",
846
- quotaExceeded: "当前订阅额度已用尽。",
847
- plan: "套餐计划",
848
- planExpiresAt: "有效期至:{value}",
849
- organization: "组织:{name}",
850
- accountSuspended: "当前账号或组织已被暂停/冻结。"
851
- };
852
- const en = {
853
- nav: "Qoder",
854
- title: "Qoder credential",
855
- configured: "Configured",
856
- missing: "Not configured",
857
- loading: "Reading credential status…",
858
- loadFailed: "Could not read credential status.",
859
- retry: "Retry",
860
- edit: "Edit",
861
- cancel: "Cancel",
862
- regionLabel: "Service Region",
863
- regionGlobal: "Global",
864
- regionChina: "China (CN)",
865
- regionHint: "Ensure your PAT matches the selected service region.",
866
- tokenLabel: "API Key",
867
- tokenPlaceholder: "Enter a new Personal Access Token",
868
- configuredHint: "Configured — enter a new value to replace",
869
- readOnly: "The current credential store is read-only.",
870
- save: "Save",
871
- saving: "Saving…",
872
- saved: "Qoder settings saved.",
873
- saveFailed: "Could not save the Qoder settings. Try again.",
874
- remove: "Remove",
875
- removing: "Removing…",
876
- removeFailed: "Could not remove the credential. Try again.",
877
- confirmRemove: "Remove the stored Qoder credential? New model requests will stop working.",
878
- customized: "Custom settings",
879
- modelsTitle: "Model catalog",
880
- modelsLoading: "Reading model settings…",
881
- modelsUnavailable: "Model settings cannot currently be read or saved.",
882
- modelsEnabled: "{count} models enabled",
883
- modelsFetch: "Fetch available models",
884
- modelsFetching: "Fetching…",
885
- modelsFetchFailed: "Could not fetch available models.",
886
- modelsRequired: "Keep at least one model.",
887
- modelUnavailable: "Unavailable",
888
- modelRate: "{value}x",
889
- modelRateUnknown: "Rate unknown",
890
- accountTitle: "Qoder",
891
- accountLoading: "Reading account and quota…",
892
- accountFailed: "Could not read account details or quota.",
893
- accountCredentialHint: "Configure the Qoder Personal Access Token in Models settings first.",
894
- refresh: "Refresh",
895
- refreshing: "Refreshing…",
896
- userQuotaTitle: "Personal Quota",
897
- orgResourceTitle: "Org Resource Package",
898
- quotaRemaining: "{value}% remaining",
899
- quotaUsed: "Used {used} / {total} {unit}",
900
- resetsAt: "Resets: {value}",
901
- noQuota: "No quota metrics available",
902
- quotaExceeded: "The current subscription quota is exhausted.",
903
- plan: "Plan",
904
- planExpiresAt: "Expires at: {value}",
905
- organization: "Org: {name}",
906
- accountSuspended: "Account or organization is currently suspended."
907
- };
1091
+ //#region src/client/rpc-client.ts
1092
+ /**
1093
+ * Browser caller for the Qoder settings RPC.
1094
+ *
1095
+ * Requests go to Connection's shared `/api` channel, which the connection
1096
+ * plugin mounts itself and guards with its Host/Origin fence and browser
1097
+ * authentication; the caller only has to carry the session cookie.
1098
+ */
1099
+ /**
1100
+ * Create the caller used by the account card and the model catalog.
1101
+ *
1102
+ * Failures are returned rather than thrown so the cards keep their existing
1103
+ * "result or undefined" handling: a transport status becomes a transport
1104
+ * failure message, and an endpoint failure keeps the provider's own message.
1105
+ */
1106
+ function createQoderRpcCaller(transport = {}) {
1107
+ const send = transport.fetch ?? ((input, init) => globalThis.fetch(input, init));
1108
+ return { async call(endpoint, payload, signal) {
1109
+ const path = qoderRpcPath(endpoint);
1110
+ try {
1111
+ const response = await send(path, {
1112
+ method: "POST",
1113
+ headers: { "content-type": "application/json" },
1114
+ credentials: "include",
1115
+ body: JSON.stringify(payload ?? {}),
1116
+ ...signal === void 0 ? {} : { signal }
1117
+ });
1118
+ if (!response.ok) return {
1119
+ ok: false,
1120
+ error: {
1121
+ code: signal?.aborted ? "ABORTED" : "UPSTREAM_ERROR",
1122
+ message: `transport failure for ${path}: HTTP ${response.status}`
1123
+ }
1124
+ };
1125
+ const envelope = await response.json();
1126
+ if (envelope?.ok === true) return {
1127
+ ok: true,
1128
+ value: envelope.value
1129
+ };
1130
+ const rawError = envelope?.error;
1131
+ let code = "INTERNAL";
1132
+ let message = "RPC returned error";
1133
+ let details;
1134
+ if (typeof rawError === "string") message = rawError;
1135
+ else if (typeof rawError === "object" && rawError !== null) {
1136
+ const errObj = rawError;
1137
+ if (isQoderRpcErrorCode(errObj.code)) code = errObj.code;
1138
+ if (typeof errObj.message === "string") message = errObj.message;
1139
+ if (typeof errObj.details === "object" && errObj.details !== null) details = errObj.details;
1140
+ }
1141
+ if (code === "INTERNAL" && signal?.aborted) code = "ABORTED";
1142
+ return {
1143
+ ok: false,
1144
+ error: {
1145
+ code,
1146
+ message,
1147
+ ...details === void 0 ? {} : { details }
1148
+ }
1149
+ };
1150
+ } catch (error) {
1151
+ return {
1152
+ ok: false,
1153
+ error: {
1154
+ code: signal?.aborted || error instanceof Error && error.name === "AbortError" ? "ABORTED" : "INTERNAL",
1155
+ message: error instanceof Error ? error.message : String(error)
1156
+ }
1157
+ };
1158
+ }
1159
+ } };
1160
+ }
908
1161
  //#endregion
909
1162
  //#region src/client/index.ts
910
1163
  const localeNamespace = "settings.qoderCredential";
911
- const qoderChannel = "/qoder-subscription";
912
1164
  const settingsNamespace = "provider-qoder";
913
1165
  const inject = [
914
1166
  "slots",
915
1167
  "locale",
916
1168
  "remote",
917
1169
  "remote.credentials",
918
- "connection",
919
1170
  "settingsScope"
920
1171
  ];
921
1172
  const fill = (text, values) => {
@@ -928,7 +1179,7 @@ window.__ModuleLoader__.load({
928
1179
  en
929
1180
  }), "provider-qoder: credential copy");
930
1181
  const credentials = ctx.remote.credentials;
931
- const connection = ctx.connection;
1182
+ const rpc = createQoderRpcCaller();
932
1183
  const modelScope = ctx.settingsScope.bind({ namespace: settingsNamespace });
933
1184
  const operations = {
934
1185
  describe: async () => {
@@ -953,24 +1204,7 @@ window.__ModuleLoader__.load({
953
1204
  return false;
954
1205
  }
955
1206
  },
956
- getAccount: async (force) => {
957
- try {
958
- const response = await connection.rpc.call(qoderChannel, "account", { force });
959
- if (response.ok) return {
960
- ok: true,
961
- data: response.value
962
- };
963
- return {
964
- ok: false,
965
- error: response.error?.message || "RPC returned error"
966
- };
967
- } catch (error) {
968
- return {
969
- ok: false,
970
- error: error instanceof Error ? error.message : String(error)
971
- };
972
- }
973
- },
1207
+ getAccount: async (force) => await rpc.call("account", { force }),
974
1208
  getModelSnapshot: () => modelScope.getSnapshot(),
975
1209
  subscribeModels: (listener) => modelScope.subscribe(listener),
976
1210
  storeModels: async (region, models) => {
@@ -993,24 +1227,15 @@ window.__ModuleLoader__.load({
993
1227
  return false;
994
1228
  }
995
1229
  },
996
- discoverModels: async () => {
1230
+ storeWebSearchMode: async (mode) => {
997
1231
  try {
998
- const response = await connection.rpc.call(qoderChannel, "models", {});
999
- if (response.ok) return {
1000
- ok: true,
1001
- data: response.value
1002
- };
1003
- return {
1004
- ok: false,
1005
- error: response.error?.message || "RPC returned error"
1006
- };
1007
- } catch (error) {
1008
- return {
1009
- ok: false,
1010
- error: error instanceof Error ? error.message : String(error)
1011
- };
1232
+ await modelScope.set("webSearchMode", mode);
1233
+ return true;
1234
+ } catch {
1235
+ return false;
1012
1236
  }
1013
1237
  },
1238
+ discoverModels: async () => await rpc.call("models", {}),
1014
1239
  subscribe: (listener) => ctx.remote.$on("credentials/reference-updated", (ref) => {
1015
1240
  if (ref === "QODER_MANAGED_CREDENTIAL") listener();
1016
1241
  })
@@ -1020,6 +1245,11 @@ window.__ModuleLoader__.load({
1020
1245
  const raw = rawT(key);
1021
1246
  return fill(raw, values);
1022
1247
  };
1248
+ /**
1249
+ * Read the live UI locale. Cards call this during render, so a language
1250
+ * switch re-resolves provider copy instead of reusing a setup-time snapshot.
1251
+ */
1252
+ const activeLocale = () => ctx.locale.getLocale().active;
1023
1253
  ctx.slots.inject("settings.section", () => ctx.slots.register({
1024
1254
  name: "settings.section",
1025
1255
  id: "qoder",
@@ -1028,7 +1258,8 @@ window.__ModuleLoader__.load({
1028
1258
  locale: localeNamespace,
1029
1259
  inject: () => ({
1030
1260
  operations,
1031
- t
1261
+ t,
1262
+ activeLocale
1032
1263
  })
1033
1264
  }, QoderAccountCard));
1034
1265
  ctx.slots.inject("settings.models.footer", () => ctx.slots.register({
@@ -1037,7 +1268,8 @@ window.__ModuleLoader__.load({
1037
1268
  order: 15,
1038
1269
  inject: () => ({
1039
1270
  operations,
1040
- t
1271
+ t,
1272
+ activeLocale
1041
1273
  })
1042
1274
  }, QoderCredentialCard));
1043
1275
  }