dsh-provider-qoder 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js 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,145 +1064,131 @@ 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
- "remote.credentials",
918
- "connection",
919
- "settingsScope"
1169
+ "remote.credentials"
920
1170
  ];
921
1171
  const fill = (text, values) => {
922
1172
  if (!values) return text;
923
1173
  return Object.entries(values).reduce((next, [key, value]) => next.replace(new RegExp(`\\{${key}\\}`, "g"), String(value)), text);
924
1174
  };
925
1175
  function apply(ctx) {
1176
+ ctx.inject(["settingsScope"], (child) => {
1177
+ const scopes = child.settingsScope;
1178
+ mount(child, scopes.bind({ namespace: settingsNamespace }));
1179
+ });
1180
+ ctx.inject(["configForms"], (child) => {
1181
+ const forms = child.configForms;
1182
+ mount(child, forms.get(settingsNamespace));
1183
+ });
1184
+ }
1185
+ function mount(ctx, modelScope) {
926
1186
  ctx.effect(() => ctx.locale.register(localeNamespace, {
927
1187
  zh,
928
1188
  en
929
1189
  }), "provider-qoder: credential copy");
930
1190
  const credentials = ctx.remote.credentials;
931
- const connection = ctx.connection;
932
- const modelScope = ctx.settingsScope.bind({ namespace: settingsNamespace });
1191
+ const rpc = createQoderRpcCaller();
933
1192
  const operations = {
934
1193
  describe: async () => {
935
1194
  try {
@@ -953,64 +1212,35 @@ window.__ModuleLoader__.load({
953
1212
  return false;
954
1213
  }
955
1214
  },
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
- },
1215
+ getAccount: async (force) => await rpc.call("account", { force }),
974
1216
  getModelSnapshot: () => modelScope.getSnapshot(),
975
1217
  subscribeModels: (listener) => modelScope.subscribe(listener),
976
1218
  storeModels: async (region, models) => {
977
1219
  try {
978
1220
  const current = modelScope.getSnapshot().value;
979
- await modelScope.set("modelsByRegion", {
1221
+ return await modelScope.set("modelsByRegion", {
980
1222
  ...current?.modelsByRegion,
981
1223
  [region]: models
982
- });
983
- return true;
1224
+ }) !== false;
984
1225
  } catch {
985
1226
  return false;
986
1227
  }
987
1228
  },
988
1229
  storeRegion: async (region) => {
989
1230
  try {
990
- await modelScope.set("region", region);
991
- return true;
1231
+ return await modelScope.set("region", region) !== false;
992
1232
  } catch {
993
1233
  return false;
994
1234
  }
995
1235
  },
996
- discoverModels: async () => {
1236
+ storeWebSearchMode: async (mode) => {
997
1237
  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
- };
1238
+ return await modelScope.set("webSearchMode", mode) !== false;
1239
+ } catch {
1240
+ return false;
1012
1241
  }
1013
1242
  },
1243
+ discoverModels: async () => await rpc.call("models", {}),
1014
1244
  subscribe: (listener) => ctx.remote.$on("credentials/reference-updated", (ref) => {
1015
1245
  if (ref === "QODER_MANAGED_CREDENTIAL") listener();
1016
1246
  })
@@ -1020,6 +1250,11 @@ window.__ModuleLoader__.load({
1020
1250
  const raw = rawT(key);
1021
1251
  return fill(raw, values);
1022
1252
  };
1253
+ /**
1254
+ * Read the live UI locale. Cards call this during render, so a language
1255
+ * switch re-resolves provider copy instead of reusing a setup-time snapshot.
1256
+ */
1257
+ const activeLocale = () => ctx.locale.getLocale().active;
1023
1258
  ctx.slots.inject("settings.section", () => ctx.slots.register({
1024
1259
  name: "settings.section",
1025
1260
  id: "qoder",
@@ -1028,7 +1263,8 @@ window.__ModuleLoader__.load({
1028
1263
  locale: localeNamespace,
1029
1264
  inject: () => ({
1030
1265
  operations,
1031
- t
1266
+ t,
1267
+ activeLocale
1032
1268
  })
1033
1269
  }, QoderAccountCard));
1034
1270
  ctx.slots.inject("settings.models.footer", () => ctx.slots.register({
@@ -1037,7 +1273,8 @@ window.__ModuleLoader__.load({
1037
1273
  order: 15,
1038
1274
  inject: () => ({
1039
1275
  operations,
1040
- t
1276
+ t,
1277
+ activeLocale
1041
1278
  })
1042
1279
  }, QoderCredentialCard));
1043
1280
  }