dsh-opendesign-corouter 0.1.1 → 0.1.3

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
@@ -31,13 +31,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/client.ts
32
32
  var client_exports = {};
33
33
  __export(client_exports, {
34
+ Advanced: () => Advanced,
35
+ ModelRow: () => ModelRow,
36
+ Models: () => Models,
37
+ Overview: () => Overview,
34
38
  QuotaView: () => QuotaView,
35
39
  SettingsPage: () => SettingsPage,
36
40
  apply: () => apply,
41
+ formatTime: () => formatTime,
42
+ formatTokens: () => formatTokens,
37
43
  formatUsd: () => formatUsd,
38
44
  inject: () => inject,
45
+ modelEnabled: () => modelEnabled,
46
+ modelSettingsEntry: () => modelSettingsEntry,
39
47
  name: () => name,
40
- tabAfter: () => tabAfter
48
+ nextWindowResetAt: () => nextWindowResetAt,
49
+ normaliseQuotaRefreshSeconds: () => normaliseQuotaRefreshSeconds,
50
+ parseNumberInput: () => parseNumberInput,
51
+ protocolEndpoint: () => protocolEndpoint,
52
+ quotaAutoRefresh: () => quotaAutoRefresh,
53
+ tabAfter: () => tabAfter,
54
+ writeModelSettings: () => writeModelSettings
41
55
  });
42
56
  module.exports = __toCommonJS(client_exports);
43
57
  var React = __toESM(require("react"), 1);
@@ -19911,6 +19925,66 @@ function record2(value) {
19911
19925
  return value !== null && typeof value === "object" && !Array.isArray(value);
19912
19926
  }
19913
19927
 
19928
+ // src/protocol-options.ts
19929
+ var API_PROTOCOL_IDS = ["chat-completions", "responses", "messages"];
19930
+ function openAiBaseURL(baseURL) {
19931
+ const base = baseURL.replace(/\/+$/u, "");
19932
+ return new URL(base).pathname === "/" ? `${base}/v1` : base;
19933
+ }
19934
+ function inferenceEndpoint(baseURL, protocol) {
19935
+ const suffix = protocol === "responses" ? "/responses" : protocol === "messages" ? "/messages" : "/chat/completions";
19936
+ return baseURL.trim() === "" ? suffix : `${openAiBaseURL(baseURL.trim())}${suffix}`;
19937
+ }
19938
+ var API_PROTOCOL_OPTIONS = [
19939
+ { id: "chat-completions", name: "OpenAI Chat Completions", description: "\u4F7F\u7528 Chat Completions \u8BF7\u6C42\u4E0E\u6D41\u5F0F\u54CD\u5E94\u3002" },
19940
+ { id: "responses", name: "OpenAI Responses", description: "\u4F7F\u7528 Responses \u8BF7\u6C42\u4E0E\u6D41\u5F0F\u54CD\u5E94\u3002" },
19941
+ { id: "messages", name: "Anthropic Messages", description: "\u4F7F\u7528 Messages \u8BF7\u6C42\u4E0E\u6D41\u5F0F\u54CD\u5E94\u3002" }
19942
+ ];
19943
+
19944
+ // src/model-settings.ts
19945
+ function parseModelSettingsMap(value) {
19946
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
19947
+ const keys = Object.keys(value);
19948
+ if (keys.length > 512) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u6700\u591A\u5141\u8BB8 512 \u4E2A\u6A21\u578B");
19949
+ const fields = /* @__PURE__ */ new Set(["enabled", "temperature", "topP", "maxTokens", "contextWindow", "reasoningEffort"]);
19950
+ const efforts = ["none", "minimal", "low", "medium", "high", "xhigh", "ultra", "max"];
19951
+ const out = {};
19952
+ for (const id of keys) {
19953
+ if (["__proto__", "constructor", "prototype"].includes(id)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u4E0D\u5141\u8BB8\u4FDD\u7559\u5B57\u6BB5\u540D");
19954
+ if (!/^[A-Za-z0-9._:/-]{1,128}$/u.test(id)) throw new Error("\u6A21\u578B ID \u5FC5\u987B\u662F 1\u2013128 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u6216 . _ : / -");
19955
+ const entry = value[id];
19956
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new Error("\u6BCF\u4E2A\u6A21\u578B\u7684\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
19957
+ const settings = entry;
19958
+ for (const field of Object.keys(settings)) {
19959
+ if (!fields.has(field)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u542B\u6709\u4E0D\u652F\u6301\u7684\u5B57\u6BB5");
19960
+ }
19961
+ const parsed = {};
19962
+ if (settings.enabled !== void 0) {
19963
+ if (typeof settings.enabled !== "boolean") throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C");
19964
+ parsed.enabled = settings.enabled;
19965
+ }
19966
+ for (const [field, max] of [["temperature", 2], ["topP", 1]]) {
19967
+ const item = settings[field];
19968
+ if (item === void 0) continue;
19969
+ if (typeof item !== "number" || !Number.isFinite(item) || item < 0 || item > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${field} \u5FC5\u987B\u662F 0 \u5230 ${max} \u4E4B\u95F4\u7684\u6709\u9650\u6570\u5B57`);
19970
+ parsed[field] = item;
19971
+ }
19972
+ for (const [field, max] of [["maxTokens", 1e7], ["contextWindow", 1e8]]) {
19973
+ const item = settings[field];
19974
+ if (item === void 0) continue;
19975
+ if (typeof item !== "number" || !Number.isSafeInteger(item) || item < 1 || item > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${field} \u5FC5\u987B\u662F 1 \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
19976
+ parsed[field] = item;
19977
+ }
19978
+ if (settings.reasoningEffort !== void 0) {
19979
+ if (typeof settings.reasoningEffort !== "string" || !efforts.includes(settings.reasoningEffort)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 reasoningEffort \u4E0D\u5728\u5141\u8BB8\u7684\u53D6\u503C\u5185");
19980
+ parsed.reasoningEffort = settings.reasoningEffort;
19981
+ }
19982
+ if (parsed.maxTokens !== void 0 && parsed.contextWindow !== void 0 && parsed.maxTokens >= parsed.contextWindow) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 maxTokens \u5FC5\u987B\u5C0F\u4E8E contextWindow");
19983
+ out[id] = parsed;
19984
+ }
19985
+ return out;
19986
+ }
19987
+
19914
19988
  // src/remote.ts
19915
19989
  var MAX_REVISION = 256;
19916
19990
  var MAX_JOB_ID = 128;
@@ -19924,7 +19998,7 @@ var MAX_WINDOWS = 64;
19924
19998
  var MAX_API_KEY = 16384;
19925
19999
  var HTTP_ASCII = /^[\x21-\x7e]+$/u;
19926
20000
  var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
19927
- var MODEL_ID = /^[A-Za-z0-9._:-]{1,128}$/u;
20001
+ var MODEL_ID = /^[A-Za-z0-9._:/-]{1,128}$/u;
19928
20002
  var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
19929
20003
  var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
19930
20004
  var SESSION_SOURCES = ["desktop", "login", "environment"];
@@ -19950,6 +20024,15 @@ function amount(value, label, negative = false) {
19950
20024
  if (typeof value !== "number" || !Number.isFinite(value) || !negative && value < 0) throw new Error(`${label} \u5FC5\u987B\u662F${negative ? "" : "\u975E\u8D1F"}\u6709\u9650\u6570\u5B57`);
19951
20025
  return value;
19952
20026
  }
20027
+ function positiveInteger(value, label, max) {
20028
+ const result = count(value, label);
20029
+ if (result === 0 || result > max) throw new Error(`${label} \u8D85\u51FA\u8303\u56F4`);
20030
+ return result;
20031
+ }
20032
+ function parseEfforts(value) {
20033
+ if (!Array.isArray(value) || value.length > 16) throw new Error("\u6A21\u578B\u601D\u8003\u6863\u4F4D\u683C\u5F0F\u65E0\u6548");
20034
+ return value.map((item) => text(item, "\u601D\u8003\u6863\u4F4D", 32));
20035
+ }
19953
20036
  function member(value, allowed, label) {
19954
20037
  const hit = allowed.find((candidate) => candidate === value);
19955
20038
  if (hit === void 0) throw new Error(`${label} \u4E0D\u5728\u5141\u8BB8\u7684\u53D6\u503C\u5185`);
@@ -19967,7 +20050,9 @@ function parseQuotaWindow(value) {
19967
20050
  limit,
19968
20051
  ...value.remaining === void 0 ? {} : { remaining: amount(value.remaining, "\u5269\u4F59\u989D\u5EA6") },
19969
20052
  percent: Math.min(100, Math.max(0, amount(value.percent, "\u5DF2\u7528\u767E\u5206\u6BD4"))),
19970
- ...value.resetsAt === void 0 ? {} : { resetsAt: isoTime(value.resetsAt, "\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4") }
20053
+ ...value.resetsAt === void 0 ? {} : { resetsAt: isoTime(value.resetsAt, "\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4") },
20054
+ ...value.windowStart === void 0 ? {} : { windowStart: isoTime(value.windowStart, "\u989D\u5EA6\u7A97\u53E3\u5F00\u59CB\u65F6\u95F4") },
20055
+ ...value.resetMode === void 0 ? {} : { resetMode: text(value.resetMode, "\u989D\u5EA6\u5237\u65B0\u65B9\u5F0F", 128) }
19971
20056
  };
19972
20057
  }
19973
20058
  function parseQuotaSnapshot(value) {
@@ -19980,6 +20065,12 @@ function parseQuotaSnapshot(value) {
19980
20065
  state: member(value.state, QUOTA_STATES, "\u989D\u5EA6\u72B6\u6001"),
19981
20066
  ...value.source === void 0 ? {} : { source: member(value.source, SESSION_SOURCES, "\u989D\u5EA6\u6765\u6E90") },
19982
20067
  updatedAt: isoTime(value.updatedAt, "\u989D\u5EA6\u66F4\u65B0\u65F6\u95F4"),
20068
+ ...value.subscriptionPeriodStart === void 0 ? {} : { subscriptionPeriodStart: isoTime(value.subscriptionPeriodStart, "\u8BA2\u9605\u5468\u671F\u5F00\u59CB\u65F6\u95F4") },
20069
+ ...value.subscriptionPeriodEnd === void 0 ? {} : { subscriptionPeriodEnd: isoTime(value.subscriptionPeriodEnd, "\u8BA2\u9605\u5468\u671F\u7ED3\u675F\u65F6\u95F4") },
20070
+ ...value.nextRefreshAt === void 0 ? {} : { nextRefreshAt: isoTime(value.nextRefreshAt, "\u4E0B\u6B21\u81EA\u52A8\u5237\u65B0\u65F6\u95F4") },
20071
+ ...value.stale === void 0 ? {} : { stale: flag(value.stale, "\u5386\u53F2\u989D\u5EA6\u8BFB\u6570") },
20072
+ ...value.refreshFailures === void 0 ? {} : { refreshFailures: count(value.refreshFailures, "\u8FDE\u7EED\u5237\u65B0\u5931\u8D25\u6B21\u6570") },
20073
+ ...value.autoRefreshPaused === void 0 ? {} : { autoRefreshPaused: flag(value.autoRefreshPaused, "\u81EA\u52A8\u5237\u65B0\u6682\u505C\u72B6\u6001") },
19983
20074
  ...value.tier === void 0 ? {} : { tier: text(value.tier, "\u5957\u9910\u540D", MAX_TIER) },
19984
20075
  ...value.eligible === void 0 ? {} : { eligible: flag(value.eligible, "\u5957\u9910\u8D44\u683C") },
19985
20076
  windows: windows.map(parseQuotaWindow),
@@ -20010,7 +20101,11 @@ function parseStatusModel(value) {
20010
20101
  return {
20011
20102
  id,
20012
20103
  name: text(value.name, "\u6A21\u578B\u540D", 128),
20013
- group: member(value.group, MODEL_GROUPS, "\u6A21\u578B\u5206\u7EC4")
20104
+ group: member(value.group, MODEL_GROUPS, "\u6A21\u578B\u5206\u7EC4"),
20105
+ ...value.enabled === void 0 ? {} : { enabled: flag(value.enabled, "\u6A21\u578B\u542F\u7528\u72B6\u6001") },
20106
+ ...value.contextWindow === void 0 ? {} : { contextWindow: positiveInteger(value.contextWindow, "\u4E0A\u4E0B\u6587\u9884\u7B97", 1e8) },
20107
+ ...value.maxOutputTokens === void 0 ? {} : { maxOutputTokens: positiveInteger(value.maxOutputTokens, "\u6700\u5927\u8F93\u51FA", 1e7) },
20108
+ ...value.reasoningEfforts === void 0 ? {} : { reasoningEfforts: parseEfforts(value.reasoningEfforts) }
20014
20109
  };
20015
20110
  }
20016
20111
  function parseStatusConfig(value) {
@@ -20021,7 +20116,12 @@ function parseStatusConfig(value) {
20021
20116
  emptyTurnGuard: flag(value.emptyTurnGuard, "\u7A7A\u8F6E\u4FDD\u62A4\u5F00\u5173"),
20022
20117
  useDesktopCredential: flag(value.useDesktopCredential, "\u684C\u9762\u767B\u5F55\u6001\u5F00\u5173"),
20023
20118
  baseURL: text(value.baseURL, "\u63A8\u7406\u5730\u5740", MAX_BASE_URL),
20024
- apiKeyEnv
20119
+ apiKeyEnv,
20120
+ ...value.apiProtocol === void 0 ? {} : { apiProtocol: member(value.apiProtocol, API_PROTOCOL_IDS, "\u63A8\u7406\u534F\u8BAE") },
20121
+ ...value.modelSettings === void 0 ? {} : { modelSettings: parseModelSettingsMap(value.modelSettings) },
20122
+ ...value.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: positiveInteger(value.requestTimeoutMs, "\u8BF7\u6C42\u8D85\u65F6", 36e5) },
20123
+ ...value.streamIdleTimeoutMs === void 0 ? {} : { streamIdleTimeoutMs: positiveInteger(value.streamIdleTimeoutMs, "\u6D41\u7A7A\u95F2\u8D85\u65F6", 18e5) },
20124
+ ...value.quotaRefreshSeconds === void 0 ? {} : { quotaRefreshSeconds: positiveInteger(value.quotaRefreshSeconds, "\u81EA\u52A8\u5237\u65B0\u95F4\u9694", 3600) }
20025
20125
  };
20026
20126
  }
20027
20127
  function parseStatus(value) {
@@ -20042,7 +20142,10 @@ function parseStatus(value) {
20042
20142
  login: parseLoginState(value.login),
20043
20143
  config: parseStatusConfig(value.config),
20044
20144
  guardedTurns: count(value.guardedTurns, "\u5DF2\u62E6\u622A\u7A7A\u8F6E\u6570"),
20045
- models: parsed
20145
+ models: parsed,
20146
+ ...value.modelsSource === void 0 ? {} : { modelsSource: member(value.modelsSource, ["builtin", "openai"], "\u6A21\u578B\u76EE\u5F55\u6765\u6E90") },
20147
+ ...value.modelsUpdatedAt === void 0 ? {} : { modelsUpdatedAt: isoTime(value.modelsUpdatedAt, "\u6A21\u578B\u66F4\u65B0\u65F6\u95F4") },
20148
+ ...value.modelsError === void 0 ? {} : { modelsError: text(value.modelsError, "\u6A21\u578B\u66F4\u65B0\u63D0\u793A", MAX_MESSAGE) }
20046
20149
  };
20047
20150
  }
20048
20151
  function parseStartLoginRequest(value) {
@@ -20071,7 +20174,9 @@ var CancelLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-coro
20071
20174
  var SaveApiKeyRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#SaveApiKeyRequest", create: () => external_exports.unknown().transform((value) => parseSaveApiKeyRequest(value)) };
20072
20175
  var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter", methods: {
20073
20176
  "openDesignCoRouter/status": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: StatusCodec },
20177
+ "openDesignCoRouter/refreshModels": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: StatusCodec },
20074
20178
  "openDesignCoRouter/refreshQuota": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: QuotaSnapshotCodec },
20179
+ "openDesignCoRouter/refreshQuotaNow": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: QuotaSnapshotCodec },
20075
20180
  "openDesignCoRouter/startLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: StartLoginRequestCodec, strictCodec: StartLoginRequestCodec }], result: LoginStateCodec },
20076
20181
  "openDesignCoRouter/loginStatus": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: LoginStateCodec },
20077
20182
  "openDesignCoRouter/cancelLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: CancelLoginRequestCodec, strictCodec: CancelLoginRequestCodec }], result: LoginStateCodec },
@@ -20089,6 +20194,17 @@ var TABS = [
20089
20194
  { id: "connection", label: "\u8FDE\u63A5\u4E0E\u767B\u5F55" },
20090
20195
  { id: "advanced", label: "\u9AD8\u7EA7" }
20091
20196
  ];
20197
+ var DEFAULT_QUOTA_REFRESH_SECONDS = 60;
20198
+ var MIN_QUOTA_REFRESH_SECONDS = 5;
20199
+ var MAX_QUOTA_REFRESH_SECONDS = 3600;
20200
+ var DEFAULT_REQUEST_TIMEOUT_MS = 18e5;
20201
+ var MIN_REQUEST_TIMEOUT_MS = 1e3;
20202
+ var MAX_REQUEST_TIMEOUT_MS = 36e5;
20203
+ var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
20204
+ var MIN_STREAM_IDLE_TIMEOUT_MS = 1e3;
20205
+ var MAX_STREAM_IDLE_TIMEOUT_MS = 18e5;
20206
+ var MAX_OUTPUT_TOKENS = 1e7;
20207
+ var MAX_CONTEXT_WINDOW = 1e8;
20092
20208
  var GENERIC_ERROR = "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
20093
20209
  var LOGIN_LABELS = {
20094
20210
  idle: "\u5C1A\u672A\u5F00\u59CB\u72EC\u7ACB\u767B\u5F55",
@@ -20114,13 +20230,18 @@ function installStyles() {
20114
20230
  .odc-intro{margin:6px 0 18px!important;color:var(--dsw-alias-label-secondary,inherit);font-size:12px}.odc-tabs{display:flex;gap:4px;overflow-x:auto;margin:0 0 16px;padding:0 0 8px;border-bottom:1px solid var(--dsw-alias-border-l4,transparent)}
20115
20231
  .odc-tab{flex:none;border:1px solid transparent;border-radius:8px;padding:7px 13px;background:transparent;color:var(--dsw-alias-label-secondary,inherit);font:inherit;cursor:pointer}.odc-tab:hover{background:var(--dsw-alias-bg-layer-1,transparent)}.odc-tab[aria-selected=true]{color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l3,transparent);background:var(--dsw-alias-bg-layer-1,transparent)}
20116
20232
  .odc-panel{display:grid;gap:14px}.odc-card{min-width:0;padding:15px;border:1px solid var(--dsw-alias-border-l4,transparent);border-radius:12px;background:var(--dsw-alias-bg-layer-1,transparent)}.odc-card>p{margin-top:6px}.odc-card-head,.odc-row{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.odc-card-head{margin-bottom:12px}
20117
- .odc-quota-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.odc-metric{display:grid;align-content:start;gap:5px;min-height:96px}.odc-metric-value{font-size:20px;font-weight:650;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.odc-label,.odc-hint,.odc-meta{color:var(--dsw-alias-label-secondary,inherit);font-size:11px;line-height:1.6}.odc-meta{margin-top:10px!important}.odc-window{display:grid;gap:7px;margin-top:10px}.odc-window progress{width:100%;height:7px;accent-color:var(--dsw-alias-brand-primary,Highlight)}
20118
- .odc-button{display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:6px 12px;border:1px solid var(--dsw-alias-border-l3,transparent);border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:12px;cursor:pointer}.odc-button:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary,Highlight)}.odc-button-primary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary,Highlight));color:var(--dsw-alias-label-primary-foreground,HighlightText);border-color:transparent}.odc-button-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-brand-primary,Highlight))}.odc-button:disabled{opacity:.55;cursor:not-allowed}.odc-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
20233
+ .odc-quota-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.odc-metric{display:grid;align-content:start;gap:5px;min-height:96px}.odc-metric-value{font-size:20px;font-weight:650;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.odc-metric-value.odc-metric-text{font-size:13px;font-weight:600;line-height:1.55}.odc-quota-grid+.odc-quota-grid{margin-top:10px}
20234
+ .odc-label,.odc-hint,.odc-meta{color:var(--dsw-alias-label-secondary,inherit);font-size:11px;line-height:1.6}.odc-meta{margin-top:10px!important}.odc-window{display:grid;gap:7px;margin-top:10px}.odc-window progress{width:100%;height:7px;accent-color:var(--dsw-alias-brand-primary,Highlight)}
20235
+ .odc-button{display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:6px 12px;border:1px solid var(--dsw-alias-border-l3,transparent);border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:12px;cursor:pointer}.odc-button:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary,Highlight)}.odc-button-primary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary,Highlight));color:var(--dsw-alias-label-primary-foreground,HighlightText);border-color:transparent}.odc-button-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-brand-primary,Highlight))}.odc-button:disabled{opacity:.55;cursor:not-allowed}.odc-button-small{min-height:26px;padding:3px 9px;font-size:11px}.odc-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
20119
20236
  .odc-notice{padding:9px 11px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);font-size:12px;line-height:1.6}.odc-notice[data-kind=error]{color:var(--dsw-alias-state-error-primary,inherit);color:color-mix(in srgb,var(--dsw-alias-state-error-primary,CanvasText) 85%,var(--dsw-alias-label-primary,CanvasText))}
20120
- .odc-model-groups{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.odc-model-list{display:grid;gap:6px;margin:10px 0 0;padding:0;list-style:none}.odc-model-item{padding:9px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);min-width:0}.odc-model-item strong,.odc-model-item code{display:block;overflow-wrap:anywhere}.odc-model-item code{margin-top:2px;color:var(--dsw-alias-label-secondary,inherit);font-size:11px}.odc-model-count{color:var(--dsw-alias-label-secondary,inherit);font-size:11px;font-weight:400}
20121
- .odc-field{display:grid;gap:6px;margin-top:14px}.odc-field label,.odc-switch-text{font-size:12px;font-weight:550}.odc-input{width:100%;min-width:0;min-height:34px;padding:7px 9px;border:1px solid var(--dsw-alias-border-l3,transparent);border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:12px}.odc-input::placeholder{color:var(--dsw-alias-label-secondary,inherit);opacity:1}.odc-input:disabled{opacity:.55}
20122
- .odc-switch{display:flex;align-items:flex-start;gap:10px;cursor:pointer}.odc-switch input{width:16px;height:16px;flex:none;margin:2px 0 0;accent-color:var(--dsw-alias-brand-primary,Highlight)}.odc-switch>span{display:grid;gap:4px}.odc-switch:has(input:disabled){cursor:not-allowed;opacity:.6}.odc-divider{height:1px;border:0;background:var(--dsw-alias-border-l4,transparent);margin:2px 0}.odc-link{color:var(--dsw-alias-brand-primary,inherit);text-underline-offset:3px}
20123
- .odc-settings :focus-visible{outline:2px solid var(--dsw-alias-brand-primary,Highlight);outline-offset:3px}@media(max-width:680px){.odc-quota-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.odc-model-groups{grid-template-columns:1fr}}@media(max-width:380px){.odc-quota-grid{grid-template-columns:1fr}.odc-card{padding:12px}}
20237
+ .odc-model-groups{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:12px}.odc-model-list{display:grid;gap:6px;margin:10px 0 0;padding:0;list-style:none}.odc-model-item{padding:9px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);min-width:0}.odc-model-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;flex-wrap:wrap}.odc-model-switch{min-width:0;flex:1 1 190px}.odc-model-item strong,.odc-model-item code{display:block;overflow-wrap:anywhere}.odc-model-item strong{font-size:12px;font-weight:600}.odc-model-item code{margin-top:2px;color:var(--dsw-alias-label-secondary,inherit);font-size:11px}.odc-model-meta{margin-top:6px!important;color:var(--dsw-alias-label-secondary,inherit);font-size:11px;overflow-wrap:anywhere}.odc-model-count{color:var(--dsw-alias-label-secondary,inherit);font-size:11px;font-weight:400}.odc-chip{display:inline-flex;align-items:center;padding:1px 7px;border:1px solid var(--dsw-alias-border-l3,transparent);border-radius:999px;color:var(--dsw-alias-label-secondary,inherit);font-size:10px;line-height:1.6}
20238
+ .odc-model-details{margin-top:8px}.odc-model-details>summary{cursor:pointer;color:var(--dsw-alias-label-secondary,inherit);font-size:11px;line-height:1.7}.odc-model-editor{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2px 10px;margin-top:8px}
20239
+ .odc-field{display:grid;gap:6px;margin-top:14px}.odc-field-compact{margin-top:8px}.odc-field label,.odc-switch-text{font-size:12px;font-weight:550}.odc-input{width:100%;min-width:0;min-height:34px;padding:7px 9px;border:1px solid var(--dsw-alias-border-l3,transparent);border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:12px}.odc-field-compact .odc-input{min-height:30px;padding:5px 8px;font-size:11px}.odc-input::placeholder{color:var(--dsw-alias-label-secondary,inherit);opacity:1}.odc-input:disabled{opacity:.55}
20240
+ .odc-switch{display:flex;align-items:center;gap:12px;min-height:44px;padding:6px 2px;cursor:pointer}.odc-switch input{appearance:auto;width:20px;height:20px;flex:none;margin:0;cursor:inherit;accent-color:var(--dsw-alias-brand-primary,Highlight)}.odc-switch>span{display:grid;gap:4px;min-width:0}.odc-switch:has(input:disabled){cursor:not-allowed;opacity:.6}.odc-divider{height:1px;border:0;background:var(--dsw-alias-border-l4,transparent);margin:2px 0}.odc-link{color:var(--dsw-alias-brand-primary,inherit);text-underline-offset:3px}
20241
+ .odc-readonly{display:grid;gap:10px;margin-top:14px;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent)}.odc-code{display:block;min-width:0;color:var(--dsw-alias-label-primary,inherit);font-family:ui-monospace,'Cascadia Mono',Consolas,monospace;font-size:11px;line-height:1.6;overflow-wrap:anywhere}
20242
+ .odc-timing{padding:12px 0;border-top:1px solid var(--dsw-alias-border-l4,transparent)}.odc-timing .odc-metric{min-height:0}.odc-settings>.odc-notice{margin-bottom:14px}.odc-model-details>summary{min-height:32px;padding:6px 0}
20243
+ .odc-field-error{color:var(--dsw-alias-state-error-primary,inherit);color:color-mix(in srgb,var(--dsw-alias-state-error-primary,CanvasText) 85%,var(--dsw-alias-label-primary,CanvasText))}
20244
+ .odc-settings :focus-visible{outline:2px solid var(--dsw-alias-brand-primary,Highlight);outline-offset:3px}@media(max-width:680px){.odc-quota-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.odc-model-groups{grid-template-columns:1fr}.odc-model-editor{grid-template-columns:1fr}}@media(max-width:380px){.odc-quota-grid{grid-template-columns:1fr}.odc-card{padding:12px}}
20124
20245
  `;
20125
20246
  document.head.append(style);
20126
20247
  return () => style.remove();
@@ -20218,19 +20339,65 @@ function formatTime(value) {
20218
20339
  const date5 = new Date(value);
20219
20340
  return Number.isFinite(date5.getTime()) ? date5.toLocaleString("zh-CN", { hour12: false }) : "\u2014";
20220
20341
  }
20342
+ function formatTokens(value) {
20343
+ if (value === void 0 || !Number.isFinite(value)) return "\u2014";
20344
+ return new Intl.NumberFormat("zh-CN").format(value);
20345
+ }
20221
20346
  function windowLabel(seconds) {
20222
20347
  if (seconds % 86400 === 0) return `${seconds / 86400} \u5929\u8D60\u9001\u7A97\u53E3`;
20223
20348
  if (seconds % 3600 === 0) return `${seconds / 3600} \u5C0F\u65F6\u8D60\u9001\u7A97\u53E3`;
20224
20349
  if (seconds % 60 === 0) return `${seconds / 60} \u5206\u949F\u8D60\u9001\u7A97\u53E3`;
20225
20350
  return `${seconds} \u79D2\u8D60\u9001\u7A97\u53E3`;
20226
20351
  }
20227
- function QuotaView({ quota }) {
20352
+ function nextWindowResetAt(windows, now) {
20353
+ let earliest;
20354
+ let earliestMs = Number.POSITIVE_INFINITY;
20355
+ for (const window of windows) {
20356
+ if (window.resetsAt === void 0) continue;
20357
+ const ms = Date.parse(window.resetsAt);
20358
+ if (!Number.isFinite(ms) || ms <= now || ms >= earliestMs) continue;
20359
+ earliest = window.resetsAt;
20360
+ earliestMs = ms;
20361
+ }
20362
+ return earliest;
20363
+ }
20364
+ var SOURCE_LABELS = {
20365
+ desktop: "\u684C\u9762\u767B\u5F55\u6001",
20366
+ login: "\u72EC\u7ACB\u767B\u5F55",
20367
+ environment: "\u73AF\u5883\u51ED\u636E"
20368
+ };
20369
+ var RESET_MODE_LABELS = {
20370
+ activity_triggered: "\u6309\u4F7F\u7528\u89E6\u53D1",
20371
+ anchored_recurring: "\u56FA\u5B9A\u5468\u671F"
20372
+ };
20373
+ function QuotaView({ quota, nextAutoRefreshAt, refreshIntervalSeconds }) {
20228
20374
  if (quota === void 0) return h("p", { className: "odc-hint", role: "status" }, "\u6B63\u5728\u8BFB\u53D6\u989D\u5EA6\u2026");
20229
20375
  const metrics = [
20230
20376
  { label: "\u5957\u9910\u4F59\u989D", value: formatUsd(quota.balances.subscriptionUsd), hint: "\u8BA2\u9605\u5269\u4F59\u53EF\u7528\u91D1\u989D" },
20231
20377
  { label: "\u5145\u503C / \u8D60\u9001\u91D1\u989D", value: formatUsd(quota.balances.rechargeUsd), hint: "\u5145\u503C\u4E0E\u8D60\u9001\u7684\u53EF\u7528\u91D1\u989D" },
20232
20378
  { label: "\u603B\u989D\u5EA6", value: formatUsd(quota.balances.totalUsd), hint: "\u94B1\u5305\u5F53\u524D\u53EF\u7528\u603B\u989D" }
20233
20379
  ];
20380
+ const timing = [
20381
+ {
20382
+ label: "\u8BA2\u9605\u8D26\u671F\u7ED3\u675F",
20383
+ value: formatTime(quota.subscriptionPeriodEnd),
20384
+ hint: `\u8D26\u671F\u5F00\u59CB\uFF1A${formatTime(quota.subscriptionPeriodStart)}`
20385
+ },
20386
+ {
20387
+ label: "\u4E0B\u6B21\u989D\u5EA6\u6062\u590D",
20388
+ value: formatTime(nextWindowResetAt(quota.windows, Date.now())),
20389
+ hint: "\u6700\u65E9\u6062\u590D\u7684\u8D60\u9001\u7A97\u53E3\uFF1B\u4E0D\u4EE3\u8868\u94B1\u5305\u5230\u8D26\u65F6\u95F4"
20390
+ },
20391
+ {
20392
+ label: "\u4E0B\u6B21\u81EA\u52A8\u5237\u65B0",
20393
+ value: formatTime(quota.state === "need-login" || quota.autoRefreshPaused ? void 0 : nextAutoRefreshAt),
20394
+ hint: quota.state === "need-login" ? "\u767B\u5F55\u540E\u81EA\u52A8\u5237\u65B0" : quota.autoRefreshPaused ? "\u8FDE\u7EED\u5931\u8D25\uFF0C\u81EA\u52A8\u91CD\u8BD5\u5DF2\u6682\u505C\uFF1B\u70B9\u51FB\u5237\u65B0\u989D\u5EA6\u91CD\u8BD5" : refreshIntervalSeconds === void 0 ? "\u672A\u542F\u7528\u81EA\u52A8\u5237\u65B0" : quota.refreshFailures ? `\u5237\u65B0\u5931\u8D25 ${quota.refreshFailures} \u6B21\uFF0C\u6B63\u5728\u5EF6\u8FDF\u91CD\u8BD5` : `\u6BCF ${refreshIntervalSeconds} \u79D2\u81EA\u52A8\u5237\u65B0`
20395
+ }
20396
+ ];
20397
+ const tierLine = [
20398
+ quota.tier === void 0 ? void 0 : `\u5F53\u524D\u5957\u9910\uFF1A${quota.tier}`,
20399
+ quota.eligible === void 0 ? void 0 : `\u5957\u9910\u8D44\u683C\uFF1A${quota.eligible ? "\u5DF2\u5F00\u901A" : "\u672A\u5F00\u901A"}`
20400
+ ].filter((value) => value !== void 0).join(" \xB7 ");
20234
20401
  return h(
20235
20402
  React.Fragment,
20236
20403
  null,
@@ -20240,10 +20407,25 @@ function QuotaView({ quota }) {
20240
20407
  h(
20241
20408
  "div",
20242
20409
  { className: "odc-card-head" },
20243
- h("div", null, h("h3", null, "\u5957\u9910\u8D60\u9001\u7A97\u53E3"), quota.tier !== void 0 ? h("p", { className: "odc-hint" }, `\u5F53\u524D\u5957\u9910\uFF1A${quota.tier}`) : null),
20244
- h("span", { className: "odc-label" }, QUOTA_LABELS[quota.state])
20410
+ h(
20411
+ "div",
20412
+ null,
20413
+ h("h3", null, "\u5957\u9910\u8D60\u9001\u7A97\u53E3"),
20414
+ tierLine === "" ? null : h("p", { className: "odc-hint" }, tierLine)
20415
+ ),
20416
+ h(
20417
+ "div",
20418
+ { className: "odc-actions" },
20419
+ h("span", { className: "odc-label" }, QUOTA_LABELS[quota.state]),
20420
+ quota.stale ? h("span", { className: "odc-chip" }, "\u4E0A\u6B21\u6210\u529F\u8BFB\u6570") : null
20421
+ )
20245
20422
  ),
20246
- quota.windows.length === 0 ? h("p", { className: "odc-metric-value" }, "\u2014") : quota.windows.map((window, index) => h(
20423
+ quota.windows.length === 0 ? quota.eligible === false ? h("p", { className: "odc-hint", role: "status" }, "\u5F53\u524D\u8D26\u53F7\u672A\u5F00\u901A Design Plan\uFF0C\u6CA1\u6709\u8D60\u9001\u989D\u5EA6\u7A97\u53E3\u3002") : h(
20424
+ React.Fragment,
20425
+ null,
20426
+ h("p", { className: "odc-metric-value" }, "\u2014"),
20427
+ h("p", { className: "odc-hint" }, "\u989D\u5EA6\u7A97\u53E3\u6682\u4E0D\u53EF\u786E\u8BA4\uFF1B\u4E0D\u6309 0 \u586B\u5145\u3002")
20428
+ ) : quota.windows.map((window, index) => h(
20247
20429
  "div",
20248
20430
  { className: "odc-window", key: `${window.durationSeconds}-${index}` },
20249
20431
  h(
@@ -20253,7 +20435,7 @@ function QuotaView({ quota }) {
20253
20435
  h("span", { className: "odc-label" }, `\u5DF2\u7528 ${window.percent.toFixed(1)}% \xB7 \u5269\u4F59 ${window.remaining ?? "\u2014"}`)
20254
20436
  ),
20255
20437
  h("progress", { max: 100, value: window.percent, "aria-label": `${windowLabel(window.durationSeconds)}\u5DF2\u7528\u989D\u5EA6` }),
20256
- h("span", { className: "odc-meta" }, `\u5DF2\u7528 ${window.used} / \u4E0A\u9650 ${window.limit} \xB7 \u91CD\u7F6E ${formatTime(window.resetsAt)}`)
20438
+ h("span", { className: "odc-meta" }, `\u5DF2\u7528 ${window.used} / \u4E0A\u9650 ${window.limit} \xB7 \u6062\u590D ${formatTime(window.resetsAt)}${window.windowStart === void 0 ? "" : ` \xB7 \u7A97\u53E3\u5F00\u59CB ${formatTime(window.windowStart)}`}${window.resetMode === void 0 ? "" : ` \xB7 \u91CD\u7F6E\u65B9\u5F0F ${RESET_MODE_LABELS[window.resetMode] ?? window.resetMode}`}`)
20257
20439
  ))
20258
20440
  ),
20259
20441
  h("div", { className: "odc-quota-grid" }, metrics.map((metric) => h(
@@ -20263,28 +20445,316 @@ function QuotaView({ quota }) {
20263
20445
  h("strong", { className: "odc-metric-value" }, metric.value),
20264
20446
  h("span", { className: "odc-hint" }, metric.hint)
20265
20447
  ))),
20266
- h("p", { className: "odc-meta" }, `\u66F4\u65B0\u4E8E ${formatTime(quota.updatedAt)}${quota.source !== void 0 ? ` \xB7 \u6765\u6E90\uFF1A${{ desktop: "\u684C\u9762\u767B\u5F55\u6001", login: "\u72EC\u7ACB\u767B\u5F55", environment: "\u73AF\u5883\u51ED\u636E" }[quota.source]}` : ""}`),
20448
+ h("div", { className: "odc-quota-grid odc-timing", "aria-label": "\u989D\u5EA6\u65F6\u95F4\u5B89\u6392" }, timing.map((metric) => h(
20449
+ "div",
20450
+ { className: "odc-metric", key: metric.label },
20451
+ h("span", { className: "odc-label" }, metric.label),
20452
+ h("strong", { className: "odc-metric-value odc-metric-text" }, metric.value),
20453
+ h("span", { className: "odc-hint" }, metric.hint)
20454
+ ))),
20455
+ h("p", { className: "odc-meta" }, `\u66F4\u65B0\u4E8E ${formatTime(quota.updatedAt)}${quota.source !== void 0 ? ` \xB7 \u6765\u6E90\uFF1A${SOURCE_LABELS[quota.source]}\uFF08\u53EA\u8BFB\u5C55\u793A\uFF09` : ""}`),
20456
+ h(
20457
+ "details",
20458
+ { className: "odc-model-details" },
20459
+ h("summary", null, "\u5982\u4F55\u7406\u89E3\u8FD9\u4E9B\u989D\u5EA6\uFF1F"),
20460
+ h("p", { className: "odc-hint" }, "\u8D60\u9001\u7A97\u53E3\u4E0E\u94B1\u5305\u4F59\u989D\u5206\u522B\u8BA1\u91CF\u3002\u5957\u9910\u4F59\u989D\u6765\u81EA\u8BA2\u9605\uFF0C\u5145\u503C / \u8D60\u9001\u91D1\u989D\u6765\u81EA\u94B1\u5305\uFF1B\u603B\u989D\u5EA6\u662F\u94B1\u5305\u5F53\u524D\u53EF\u7528\u603B\u989D\u3002\u5B9E\u9645\u6263\u6B3E\u7531\u670D\u52A1\u7AEF\u51B3\u5B9A\u3002\u989D\u5EA6\u6570\u636E\u53EA\u8BFB\u5C55\u793A\uFF0C\u672A\u77E5\u91D1\u989D\u663E\u793A \u2014\u3002")
20461
+ ),
20267
20462
  quota.note !== void 0 ? h("p", { className: "odc-notice", "data-kind": quota.state === "error" ? "error" : "status", role: quota.state === "error" ? "alert" : "status" }, quota.note) : null
20268
20463
  );
20269
20464
  }
20270
- function Overview({ status, busy, onRefresh }) {
20465
+ function writeModelSettings(map2, id, next) {
20466
+ const out = { ...map2 ?? {} };
20467
+ if (next === void 0) {
20468
+ delete out[id];
20469
+ return out;
20470
+ }
20471
+ const entry = {};
20472
+ if (next.enabled !== void 0) entry.enabled = next.enabled;
20473
+ if (next.temperature !== void 0) entry.temperature = next.temperature;
20474
+ if (next.topP !== void 0) entry.topP = next.topP;
20475
+ if (next.maxTokens !== void 0) entry.maxTokens = next.maxTokens;
20476
+ if (next.contextWindow !== void 0) entry.contextWindow = next.contextWindow;
20477
+ if (next.reasoningEffort !== void 0) entry.reasoningEffort = next.reasoningEffort;
20478
+ if (Object.keys(entry).length === 0) delete out[id];
20479
+ else out[id] = entry;
20480
+ return out;
20481
+ }
20482
+ function modelSettingsEntry(map2, id) {
20483
+ return { ...map2?.[id] ?? {} };
20484
+ }
20485
+ function modelEnabled(model, settings, authoritative = true) {
20486
+ if (settings?.enabled !== void 0) return settings.enabled;
20487
+ if (authoritative) return true;
20488
+ return model.enabled !== false;
20489
+ }
20490
+ var DECIMAL_TEXT = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/u;
20491
+ function parseNumberInput(raw, spec) {
20492
+ const text2 = raw.trim();
20493
+ if (text2 === "") {
20494
+ return spec.allowEmpty === true ? { ok: true, value: void 0 } : { ok: false, message: `${spec.label}\u4E0D\u80FD\u4E3A\u7A7A\u3002` };
20495
+ }
20496
+ const integral = spec.integer === true;
20497
+ const value = DECIMAL_TEXT.test(text2) ? Number(text2) : Number.NaN;
20498
+ if (!DECIMAL_TEXT.test(text2) || !Number.isFinite(value) || value < spec.min || value > spec.max || integral && !Number.isSafeInteger(value)) {
20499
+ return {
20500
+ ok: false,
20501
+ message: `${spec.label}\u9700\u4E3A ${spec.min} \u5230 ${spec.max} \u4E4B\u95F4\u7684${integral ? "\u6574\u6570" : "\u6570\u5B57"}${spec.allowEmpty === true ? "\uFF0C\u6216\u7559\u7A7A\u7EE7\u627F\u9ED8\u8BA4\u3002" : "\u3002"}`
20502
+ };
20503
+ }
20504
+ return { ok: true, value };
20505
+ }
20506
+ function protocolEndpoint(baseURL, protocol) {
20507
+ return inferenceEndpoint(baseURL, protocol);
20508
+ }
20509
+ function normaliseQuotaRefreshSeconds(value) {
20510
+ if (value === void 0 || !Number.isSafeInteger(value) || value < MIN_QUOTA_REFRESH_SECONDS || value > MAX_QUOTA_REFRESH_SECONDS) return void 0;
20511
+ return value;
20512
+ }
20513
+ function quotaAutoRefresh(secondsRaw, nextRefreshAt, now, backingOff = false) {
20514
+ const intervalSeconds = normaliseQuotaRefreshSeconds(secondsRaw);
20515
+ if (intervalSeconds === void 0) return void 0;
20516
+ const intervalMs = intervalSeconds * 1e3;
20517
+ const planMs = nextRefreshAt === void 0 ? Number.NaN : Date.parse(nextRefreshAt);
20518
+ const planDelay = Number.isFinite(planMs) ? planMs - now : Number.POSITIVE_INFINITY;
20519
+ const plannedDelay = Math.max(1e3, Math.floor(planDelay) + 500);
20520
+ const delayMs = Number.isFinite(planDelay) && planDelay > 0 ? backingOff ? plannedDelay : Math.min(intervalMs, plannedDelay) : intervalMs;
20521
+ return { delayMs, at: new Date(now + delayMs).toISOString(), intervalSeconds };
20522
+ }
20523
+ function Overview({ status, busy, onRefresh, nextAutoRefreshAt, refreshIntervalSeconds, onConnection }) {
20271
20524
  return h(
20272
20525
  React.Fragment,
20273
20526
  null,
20527
+ status !== void 0 && (status.apiKeyConfigured === false || status.quota.state === "need-login") ? h(
20528
+ "section",
20529
+ { className: "odc-card odc-row", "aria-label": "\u8FDE\u63A5\u5F85\u5B8C\u6210" },
20530
+ h(
20531
+ "div",
20532
+ null,
20533
+ h("h3", null, "\u5148\u5B8C\u6210\u8FDE\u63A5"),
20534
+ h("p", { className: "odc-hint" }, status.apiKeyConfigured === false ? "\u63A8\u7406\u5C1A\u7F3A API Key\uFF1B\u989D\u5EA6\u767B\u5F55\u4E0E\u63A8\u7406\u51ED\u636E\u5206\u522B\u914D\u7F6E\u3002" : "API Key \u5DF2\u914D\u7F6E\uFF1B\u767B\u5F55\u540E\u5373\u53EF\u67E5\u770B\u8D26\u53F7\u989D\u5EA6\u3002")
20535
+ ),
20536
+ onConnection === void 0 ? null : h("button", { type: "button", className: "odc-button odc-button-primary", onClick: onConnection }, "\u524D\u5F80\u8FDE\u63A5\u4E0E\u767B\u5F55")
20537
+ ) : null,
20274
20538
  h(
20275
20539
  "div",
20276
20540
  { className: "odc-card-head" },
20277
20541
  h("div", null, h("h3", null, "\u989D\u5EA6\u6982\u89C8"), h("p", { className: "odc-hint" }, "\u5957\u9910\u7A97\u53E3\u4E0E\u94B1\u5305\u4F59\u989D\u5206\u5F00\u8BA1\u91CF\uFF1B\u672A\u77E5\u6570\u636E\u4E0D\u505A\u63A8\u7B97\u3002")),
20278
20542
  h("button", { className: "odc-button", type: "button", disabled: busy, onClick: onRefresh }, busy ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0\u989D\u5EA6")
20279
20543
  ),
20280
- h(QuotaView, { quota: status?.quota })
20544
+ h(QuotaView, { quota: status?.quota, nextAutoRefreshAt, refreshIntervalSeconds })
20281
20545
  );
20282
20546
  }
20283
- function ModelGroup({ title, group, models }) {
20547
+ var NUMERIC_FIELDS = {
20548
+ temperature: { label: "\u6E29\u5EA6", hint: "0\u20132\uFF1B\u7559\u7A7A\u7EE7\u627F\u9ED8\u8BA4\u3002", min: 0, max: 2 },
20549
+ topP: { label: "Top P", hint: "0\u20131\uFF1B\u7559\u7A7A\u7EE7\u627F\u9ED8\u8BA4\u3002", min: 0, max: 1 },
20550
+ maxTokens: { label: "\u6700\u5927\u8F93\u51FA\uFF08tokens\uFF09", hint: "\u6B63\u6574\u6570\uFF1B\u7559\u7A7A\u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\u3002", min: 1, max: MAX_OUTPUT_TOKENS, integer: true },
20551
+ contextWindow: { label: "\u4E0A\u4E0B\u6587\u9884\u7B97\uFF08tokens\uFF09", hint: "\u6B63\u6574\u6570\uFF1B\u7559\u7A7A\u7EE7\u627F\u76EE\u5F55\u9ED8\u8BA4\u3002", min: 1, max: MAX_CONTEXT_WINDOW, integer: true }
20552
+ };
20553
+ var NUMERIC_ORDER = ["temperature", "topP", "maxTokens", "contextWindow"];
20554
+ function OptionalNumberField({ id, field, value, disabled, resetToken, onSave }) {
20555
+ const spec = NUMERIC_FIELDS[field];
20556
+ const mounted = useMounted();
20557
+ const [draft, setDraft] = React.useState(void 0);
20558
+ const [error62, setError] = React.useState(void 0);
20559
+ React.useEffect(() => {
20560
+ setDraft(void 0);
20561
+ setError(void 0);
20562
+ }, [value, resetToken]);
20563
+ const commit = async () => {
20564
+ if (disabled || draft === void 0) return;
20565
+ const result = parseNumberInput(draft, {
20566
+ label: spec.label,
20567
+ min: spec.min,
20568
+ max: spec.max,
20569
+ ...spec.integer === true ? { integer: true } : {},
20570
+ allowEmpty: true
20571
+ });
20572
+ if (!result.ok) {
20573
+ setError(result.message);
20574
+ return;
20575
+ }
20576
+ if (result.value === value) {
20577
+ setDraft(void 0);
20578
+ setError(void 0);
20579
+ return;
20580
+ }
20581
+ const saved = await onSave(result.value);
20582
+ if (!mounted.current) return;
20583
+ if (saved) {
20584
+ setDraft(void 0);
20585
+ setError(void 0);
20586
+ } else setError("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20587
+ };
20588
+ return h(
20589
+ "div",
20590
+ { className: "odc-field odc-field-compact" },
20591
+ h("label", { htmlFor: id }, spec.label),
20592
+ h("input", {
20593
+ id,
20594
+ className: "odc-input",
20595
+ type: "number",
20596
+ inputMode: "decimal",
20597
+ min: spec.min,
20598
+ max: spec.max,
20599
+ step: spec.integer === true ? 1 : 0.1,
20600
+ value: draft ?? (value === void 0 ? "" : String(value)),
20601
+ disabled,
20602
+ "aria-invalid": error62 !== void 0,
20603
+ "aria-describedby": `${id}-hint`,
20604
+ onChange: (event) => setDraft(event.target.value),
20605
+ onBlur: () => {
20606
+ void commit();
20607
+ }
20608
+ }),
20609
+ h("small", {
20610
+ className: error62 === void 0 ? "odc-hint" : "odc-hint odc-field-error",
20611
+ id: `${id}-hint`,
20612
+ ...error62 === void 0 ? {} : { role: "alert" }
20613
+ }, error62 ?? spec.hint)
20614
+ );
20615
+ }
20616
+ function EffortField({ id, model, value, disabled, onChange }) {
20617
+ const mounted = useMounted();
20618
+ const [error62, setError] = React.useState(void 0);
20619
+ const listed = model.reasoningEfforts ?? [];
20620
+ const options = value === void 0 || listed.includes(value) ? listed : [...listed, value];
20621
+ const unknown2 = options.length === 0;
20622
+ return h(
20623
+ "div",
20624
+ { className: "odc-field odc-field-compact" },
20625
+ h("label", { htmlFor: id }, "\u9ED8\u8BA4\u601D\u8003\u6DF1\u5EA6"),
20626
+ h("select", {
20627
+ id,
20628
+ className: "odc-input",
20629
+ value: value ?? "",
20630
+ disabled: disabled || unknown2,
20631
+ "aria-describedby": `${id}-hint`,
20632
+ onChange: (event) => {
20633
+ const next = event.target.value;
20634
+ void (async () => {
20635
+ const saved = await onChange(next);
20636
+ if (mounted.current) setError(saved ? void 0 : "\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20637
+ })();
20638
+ }
20639
+ }, [
20640
+ h("option", { key: "", value: "" }, "\u7EE7\u627F\u9ED8\u8BA4"),
20641
+ ...options.map((effort) => h("option", { key: effort, value: effort }, effort))
20642
+ ]),
20643
+ h("small", {
20644
+ className: error62 === void 0 ? "odc-hint" : "odc-hint odc-field-error",
20645
+ id: `${id}-hint`,
20646
+ ...error62 === void 0 ? {} : { role: "alert" }
20647
+ }, error62 ?? (unknown2 ? "\u8BE5\u6A21\u578B\u76EE\u5F55\u672A\u63D0\u4F9B\u601D\u8003\u6863\u4F4D\uFF0C\u65E0\u6CD5\u8986\u76D6\u3002" : "\u53EA\u5217\u51FA\u8BE5\u6A21\u578B\u5DF2\u77E5\u6863\u4F4D\uFF1B\u7559\u7A7A\u7EE7\u627F\u9ED8\u8BA4\u3002"))
20648
+ );
20649
+ }
20650
+ function ModelRow({ model, settings, authoritative, writable, pending, onCommit }) {
20651
+ const id = React.useId();
20652
+ const mounted = useMounted();
20653
+ const [error62, setError] = React.useState(void 0);
20654
+ const [resetToken, setResetToken] = React.useState(0);
20655
+ const enabled = modelEnabled(model, settings, authoritative);
20656
+ const hasOverrides = Object.keys(settings).length > 0;
20657
+ const commit = async (next) => {
20658
+ const saved = await onCommit(next);
20659
+ if (mounted.current) setError(saved ? void 0 : "\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20660
+ return saved;
20661
+ };
20662
+ const saveField = async (key, value) => {
20663
+ const next = { ...settings };
20664
+ if (value === void 0) delete next[key];
20665
+ else next[key] = value;
20666
+ const output2 = next.maxTokens ?? model.maxOutputTokens;
20667
+ const context = next.contextWindow ?? model.contextWindow;
20668
+ if (output2 !== void 0 && context !== void 0 && output2 >= context) {
20669
+ setError("\u6700\u5927\u8F93\u51FA\u5FC5\u987B\u5C0F\u4E8E\u4E0A\u4E0B\u6587\u9884\u7B97\uFF1B\u8BF7\u5148\u589E\u5927\u4E0A\u4E0B\u6587\u9884\u7B97\u6216\u51CF\u5C0F\u6700\u5927\u8F93\u51FA\u3002");
20670
+ return false;
20671
+ }
20672
+ setError(void 0);
20673
+ return commit(next);
20674
+ };
20675
+ const reset = async () => {
20676
+ setError(void 0);
20677
+ setResetToken((token) => token + 1);
20678
+ await commit(void 0);
20679
+ };
20680
+ const facts = [
20681
+ `\u4E0A\u4E0B\u6587 ${formatTokens(model.contextWindow)}`,
20682
+ `\u6700\u5927\u8F93\u51FA ${formatTokens(model.maxOutputTokens)}`,
20683
+ ...model.reasoningEfforts === void 0 || model.reasoningEfforts.length === 0 ? [] : [`\u601D\u8003\u6863\u4F4D ${model.reasoningEfforts.join(" / ")}`]
20684
+ ].join(" \xB7 ");
20685
+ return h(
20686
+ "li",
20687
+ { className: "odc-model-item" },
20688
+ h(
20689
+ "div",
20690
+ { className: "odc-model-head" },
20691
+ h(
20692
+ "label",
20693
+ { className: "odc-switch odc-model-switch" },
20694
+ h("input", {
20695
+ type: "checkbox",
20696
+ checked: enabled,
20697
+ disabled: !writable || pending,
20698
+ onChange: (event) => {
20699
+ void commit({ ...settings, enabled: event.target.checked });
20700
+ }
20701
+ }),
20702
+ h("span", null, h("strong", null, model.name), h("code", null, model.id))
20703
+ ),
20704
+ h(
20705
+ "div",
20706
+ { className: "odc-actions" },
20707
+ enabled ? null : h("span", { className: "odc-chip" }, "\u5DF2\u7981\u7528"),
20708
+ h("button", { className: "odc-button odc-button-small", type: "button", disabled: !writable || pending || !hasOverrides, onClick: () => {
20709
+ void reset();
20710
+ } }, "\u91CD\u7F6E")
20711
+ )
20712
+ ),
20713
+ h(
20714
+ "details",
20715
+ { className: "odc-model-details" },
20716
+ h("summary", { "aria-label": `${model.name} \u53C2\u6570\u8BBE\u7F6E` }, hasOverrides ? "\u53C2\u6570\u8BBE\u7F6E \xB7 \u5DF2\u81EA\u5B9A\u4E49" : "\u53C2\u6570\u8BBE\u7F6E \xB7 \u4F7F\u7528\u9ED8\u8BA4\u503C"),
20717
+ h("p", { className: "odc-model-meta" }, facts),
20718
+ h("p", { className: "odc-hint" }, "\u6570\u503C\u79BB\u5F00\u8F93\u5165\u6846\u540E\u4FDD\u5B58\uFF0C\u7559\u7A7A\u7EE7\u627F\u9ED8\u8BA4\u3002"),
20719
+ h(
20720
+ "div",
20721
+ { className: "odc-model-editor" },
20722
+ NUMERIC_ORDER.map((key) => h(OptionalNumberField, {
20723
+ key,
20724
+ id: `${id}-${key}`,
20725
+ field: key,
20726
+ value: settings[key],
20727
+ disabled: !writable || pending,
20728
+ resetToken,
20729
+ onSave: (value) => saveField(key, value)
20730
+ })),
20731
+ h(EffortField, {
20732
+ id: `${id}-effort`,
20733
+ model,
20734
+ value: settings.reasoningEffort,
20735
+ disabled: !writable || pending,
20736
+ onChange: async (value) => {
20737
+ const next = { ...settings };
20738
+ if (value === "") delete next.reasoningEffort;
20739
+ else next.reasoningEffort = value;
20740
+ setError(void 0);
20741
+ return commit(next);
20742
+ }
20743
+ })
20744
+ )
20745
+ ),
20746
+ error62 !== void 0 ? h("p", { className: "odc-hint odc-field-error", role: "alert" }, error62) : null
20747
+ );
20748
+ }
20749
+ function ModelGroup({ title, group, models, settingsMap, authoritative, writable, pending, onCommit }) {
20284
20750
  const [query, setQuery] = React.useState("");
20751
+ const [expanded, setExpanded] = React.useState(false);
20285
20752
  const id = React.useId();
20286
20753
  const normalized = query.trim().toLocaleLowerCase();
20287
- const filtered = models.filter((model) => model.group === group && (normalized === "" || `${model.name} ${model.id}`.toLocaleLowerCase().includes(normalized)));
20754
+ const groupModels = models.filter((model) => model.group === group);
20755
+ const filtered = groupModels.filter((model) => normalized === "" || `${model.name} ${model.id}`.toLocaleLowerCase().includes(normalized));
20756
+ const visible = normalized !== "" || expanded ? filtered : filtered.slice(0, 6);
20757
+ const enabledCount = groupModels.filter((model) => modelEnabled(model, settingsMap[model.id], authoritative)).length;
20288
20758
  return h(
20289
20759
  "section",
20290
20760
  { className: "odc-card", "aria-labelledby": `${id}-title` },
@@ -20292,7 +20762,7 @@ function ModelGroup({ title, group, models }) {
20292
20762
  "div",
20293
20763
  { className: "odc-card-head" },
20294
20764
  h("h3", { id: `${id}-title` }, title),
20295
- h("span", { className: "odc-model-count" }, `${filtered.length} / ${models.filter((model) => model.group === group).length}`)
20765
+ h("span", { className: "odc-model-count" }, `${enabledCount} / ${groupModels.length} \u5DF2\u542F\u7528`)
20296
20766
  ),
20297
20767
  h("label", { className: "odc-label", htmlFor: `${id}-search` }, `\u641C\u7D22${title}`),
20298
20768
  h("input", {
@@ -20300,28 +20770,291 @@ function ModelGroup({ title, group, models }) {
20300
20770
  className: "odc-input",
20301
20771
  type: "search",
20302
20772
  value: query,
20303
- placeholder: "\u8F93\u5165\u6A21\u578B\u540D\u79F0\u6216 ID",
20773
+ placeholder: "\u641C\u7D22\u5168\u90E8\u6A21\u578B\u7684\u540D\u79F0\u6216 ID",
20304
20774
  onChange: (event) => setQuery(event.target.value)
20305
20775
  }),
20306
- filtered.length === 0 ? h("p", { className: "odc-hint", role: "status" }, "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B\u3002") : h("ul", { className: "odc-model-list" }, filtered.map((model) => h(
20307
- "li",
20308
- { className: "odc-model-item", key: model.id },
20309
- h("strong", null, model.name),
20310
- h("code", null, model.id),
20311
- h("span", { className: "odc-label" }, `\u5F52\u5C5E\uFF1A${title}`)
20312
- )))
20776
+ filtered.length === 0 ? h("p", { className: "odc-hint", role: "status" }, "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B\u3002") : h("ul", { id: `${id}-list`, className: "odc-model-list" }, visible.map((model) => h(ModelRow, {
20777
+ key: model.id,
20778
+ model,
20779
+ settings: modelSettingsEntry(settingsMap, model.id),
20780
+ authoritative,
20781
+ writable,
20782
+ pending,
20783
+ onCommit: (next) => onCommit(model.id, next)
20784
+ }))),
20785
+ normalized === "" && filtered.length > 6 ? h("button", {
20786
+ type: "button",
20787
+ className: "odc-button",
20788
+ style: { marginTop: 12 },
20789
+ "aria-expanded": expanded,
20790
+ "aria-controls": `${id}-list`,
20791
+ onClick: () => setExpanded((value) => !value)
20792
+ }, expanded ? "\u6536\u8D77\u6A21\u578B\u5217\u8868" : `\u663E\u793A\u5168\u90E8 ${filtered.length} \u4E2A\u6A21\u578B`) : null
20313
20793
  );
20314
20794
  }
20315
- function Models({ models }) {
20795
+ function Models({ status, formSnapshot, setFormField, pendingField, onRefreshModels, refreshingModels, notice }) {
20796
+ const formReady = formSnapshot.status === "ready" && formSnapshot.value !== void 0;
20797
+ const settingsMap = formReady ? formSnapshot.value?.modelSettings ?? {} : status?.config.modelSettings ?? {};
20798
+ const authoritative = formReady || status?.config.modelSettings !== void 0;
20799
+ const writable = formSnapshot.status === "ready" && formSnapshot.value !== void 0 && formSnapshot.writable;
20800
+ const pending = pendingField.includes("modelSettings");
20801
+ const latestSettings = React.useRef(settingsMap);
20802
+ React.useEffect(() => {
20803
+ latestSettings.current = settingsMap;
20804
+ }, [settingsMap]);
20805
+ const saving = React.useRef(false);
20806
+ const commit = async (id, next) => {
20807
+ if (!writable || saving.current) return false;
20808
+ saving.current = true;
20809
+ try {
20810
+ return await setFormField("modelSettings", writeModelSettings(latestSettings.current, id, next));
20811
+ } finally {
20812
+ saving.current = false;
20813
+ }
20814
+ };
20815
+ const source = status?.modelsSource === "openai" ? "OpenAI /models \u53D1\u73B0" : status?.modelsSource === "builtin" ? "\u5185\u7F6E\u76EE\u5F55" : "\u5C1A\u672A\u786E\u8BA4";
20816
+ const updated = status?.modelsUpdatedAt === void 0 ? "" : ` \xB7 \u66F4\u65B0\u4E8E ${formatTime(status.modelsUpdatedAt)}`;
20817
+ const models = status?.models ?? [];
20316
20818
  return h(
20317
20819
  React.Fragment,
20318
20820
  null,
20319
- h("div", null, h("h3", null, "\u53EF\u7528\u6A21\u578B"), h("p", { className: "odc-hint" }, "\u4E24\u4E2A Provider \u72EC\u7ACB\u7B5B\u9009\u3002\u8FD9\u91CC\u4EC5\u5C55\u793A\u76EE\u5F55\uFF0C\u4E0D\u4FEE\u6539 API \u6216\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\u3002")),
20320
20821
  h(
20822
+ "div",
20823
+ { className: "odc-card-head" },
20824
+ h(
20825
+ "div",
20826
+ null,
20827
+ h("h3", null, "\u53EF\u7528\u6A21\u578B"),
20828
+ h("p", { className: "odc-hint" }, "\u4E24\u4E2A Provider \u72EC\u7ACB\u7B5B\u9009\u3002\u9010\u6A21\u578B\u7684\u542F\u7528\u4E0E\u53C2\u6570\u4F1A\u4FDD\u5B58\u5230\u63D2\u4EF6\u8BBE\u7F6E\uFF0C\u53EA\u5F71\u54CD\u672C\u63D2\u4EF6\u7684\u6A21\u578B\u9009\u62E9\u4E0E\u8BF7\u6C42\u3002")
20829
+ ),
20830
+ h(
20831
+ "div",
20832
+ { className: "odc-actions" },
20833
+ h("span", { className: "odc-label", role: "status" }, `\u6765\u6E90\uFF1A${source}${updated}`),
20834
+ h("button", {
20835
+ className: "odc-button",
20836
+ type: "button",
20837
+ disabled: refreshingModels || status === void 0 || status.apiKeyConfigured === false,
20838
+ onClick: onRefreshModels
20839
+ }, refreshingModels ? "\u6B63\u5728\u5237\u65B0\u6A21\u578B\u2026" : "\u4ECE OpenAI \u5237\u65B0\u6A21\u578B")
20840
+ )
20841
+ ),
20842
+ status?.apiKeyConfigured === false ? h("p", { className: "odc-hint" }, "\u5237\u65B0\u6A21\u578B\u9700\u8981\u53EF\u7528\u7684 API Key\uFF1B\u4FDD\u5B58 Key \u540E\u91CD\u8BD5\u3002") : null,
20843
+ status?.modelsError !== void 0 ? h("p", { className: "odc-notice", "data-kind": "error", role: "alert" }, status.modelsError) : null,
20844
+ h(NoticeView, { notice }),
20845
+ models.length === 0 ? h("p", { className: "odc-hint", role: "status" }, "\u6682\u65E0\u53EF\u7528\u6A21\u578B\uFF1B\u53EF\u5237\u65B0\u6A21\u578B\u5217\u8868\u540E\u91CD\u8BD5\u3002") : h(
20321
20846
  "div",
20322
20847
  { className: "odc-model-groups" },
20323
- h(ModelGroup, { title: "\u5957\u9910\u8D60\u9001 Provider", group: "plan", models: models ?? [] }),
20324
- h(ModelGroup, { title: "\u4ED8\u8D39 Provider", group: "paid", models: models ?? [] })
20848
+ h(ModelGroup, { title: "\u5957\u9910\u8D60\u9001 Provider", group: "plan", models, settingsMap, authoritative, writable, pending, onCommit: commit }),
20849
+ h(ModelGroup, { title: "\u4ED8\u8D39 Provider", group: "paid", models, settingsMap, authoritative, writable, pending, onCommit: commit })
20850
+ )
20851
+ );
20852
+ }
20853
+ function NumberField({ id, label, hint, value, min, max, integer: integer2, disabled, onCommit }) {
20854
+ const mounted = useMounted();
20855
+ const [draft, setDraft] = React.useState(String(value));
20856
+ const [error62, setError] = React.useState(void 0);
20857
+ React.useEffect(() => {
20858
+ setDraft(String(value));
20859
+ setError(void 0);
20860
+ }, [value]);
20861
+ const commit = async () => {
20862
+ const result = parseNumberInput(draft, { label, min, max, ...integer2 ? { integer: true } : {} });
20863
+ if (!result.ok) {
20864
+ setError(result.message);
20865
+ return;
20866
+ }
20867
+ if (result.value === void 0 || result.value === value) {
20868
+ setDraft(String(value));
20869
+ setError(void 0);
20870
+ return;
20871
+ }
20872
+ const saved = await onCommit(result.value);
20873
+ if (!mounted.current) return;
20874
+ if (saved) setError(void 0);
20875
+ else setError("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20876
+ };
20877
+ return h(
20878
+ "div",
20879
+ { className: "odc-field" },
20880
+ h("label", { htmlFor: id }, label),
20881
+ h("input", {
20882
+ id,
20883
+ className: "odc-input",
20884
+ type: "number",
20885
+ inputMode: "numeric",
20886
+ min,
20887
+ max,
20888
+ step: integer2 ? 1 : 0.1,
20889
+ value: draft,
20890
+ disabled,
20891
+ "aria-invalid": error62 !== void 0,
20892
+ "aria-describedby": `${id}-hint`,
20893
+ onChange: (event) => setDraft(event.target.value),
20894
+ onBlur: () => {
20895
+ void commit();
20896
+ }
20897
+ }),
20898
+ h("small", {
20899
+ className: error62 === void 0 ? "odc-hint" : "odc-hint odc-field-error",
20900
+ id: `${id}-hint`,
20901
+ ...error62 === void 0 ? {} : { role: "alert" }
20902
+ }, error62 ?? hint)
20903
+ );
20904
+ }
20905
+ function Advanced({ status, formSnapshot, setFormField, pendingField }) {
20906
+ const [apiKeyEnv, setApiKeyEnv] = React.useState(status?.config.apiKeyEnv ?? "");
20907
+ const [keyNotice, setKeyNotice] = React.useState(null);
20908
+ const mounted = useMounted();
20909
+ React.useEffect(() => {
20910
+ setApiKeyEnv(formSnapshot.value?.apiKeyEnv ?? status?.config.apiKeyEnv ?? "");
20911
+ }, [formSnapshot.value?.apiKeyEnv, status?.config.apiKeyEnv]);
20912
+ const writable = formSnapshot.status === "ready" && formSnapshot.value !== void 0 && formSnapshot.writable;
20913
+ const configuredProtocol = formSnapshot.value?.apiProtocol ?? status?.config.apiProtocol;
20914
+ const protocol = configuredProtocol !== void 0 && API_PROTOCOL_OPTIONS.some((option) => option.id === configuredProtocol) ? configuredProtocol : "chat-completions";
20915
+ const baseURL = formSnapshot.value?.baseURL ?? status?.config.baseURL ?? "";
20916
+ const requestTimeout = formSnapshot.value?.requestTimeoutMs ?? status?.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
20917
+ const streamIdleTimeout = formSnapshot.value?.streamIdleTimeoutMs ?? status?.config.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
20918
+ const refreshSeconds = formSnapshot.value?.quotaRefreshSeconds ?? status?.config.quotaRefreshSeconds ?? DEFAULT_QUOTA_REFRESH_SECONDS;
20919
+ const selected = API_PROTOCOL_OPTIONS.find((option) => option.id === protocol);
20920
+ const saveKeyRef = async () => {
20921
+ const value = apiKeyEnv.trim();
20922
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/u.test(value)) {
20923
+ setKeyNotice({ kind: "error", text: "\u5F15\u7528\u540D\u9700\u4E3A 1\u2013128 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u6216\u4E0B\u5212\u7EBF\uFF0C\u4E14\u4E0D\u80FD\u4EE5\u6570\u5B57\u5F00\u5934\u3002" });
20924
+ return;
20925
+ }
20926
+ setKeyNotice(null);
20927
+ try {
20928
+ const accepted = await setFormField("apiKeyEnv", value);
20929
+ if (mounted.current) setKeyNotice(accepted ? { kind: "status", text: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002" } : { kind: "error", text: "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5\u3002" });
20930
+ } catch {
20931
+ if (mounted.current) setKeyNotice({ kind: "error", text: GENERIC_ERROR });
20932
+ }
20933
+ };
20934
+ return h(
20935
+ React.Fragment,
20936
+ null,
20937
+ h(
20938
+ "section",
20939
+ { className: "odc-card" },
20940
+ h("h3", null, "\u7A7A\u8F6E\u76D1\u63A7"),
20941
+ h("p", { className: "odc-hint" }, "\u72EC\u7ACB\u76D1\u63A7\u5957\u9910\u8D60\u9001\u6A21\u578B\u7684\u7A7A\u8F6E\u6216\u534F\u8BAE\u6B8B\u6E23\uFF1B\u4E0D\u5F71\u54CD\u4ED8\u8D39 Provider \u7684\u5F00\u5173\u3002"),
20942
+ h("div", { style: { marginTop: 14 } }, h(Switch, {
20943
+ checked: formSnapshot.value?.emptyTurnGuard ?? status?.config.emptyTurnGuard ?? true,
20944
+ disabled: !writable || pendingField.includes("emptyTurnGuard"),
20945
+ label: "\u542F\u7528\u7A7A\u8F6E\u76D1\u63A7",
20946
+ hint: `\u672C\u6B21\u8FD0\u884C\u5DF2\u62E6\u622A ${status?.guardedTurns ?? "\u2014"} \u8F6E\u3002\u68C0\u6D4B\u540E\u7531\u5BBF\u4E3B\u91CD\u8BD5\u7B56\u7565\u5904\u7406\uFF0C\u5BBF\u4E3B\u91CD\u8BD5\u4F1A\u989D\u5916\u6D88\u8017\u989D\u5EA6\u3002`,
20947
+ onChange: (checked) => {
20948
+ void setFormField("emptyTurnGuard", checked);
20949
+ }
20950
+ }))
20951
+ ),
20952
+ h(
20953
+ "section",
20954
+ { className: "odc-card" },
20955
+ h("h3", null, "\u63A8\u7406\u534F\u8BAE"),
20956
+ h("p", { className: "odc-hint" }, "\u63D2\u4EF6\u6309\u6240\u9009\u534F\u8BAE\u7EC4\u88C5\u8BF7\u6C42\uFF1B\u6A21\u578B\u53D1\u73B0\u59CB\u7EC8\u8BFB\u53D6 OpenAI /models\uFF0C\u4E0E\u8FD9\u91CC\u7684\u9009\u62E9\u65E0\u5173\u3002"),
20957
+ h(
20958
+ "div",
20959
+ { className: "odc-field" },
20960
+ h("label", { htmlFor: "odc-api-protocol" }, "API \u534F\u8BAE"),
20961
+ h("select", {
20962
+ id: "odc-api-protocol",
20963
+ className: "odc-input",
20964
+ value: protocol,
20965
+ disabled: !writable || pendingField.includes("apiProtocol"),
20966
+ onChange: (event) => {
20967
+ void setFormField("apiProtocol", event.target.value);
20968
+ }
20969
+ }, API_PROTOCOL_OPTIONS.map((option) => h("option", { key: option.id, value: option.id }, option.name))),
20970
+ h("small", { className: "odc-hint" }, selected?.description ?? "\u9009\u62E9\u4E0E\u7F51\u5173\u517C\u5BB9\u7684\u63A8\u7406\u534F\u8BAE\u3002")
20971
+ ),
20972
+ h(
20973
+ "div",
20974
+ { className: "odc-readonly" },
20975
+ h(
20976
+ "div",
20977
+ null,
20978
+ h("span", { className: "odc-label" }, "\u5F53\u524D\u7F51\u5173\u5730\u5740\uFF08\u53EA\u8BFB\uFF09"),
20979
+ h("code", { className: "odc-code" }, baseURL === "" ? "\u2014" : baseURL)
20980
+ ),
20981
+ h(
20982
+ "div",
20983
+ null,
20984
+ h("span", { className: "odc-label" }, "\u5F53\u524D\u63A8\u7406\u8DEF\u5F84\uFF08\u53EA\u8BFB\uFF09"),
20985
+ h("code", { className: "odc-code" }, protocolEndpoint(baseURL, protocol))
20986
+ ),
20987
+ h("small", { className: "odc-hint" }, "\u5730\u5740\u7531\u63D2\u4EF6\u914D\u7F6E\u63D0\u4F9B\uFF0C\u65E0\u9700\u5728\u6B64\u586B\u5199\uFF1B\u5207\u6362\u534F\u8BAE\u53EA\u6539\u53D8\u8BF7\u6C42\u8DEF\u5F84\u4E0E\u62A5\u6587\u683C\u5F0F\u3002")
20988
+ )
20989
+ ),
20990
+ h(
20991
+ "section",
20992
+ { className: "odc-card" },
20993
+ h("h3", null, "\u51ED\u636E\u4E0E\u7F51\u5173"),
20994
+ h("p", { className: "odc-hint" }, "\u8FD9\u91CC\u53EA\u4FDD\u5B58\u51ED\u636E\u5F15\u7528\u540D\u79F0\uFF0C\u4E0D\u586B\u5199 Key \u672C\u8EAB\uFF1B\u7F51\u5173\u5730\u5740\u7531\u63D2\u4EF6\u914D\u7F6E\u6258\u7BA1\u3002"),
20995
+ h(
20996
+ "div",
20997
+ { className: "odc-field" },
20998
+ h("label", { htmlFor: "odc-api-key-env" }, "API Key \u73AF\u5883\u53D8\u91CF / \u51ED\u636E\u5F15\u7528\u540D"),
20999
+ h("input", {
21000
+ id: "odc-api-key-env",
21001
+ className: "odc-input",
21002
+ type: "text",
21003
+ value: apiKeyEnv,
21004
+ disabled: !writable,
21005
+ autoCapitalize: "off",
21006
+ spellCheck: false,
21007
+ onChange: (event) => setApiKeyEnv(event.target.value)
21008
+ }),
21009
+ h("small", { className: "odc-hint" }, "\u5F15\u7528\u540D\u5BF9\u5E94\u7684\u51ED\u636E\u4ECD\u4F18\u5148\u4E8E\u672C\u63D2\u4EF6\u4FDD\u5B58\u7684 Key\u3002")
21010
+ ),
21011
+ h(
21012
+ "div",
21013
+ { className: "odc-actions", style: { marginTop: 10 } },
21014
+ h("button", { className: "odc-button", type: "button", disabled: !writable || pendingField.includes("apiKeyEnv"), onClick: () => {
21015
+ void saveKeyRef();
21016
+ } }, "\u4FDD\u5B58\u5F15\u7528\u540D")
21017
+ ),
21018
+ h(NoticeView, { notice: keyNotice })
21019
+ ),
21020
+ h(
21021
+ "section",
21022
+ { className: "odc-card" },
21023
+ h("h3", null, "\u8BF7\u6C42\u65F6\u9650\u4E0E\u81EA\u52A8\u5237\u65B0"),
21024
+ h("p", { className: "odc-hint" }, "\u6570\u503C\u5728\u79BB\u5F00\u8F93\u5165\u6846\u65F6\u6821\u9A8C\u5E76\u4FDD\u5B58\uFF1B\u7559\u7A7A\u6216\u8D8A\u754C\u4E0D\u4F1A\u5199\u5165\u3002"),
21025
+ h(NumberField, {
21026
+ id: "odc-request-timeout",
21027
+ label: "\u8BF7\u6C42\u603B\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
21028
+ hint: "\u4ECE\u53D1\u51FA\u8BF7\u6C42\u5230\u54CD\u5E94\u7ED3\u675F\u7684\u4E0A\u9650\uFF0C1000\u20133600000\u3002",
21029
+ value: requestTimeout,
21030
+ min: MIN_REQUEST_TIMEOUT_MS,
21031
+ max: MAX_REQUEST_TIMEOUT_MS,
21032
+ integer: true,
21033
+ disabled: !writable || pendingField.includes("requestTimeoutMs"),
21034
+ onCommit: (value) => setFormField("requestTimeoutMs", value)
21035
+ }),
21036
+ h(NumberField, {
21037
+ id: "odc-stream-idle-timeout",
21038
+ label: "\u6D41\u5F0F\u7A7A\u95F2\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
21039
+ hint: "\u6D41\u5F0F\u54CD\u5E94\u65E0\u65B0\u6570\u636E\u7684\u6700\u957F\u7B49\u5F85\uFF0C1000\u20131800000\u3002",
21040
+ value: streamIdleTimeout,
21041
+ min: MIN_STREAM_IDLE_TIMEOUT_MS,
21042
+ max: MAX_STREAM_IDLE_TIMEOUT_MS,
21043
+ integer: true,
21044
+ disabled: !writable || pendingField.includes("streamIdleTimeoutMs"),
21045
+ onCommit: (value) => setFormField("streamIdleTimeoutMs", value)
21046
+ }),
21047
+ h(NumberField, {
21048
+ id: "odc-quota-refresh",
21049
+ label: "\u989D\u5EA6\u81EA\u52A8\u5237\u65B0\u95F4\u9694\uFF08\u79D2\uFF09",
21050
+ hint: `5\u20133600\uFF1B\u9ED8\u8BA4 ${DEFAULT_QUOTA_REFRESH_SECONDS} \u79D2\uFF0C\u9875\u9762\u5173\u95ED\u540E\u505C\u6B62\u3002`,
21051
+ value: refreshSeconds,
21052
+ min: MIN_QUOTA_REFRESH_SECONDS,
21053
+ max: MAX_QUOTA_REFRESH_SECONDS,
21054
+ integer: true,
21055
+ disabled: !writable || pendingField.includes("quotaRefreshSeconds"),
21056
+ onCommit: (value) => setFormField("quotaRefreshSeconds", value)
21057
+ })
20325
21058
  )
20326
21059
  );
20327
21060
  }
@@ -20350,7 +21083,7 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20350
21083
  const state = login?.state ?? "idle";
20351
21084
  const inFlight = loginInFlight(state);
20352
21085
  const startLogin = async () => {
20353
- if (status === void 0 || busy) return;
21086
+ if (status === void 0 || busy || keyBusy) return;
20354
21087
  const version2 = ++actionVersion.current;
20355
21088
  setBusy(true);
20356
21089
  setNotice({ kind: "status", text: "\u6B63\u5728\u542F\u52A8\u5B98\u65B9\u767B\u5F55\u2026" });
@@ -20366,7 +21099,7 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20366
21099
  }
20367
21100
  };
20368
21101
  const cancelLogin = async () => {
20369
- if (login?.jobId == null || busy) return;
21102
+ if (login?.jobId == null || busy || keyBusy) return;
20370
21103
  const version2 = ++actionVersion.current;
20371
21104
  setBusy(true);
20372
21105
  try {
@@ -20382,7 +21115,7 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20382
21115
  };
20383
21116
  const saveKey = async (event) => {
20384
21117
  event.preventDefault();
20385
- if (status === void 0 || keyBusy || apiKey.trim() === "") {
21118
+ if (status === void 0 || busy || inFlight || keyBusy || apiKey.trim() === "") {
20386
21119
  if (apiKey.trim() === "") setNotice({ kind: "error", text: "\u8BF7\u8F93\u5165 API Key \u540E\u518D\u4FDD\u5B58\u3002" });
20387
21120
  return;
20388
21121
  }
@@ -20417,7 +21150,7 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20417
21150
  { style: { marginTop: 14 } },
20418
21151
  h(Switch, {
20419
21152
  checked: desktopChecked,
20420
- disabled: !formSnapshot.writable || pendingField === "useDesktopCredential",
21153
+ disabled: formSnapshot.status !== "ready" || formSnapshot.value === void 0 || !formSnapshot.writable || pendingField.includes("useDesktopCredential"),
20421
21154
  label: "\u4F18\u5148\u4F7F\u7528\u684C\u9762\u767B\u5F55\u6001",
20422
21155
  hint: "\u4EC5\u63A7\u5236\u8BFB\u53D6\u4F18\u5148\u7EA7\uFF0C\u4E0D\u4F1A\u4FEE\u6539\u6216\u5199\u56DE\u684C\u9762\u7AEF\u767B\u5F55\u6587\u4EF6\u3002",
20423
21156
  onChange: (checked) => {
@@ -20435,10 +21168,10 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20435
21168
  h(
20436
21169
  "div",
20437
21170
  { className: "odc-actions", style: { marginTop: 14 } },
20438
- h("button", { className: "odc-button odc-button-primary", type: "button", disabled: status === void 0 || busy || inFlight, onClick: () => {
21171
+ h("button", { className: "odc-button odc-button-primary", type: "button", disabled: status === void 0 || busy || keyBusy || inFlight, onClick: () => {
20439
21172
  void startLogin();
20440
21173
  } }, "\u767B\u5F55\u5E76\u4FDD\u5B58\u4F1A\u8BDD"),
20441
- inFlight ? h("button", { className: "odc-button", type: "button", disabled: busy || login?.jobId == null, onClick: () => {
21174
+ inFlight ? h("button", { className: "odc-button", type: "button", disabled: busy || keyBusy || login?.jobId == null, onClick: () => {
20442
21175
  void cancelLogin();
20443
21176
  } }, "\u53D6\u6D88\u767B\u5F55") : null,
20444
21177
  h("a", { className: "odc-link", href: OFFICIAL_SITE, target: "_blank", rel: "noreferrer noopener" }, "\u6253\u5F00 CoRouter \u5B98\u7F51")
@@ -20468,7 +21201,7 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20468
21201
  autoComplete: "new-password",
20469
21202
  autoCapitalize: "off",
20470
21203
  spellCheck: false,
20471
- disabled: status === void 0 || keyBusy,
21204
+ disabled: status === void 0 || busy || inFlight || keyBusy,
20472
21205
  placeholder: "\u4EC5\u7528\u4E8E\u672C\u6B21\u4FDD\u5B58\uFF0C\u4E0D\u4F1A\u56DE\u586B",
20473
21206
  onChange: (event) => setApiKey(event.target.value)
20474
21207
  }),
@@ -20477,85 +21210,12 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20477
21210
  h(
20478
21211
  "div",
20479
21212
  { className: "odc-actions", style: { marginTop: 12 } },
20480
- h("button", { className: "odc-button", type: "submit", disabled: status === void 0 || keyBusy || apiKey.trim() === "" }, keyBusy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58 API Key")
21213
+ h("button", { className: "odc-button", type: "submit", disabled: status === void 0 || busy || inFlight || keyBusy || apiKey.trim() === "" }, keyBusy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58 API Key")
20481
21214
  )
20482
21215
  )
20483
21216
  )
20484
21217
  );
20485
21218
  }
20486
- function Advanced({ status, formSnapshot, setFormField, pendingField }) {
20487
- const [baseURL, setBaseURL] = React.useState(status?.config.baseURL ?? "");
20488
- const [apiKeyEnv, setApiKeyEnv] = React.useState(status?.config.apiKeyEnv ?? "");
20489
- const [notice, setNotice] = React.useState(null);
20490
- const mounted = useMounted();
20491
- React.useEffect(() => {
20492
- setBaseURL(formSnapshot.value?.baseURL ?? status?.config.baseURL ?? "");
20493
- }, [formSnapshot.value?.baseURL, status?.config.baseURL]);
20494
- React.useEffect(() => {
20495
- setApiKeyEnv(formSnapshot.value?.apiKeyEnv ?? status?.config.apiKeyEnv ?? "");
20496
- }, [formSnapshot.value?.apiKeyEnv, status?.config.apiKeyEnv]);
20497
- const saveText = async (field) => {
20498
- const value = field === "baseURL" ? baseURL.trim() : apiKeyEnv.trim();
20499
- if (value === "") {
20500
- setNotice({ kind: "error", text: "\u914D\u7F6E\u503C\u4E0D\u80FD\u4E3A\u7A7A\u3002" });
20501
- return;
20502
- }
20503
- setNotice(null);
20504
- try {
20505
- const accepted = await setFormField(field, value);
20506
- if (mounted.current) setNotice(accepted ? { kind: "status", text: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002" } : { kind: "error", text: "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5\u3002" });
20507
- } catch {
20508
- if (mounted.current) setNotice({ kind: "error", text: GENERIC_ERROR });
20509
- }
20510
- };
20511
- const disabled = !formSnapshot.writable;
20512
- return h(
20513
- React.Fragment,
20514
- null,
20515
- h(
20516
- "section",
20517
- { className: "odc-card" },
20518
- h("h3", null, "\u7A7A\u8F6E\u76D1\u63A7"),
20519
- h("p", { className: "odc-hint" }, "\u72EC\u7ACB\u76D1\u63A7\u5957\u9910\u8D60\u9001\u6A21\u578B\u7684\u7A7A\u8F6E\u6216\u534F\u8BAE\u6B8B\u6E23\uFF1B\u4E0D\u5F71\u54CD\u4ED8\u8D39 Provider \u7684\u5F00\u5173\u3002"),
20520
- h("div", { style: { marginTop: 14 } }, h(Switch, {
20521
- checked: formSnapshot.value?.emptyTurnGuard ?? status?.config.emptyTurnGuard ?? true,
20522
- disabled: disabled || pendingField === "emptyTurnGuard",
20523
- label: "\u542F\u7528\u7A7A\u8F6E\u76D1\u63A7",
20524
- hint: `\u672C\u6B21\u8FD0\u884C\u5DF2\u62E6\u622A ${status?.guardedTurns ?? "\u2014"} \u8F6E\u3002\u68C0\u6D4B\u540E\u7531\u5BBF\u4E3B\u91CD\u8BD5\u7B56\u7565\u5904\u7406\uFF0C\u5BBF\u4E3B\u91CD\u8BD5\u4F1A\u989D\u5916\u6D88\u8017\u989D\u5EA6\u3002`,
20525
- onChange: (checked) => {
20526
- void setFormField("emptyTurnGuard", checked);
20527
- }
20528
- }))
20529
- ),
20530
- h(
20531
- "section",
20532
- { className: "odc-card" },
20533
- h("h3", null, "\u63A8\u7406\u8FDE\u63A5"),
20534
- h("p", { className: "odc-hint" }, formSnapshot.status === "unavailable" ? "\u5BBF\u4E3B\u672A\u5F00\u653E\u6B64\u914D\u7F6E\u8868\u5355\u3002" : "\u5B57\u6BB5\u7531\u5BBF\u4E3B\u914D\u7F6E\u8868\u5355\u6821\u9A8C\u5E76\u6301\u4E45\u5316\uFF1B\u65E0\u9700\u989D\u5916\u7684\u4FDD\u5B58\u63A5\u53E3\u3002"),
20535
- h(
20536
- "div",
20537
- { className: "odc-field" },
20538
- h("label", { htmlFor: "odc-base-url" }, "Base URL"),
20539
- h("input", { id: "odc-base-url", className: "odc-input", type: "url", value: baseURL, disabled, onChange: (event) => setBaseURL(event.target.value) }),
20540
- h("small", { className: "odc-hint" }, "\u4EC5\u7528\u4E8E OpenAI \u517C\u5BB9\u63A8\u7406\uFF0C\u4E0D\u7528\u4E8E\u63A7\u5236\u53F0\u767B\u5F55\u6216\u989D\u5EA6\u67E5\u8BE2\u3002")
20541
- ),
20542
- h("div", { className: "odc-actions", style: { marginTop: 10 } }, h("button", { className: "odc-button", type: "button", disabled: disabled || pendingField === "baseURL", onClick: () => {
20543
- void saveText("baseURL");
20544
- } }, "\u4FDD\u5B58 Base URL")),
20545
- h(
20546
- "div",
20547
- { className: "odc-field" },
20548
- h("label", { htmlFor: "odc-api-key-env" }, "API Key \u73AF\u5883\u53D8\u91CF / \u51ED\u636E\u5F15\u7528\u540D"),
20549
- h("input", { id: "odc-api-key-env", className: "odc-input", type: "text", value: apiKeyEnv, disabled, autoCapitalize: "off", spellCheck: false, onChange: (event) => setApiKeyEnv(event.target.value) }),
20550
- h("small", { className: "odc-hint" }, "\u8FD9\u91CC\u53EA\u4FDD\u5B58\u5F15\u7528\u540D\u79F0\uFF0C\u4E0D\u586B\u5199 Key \u672C\u8EAB\u3002")
20551
- ),
20552
- h("div", { className: "odc-actions", style: { marginTop: 10 } }, h("button", { className: "odc-button", type: "button", disabled: disabled || pendingField === "apiKeyEnv", onClick: () => {
20553
- void saveText("apiKeyEnv");
20554
- } }, "\u4FDD\u5B58\u5F15\u7528\u540D")),
20555
- h(NoticeView, { notice })
20556
- )
20557
- );
20558
- }
20559
21219
  function tabAfter(current, key) {
20560
21220
  const index = TABS.findIndex((tab) => tab.id === current);
20561
21221
  if (key === "Home") return TABS[0].id;
@@ -20571,14 +21231,27 @@ function SettingsPage({ caller, form }) {
20571
21231
  const [error62, setError] = React.useState();
20572
21232
  const [loading, setLoading] = React.useState(true);
20573
21233
  const [refreshing, setRefreshing] = React.useState(false);
20574
- const [pendingField, setPendingField] = React.useState(null);
21234
+ const [refreshingModels, setRefreshingModels] = React.useState(false);
21235
+ const [modelsNotice, setModelsNotice] = React.useState(null);
21236
+ const [pendingField, setPendingField] = React.useState([]);
21237
+ const writeTail = React.useRef(Promise.resolve());
20575
21238
  const [formNotice, setFormNotice] = React.useState(null);
21239
+ const [scheduleTick, setScheduleTick] = React.useState(0);
20576
21240
  const statusVersion = React.useRef(0);
20577
21241
  const quotaVersion = React.useRef(0);
20578
21242
  const loginVersion = React.useRef(0);
21243
+ const modelsVersion = React.useRef(0);
20579
21244
  const tabRefs = React.useRef([]);
20580
21245
  const baseId = React.useId();
20581
- const formSnapshot = useFormSnapshot(form);
21246
+ const acceptedFormSnapshot = useFormSnapshot(form);
21247
+ const [pendingValues, setPendingValues] = React.useState({});
21248
+ const writeVersions = React.useRef(/* @__PURE__ */ new Map());
21249
+ const nextWriteVersion = React.useRef(0);
21250
+ const batchWriteFailed = React.useRef(false);
21251
+ const formSnapshot = React.useMemo(() => ({
21252
+ ...acceptedFormSnapshot,
21253
+ value: acceptedFormSnapshot.value === void 0 ? void 0 : { ...acceptedFormSnapshot.value, ...pendingValues }
21254
+ }), [acceptedFormSnapshot, pendingValues]);
20582
21255
  const loadStatus = React.useCallback(async () => {
20583
21256
  const version2 = ++statusVersion.current;
20584
21257
  try {
@@ -20593,14 +21266,18 @@ function SettingsPage({ caller, form }) {
20593
21266
  }
20594
21267
  }, [caller, mounted]);
20595
21268
  const refreshBusy = React.useRef(false);
20596
- const refresh = React.useCallback(async () => {
20597
- if (refreshBusy.current) return;
21269
+ const forcedRefreshPending = React.useRef(false);
21270
+ const refresh = React.useCallback(async (force = false) => {
21271
+ if (refreshBusy.current) {
21272
+ if (force) forcedRefreshPending.current = true;
21273
+ return;
21274
+ }
20598
21275
  refreshBusy.current = true;
20599
21276
  const version2 = ++quotaVersion.current;
20600
21277
  setRefreshing(true);
20601
21278
  setError(void 0);
20602
21279
  try {
20603
- const quota = await caller("refreshQuota");
21280
+ const quota = await caller(force ? "refreshQuotaNow" : "refreshQuota");
20604
21281
  if (!mounted.current || version2 !== quotaVersion.current) return;
20605
21282
  setStatus((previous) => previous === void 0 ? previous : { ...previous, quota });
20606
21283
  await loadStatus();
@@ -20610,32 +21287,83 @@ function SettingsPage({ caller, form }) {
20610
21287
  if (mounted.current && version2 === quotaVersion.current) {
20611
21288
  refreshBusy.current = false;
20612
21289
  setRefreshing(false);
21290
+ if (forcedRefreshPending.current) {
21291
+ forcedRefreshPending.current = false;
21292
+ void refresh(true);
21293
+ }
20613
21294
  }
20614
21295
  }
20615
21296
  }, [caller, loadStatus, mounted]);
20616
21297
  React.useEffect(() => {
21298
+ void loadStatus();
20617
21299
  void refresh();
20618
21300
  return () => {
20619
21301
  statusVersion.current += 1;
20620
21302
  quotaVersion.current += 1;
20621
21303
  loginVersion.current += 1;
21304
+ modelsVersion.current += 1;
20622
21305
  refreshBusy.current = false;
21306
+ forcedRefreshPending.current = false;
20623
21307
  };
20624
- }, [refresh]);
21308
+ }, [loadStatus, refresh]);
20625
21309
  const setFormField = React.useCallback(async (field, value) => {
20626
- setPendingField(field);
20627
- setFormNotice(null);
21310
+ const snapshot = form.getSnapshot();
21311
+ if (snapshot.status !== "ready" || snapshot.value === void 0 || !snapshot.writable || writeVersions.current.has(field)) return false;
21312
+ if (writeVersions.current.size === 0) batchWriteFailed.current = false;
21313
+ const version2 = ++nextWriteVersion.current;
21314
+ writeVersions.current.set(field, version2);
21315
+ setPendingField([...writeVersions.current.keys()]);
21316
+ setPendingValues((previous) => ({ ...previous, [field]: value }));
21317
+ setFormNotice({ kind: "status", text: "\u6B63\u5728\u4FDD\u5B58\u8BBE\u7F6E\u2026" });
20628
21318
  try {
20629
- const accepted = await form.set(field, value);
20630
- if (mounted.current) setFormNotice(accepted ? { kind: "status", text: "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002" } : { kind: "error", text: "\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002" });
21319
+ const operation = writeTail.current.then(() => mounted.current ? form.set(field, value) : false);
21320
+ writeTail.current = operation.then(() => {
21321
+ }, () => {
21322
+ });
21323
+ const accepted = await operation;
21324
+ if (!accepted) batchWriteFailed.current = true;
21325
+ if (accepted && mounted.current && (field === "useDesktopCredential" || field === "quotaRefreshSeconds")) void refresh(true);
20631
21326
  return accepted;
20632
21327
  } catch {
20633
- if (mounted.current) setFormNotice({ kind: "error", text: GENERIC_ERROR });
21328
+ batchWriteFailed.current = true;
20634
21329
  return false;
20635
21330
  } finally {
20636
- if (mounted.current) setPendingField(null);
21331
+ if (writeVersions.current.get(field) === version2) {
21332
+ writeVersions.current.delete(field);
21333
+ if (mounted.current) {
21334
+ setPendingValues((previous) => {
21335
+ const next = { ...previous };
21336
+ delete next[field];
21337
+ return next;
21338
+ });
21339
+ setPendingField([...writeVersions.current.keys()]);
21340
+ if (writeVersions.current.size === 0) setFormNotice(batchWriteFailed.current ? { kind: "error", text: "\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u5DF2\u6062\u590D\u539F\u503C\uFF0C\u8BF7\u91CD\u8BD5\u3002" } : { kind: "status", text: "\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002" });
21341
+ }
21342
+ }
21343
+ }
21344
+ }, [form, mounted, refresh]);
21345
+ const refreshModelsBusy = React.useRef(false);
21346
+ const refreshModels = React.useCallback(async () => {
21347
+ if (refreshModelsBusy.current) return;
21348
+ refreshModelsBusy.current = true;
21349
+ const version2 = ++modelsVersion.current;
21350
+ setRefreshingModels(true);
21351
+ setModelsNotice(null);
21352
+ try {
21353
+ const next = await caller("refreshModels");
21354
+ if (!mounted.current || version2 !== modelsVersion.current) return;
21355
+ setStatus(next);
21356
+ setError(void 0);
21357
+ setModelsNotice(next.modelsError === void 0 ? { kind: "status", text: `\u5DF2\u4ECE OpenAI \u5237\u65B0\u6A21\u578B\u5217\u8868\uFF0C\u5171 ${next.models.length} \u4E2A\u6A21\u578B\u3002` } : null);
21358
+ } catch {
21359
+ if (mounted.current && version2 === modelsVersion.current) setModelsNotice({ kind: "error", text: "\u6A21\u578B\u5237\u65B0\u672A\u5B8C\u6210\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u3001\u7F51\u5173\u4E0E\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002" });
21360
+ } finally {
21361
+ if (mounted.current && version2 === modelsVersion.current) {
21362
+ setRefreshingModels(false);
21363
+ refreshModelsBusy.current = false;
21364
+ }
20637
21365
  }
20638
- }, [form, mounted]);
21366
+ }, [caller, mounted]);
20639
21367
  const loginState = status?.login.state;
20640
21368
  const loginActive = loginState !== void 0 && loginInFlight(loginState);
20641
21369
  React.useEffect(() => {
@@ -20664,17 +21392,55 @@ function SettingsPage({ caller, form }) {
20664
21392
  loginVersion.current += 1;
20665
21393
  };
20666
21394
  }, [caller, loadStatus, loginActive, mounted, refresh]);
21395
+ const configuredSeconds = formSnapshot.value?.quotaRefreshSeconds ?? status?.config.quotaRefreshSeconds;
21396
+ const refreshIntervalSeconds = configuredSeconds === void 0 ? DEFAULT_QUOTA_REFRESH_SECONDS : normaliseQuotaRefreshSeconds(configuredSeconds);
21397
+ const quotaNextRefreshAt = status?.quota.nextRefreshAt;
21398
+ const schedule = React.useMemo(
21399
+ () => status?.quota.state === "need-login" || status?.quota.autoRefreshPaused ? void 0 : quotaAutoRefresh(refreshIntervalSeconds, quotaNextRefreshAt, Date.now(), Boolean(status?.quota.refreshFailures)),
21400
+ [refreshIntervalSeconds, quotaNextRefreshAt, status?.quota.state, status?.quota.autoRefreshPaused, status?.quota.refreshFailures, scheduleTick]
21401
+ );
21402
+ React.useEffect(() => {
21403
+ if (schedule === void 0) return;
21404
+ const timer = setTimeout(() => {
21405
+ void refresh().finally(() => {
21406
+ if (mounted.current) setScheduleTick((tick) => tick + 1);
21407
+ });
21408
+ }, schedule.delayMs);
21409
+ return () => clearTimeout(timer);
21410
+ }, [schedule, refresh, mounted]);
20667
21411
  const selectTab = (next) => {
20668
21412
  setTab(next);
20669
21413
  if (next === "overview" && tab !== "overview") void refresh();
20670
21414
  };
20671
21415
  let content;
20672
- if (tab === "overview") content = h(Overview, { status, busy: refreshing, onRefresh: () => {
20673
- void refresh();
20674
- } });
20675
- else if (tab === "models") content = h(Models, { models: status?.models });
20676
- else if (tab === "connection") content = h(Connection, { status, formSnapshot, setFormField, pendingField, caller, loadStatus, refreshAfterLogin: refresh });
20677
- else content = h(Advanced, { status, formSnapshot, setFormField, pendingField });
21416
+ if (tab === "overview") {
21417
+ content = h(Overview, {
21418
+ status,
21419
+ busy: refreshing,
21420
+ onRefresh: () => {
21421
+ void refresh(true);
21422
+ },
21423
+ onConnection: () => selectTab("connection"),
21424
+ nextAutoRefreshAt: schedule?.at,
21425
+ refreshIntervalSeconds
21426
+ });
21427
+ } else if (tab === "models") {
21428
+ content = h(Models, {
21429
+ status,
21430
+ formSnapshot,
21431
+ setFormField,
21432
+ pendingField,
21433
+ onRefreshModels: () => {
21434
+ void refreshModels();
21435
+ },
21436
+ refreshingModels,
21437
+ notice: modelsNotice
21438
+ });
21439
+ } else if (tab === "connection") {
21440
+ content = h(Connection, { status, formSnapshot, setFormField, pendingField, caller, loadStatus, refreshAfterLogin: refresh });
21441
+ } else {
21442
+ content = h(Advanced, { status, formSnapshot, setFormField, pendingField });
21443
+ }
20678
21444
  return h(
20679
21445
  "div",
20680
21446
  { className: "odc-settings" },
@@ -20701,6 +21467,8 @@ function SettingsPage({ caller, form }) {
20701
21467
  tabRefs.current[TABS.findIndex((candidate) => candidate.id === next)]?.focus();
20702
21468
  }
20703
21469
  }, item.label))),
21470
+ acceptedFormSnapshot.status !== "ready" || acceptedFormSnapshot.value === void 0 ? h(NoticeView, { notice: { kind: "status", text: acceptedFormSnapshot.status === "unavailable" ? "\u914D\u7F6E\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u68C0\u67E5\u63D2\u4EF6\u662F\u5426\u5DF2\u542F\u7528\u3002" : "\u6B63\u5728\u8F7D\u5165\u914D\u7F6E\uFF0C\u5B8C\u6210\u540E\u5373\u53EF\u4FEE\u6539\u3002" } }) : !acceptedFormSnapshot.writable ? h(NoticeView, { notice: { kind: "status", text: "\u5F53\u524D\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u4FEE\u6539\u3002" } }) : null,
21471
+ h(NoticeView, { notice: formNotice }),
20704
21472
  h("div", {
20705
21473
  className: "odc-panel",
20706
21474
  role: "tabpanel",
@@ -20709,8 +21477,7 @@ function SettingsPage({ caller, form }) {
20709
21477
  "aria-labelledby": `${baseId}-tab-${tab}`
20710
21478
  }, content),
20711
21479
  loading ? h("p", { className: "odc-meta", role: "status" }, "\u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u2026") : null,
20712
- error62 !== void 0 ? h(NoticeView, { notice: { kind: "error", text: error62 } }) : null,
20713
- h(NoticeView, { notice: formNotice })
21480
+ error62 !== void 0 ? h(NoticeView, { notice: { kind: "error", text: error62 } }) : null
20714
21481
  );
20715
21482
  }
20716
21483
  var name = "dsh-opendesign-corouter";