dsh-opendesign-corouter 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,77 @@ 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
+ var MAX_ENTRIES = 512;
19946
+ var MAX_MODEL_ID_LENGTH = 128;
19947
+ var MODEL_ID = /^[A-Za-z0-9._:/-]{1,128}$/u;
19948
+ var MAX_TEMPERATURE = 2;
19949
+ var MAX_TOP_P = 1;
19950
+ var MAX_TOKENS = 1e7;
19951
+ var MAX_CONTEXT_WINDOW = 1e8;
19952
+ var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "ultra", "max"];
19953
+ var RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
19954
+ var SETTINGS_FIELDS = /* @__PURE__ */ new Set(["enabled", "temperature", "topP", "maxTokens", "contextWindow", "reasoningEffort"]);
19955
+ function flag(value) {
19956
+ if (typeof value !== "boolean") throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C");
19957
+ return value;
19958
+ }
19959
+ function bounded(value, max, label) {
19960
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${label} \u5FC5\u987B\u662F 0 \u5230 ${max} \u4E4B\u95F4\u7684\u6709\u9650\u6570\u5B57`);
19961
+ return value;
19962
+ }
19963
+ function capacity(value, max, label) {
19964
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > max) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u7684 ${label} \u5FC5\u987B\u662F 1 \u5230 ${max} \u4E4B\u95F4\u7684\u6574\u6570`);
19965
+ return value;
19966
+ }
19967
+ function effort(value) {
19968
+ const hit = REASONING_EFFORTS.find((candidate) => candidate === value);
19969
+ if (hit === void 0) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u7684 reasoningEffort \u4E0D\u5728\u5141\u8BB8\u7684\u53D6\u503C\u5185");
19970
+ return hit;
19971
+ }
19972
+ function settingsOf(value) {
19973
+ if (!record2(value)) throw new Error("\u6BCF\u4E2A\u6A21\u578B\u7684\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
19974
+ for (const field of Object.keys(value)) {
19975
+ if (!SETTINGS_FIELDS.has(field)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u542B\u6709\u4E0D\u652F\u6301\u7684\u5B57\u6BB5");
19976
+ }
19977
+ const out = {};
19978
+ if (value.enabled !== void 0) out.enabled = flag(value.enabled);
19979
+ if (value.temperature !== void 0) out.temperature = bounded(value.temperature, MAX_TEMPERATURE, "temperature");
19980
+ if (value.topP !== void 0) out.topP = bounded(value.topP, MAX_TOP_P, "topP");
19981
+ if (value.maxTokens !== void 0) out.maxTokens = capacity(value.maxTokens, MAX_TOKENS, "maxTokens");
19982
+ if (value.contextWindow !== void 0) out.contextWindow = capacity(value.contextWindow, MAX_CONTEXT_WINDOW, "contextWindow");
19983
+ if (value.reasoningEffort !== void 0) out.reasoningEffort = effort(value.reasoningEffort);
19984
+ return out;
19985
+ }
19986
+ function parseModelSettingsMap(value) {
19987
+ if (!record2(value)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
19988
+ const keys = Object.keys(value);
19989
+ if (keys.length > MAX_ENTRIES) throw new Error(`\u6A21\u578B\u8BBE\u7F6E\u6700\u591A\u5141\u8BB8 ${MAX_ENTRIES} \u4E2A\u6A21\u578B`);
19990
+ const out = {};
19991
+ for (const id of keys) {
19992
+ if (RESERVED_KEYS.has(id)) throw new Error("\u6A21\u578B\u8BBE\u7F6E\u4E0D\u5141\u8BB8\u4FDD\u7559\u5B57\u6BB5\u540D");
19993
+ if (!MODEL_ID.test(id)) throw new Error(`\u6A21\u578B ID \u5FC5\u987B\u662F 1\u2013${MAX_MODEL_ID_LENGTH} \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u6216 . _ : / -`);
19994
+ out[id] = settingsOf(value[id]);
19995
+ }
19996
+ return out;
19997
+ }
19998
+
19914
19999
  // src/remote.ts
19915
20000
  var MAX_REVISION = 256;
19916
20001
  var MAX_JOB_ID = 128;
@@ -19924,7 +20009,7 @@ var MAX_WINDOWS = 64;
19924
20009
  var MAX_API_KEY = 16384;
19925
20010
  var HTTP_ASCII = /^[\x21-\x7e]+$/u;
19926
20011
  var ENV_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
19927
- var MODEL_ID = /^[A-Za-z0-9._:-]{1,128}$/u;
20012
+ var MODEL_ID2 = /^[A-Za-z0-9._:/-]{1,128}$/u;
19928
20013
  var LOGIN_PHASES = ["idle", "launching", "waiting", "verifying", "success", "cancelled", "error"];
19929
20014
  var QUOTA_STATES = ["ok", "partial", "need-login", "error"];
19930
20015
  var SESSION_SOURCES = ["desktop", "login", "environment"];
@@ -19938,7 +20023,7 @@ function isoTime(value, label) {
19938
20023
  if (!Number.isFinite(Date.parse(time3))) throw new Error(`${label} \u5FC5\u987B\u662F ISO \u65F6\u95F4\u5B57\u7B26\u4E32`);
19939
20024
  return time3;
19940
20025
  }
19941
- function flag(value, label) {
20026
+ function flag2(value, label) {
19942
20027
  if (typeof value !== "boolean") throw new Error(`${label} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);
19943
20028
  return value;
19944
20029
  }
@@ -19950,6 +20035,15 @@ function amount(value, label, negative = false) {
19950
20035
  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
20036
  return value;
19952
20037
  }
20038
+ function positiveInteger(value, label, max) {
20039
+ const result = count(value, label);
20040
+ if (result === 0 || result > max) throw new Error(`${label} \u8D85\u51FA\u8303\u56F4`);
20041
+ return result;
20042
+ }
20043
+ function parseEfforts(value) {
20044
+ if (!Array.isArray(value) || value.length > 16) throw new Error("\u6A21\u578B\u601D\u8003\u6863\u4F4D\u683C\u5F0F\u65E0\u6548");
20045
+ return value.map((item) => text(item, "\u601D\u8003\u6863\u4F4D", 32));
20046
+ }
19953
20047
  function member(value, allowed, label) {
19954
20048
  const hit = allowed.find((candidate) => candidate === value);
19955
20049
  if (hit === void 0) throw new Error(`${label} \u4E0D\u5728\u5141\u8BB8\u7684\u53D6\u503C\u5185`);
@@ -19967,7 +20061,9 @@ function parseQuotaWindow(value) {
19967
20061
  limit,
19968
20062
  ...value.remaining === void 0 ? {} : { remaining: amount(value.remaining, "\u5269\u4F59\u989D\u5EA6") },
19969
20063
  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") }
20064
+ ...value.resetsAt === void 0 ? {} : { resetsAt: isoTime(value.resetsAt, "\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4") },
20065
+ ...value.windowStart === void 0 ? {} : { windowStart: isoTime(value.windowStart, "\u989D\u5EA6\u7A97\u53E3\u5F00\u59CB\u65F6\u95F4") },
20066
+ ...value.resetMode === void 0 ? {} : { resetMode: text(value.resetMode, "\u989D\u5EA6\u5237\u65B0\u65B9\u5F0F", 128) }
19971
20067
  };
19972
20068
  }
19973
20069
  function parseQuotaSnapshot(value) {
@@ -19980,8 +20076,14 @@ function parseQuotaSnapshot(value) {
19980
20076
  state: member(value.state, QUOTA_STATES, "\u989D\u5EA6\u72B6\u6001"),
19981
20077
  ...value.source === void 0 ? {} : { source: member(value.source, SESSION_SOURCES, "\u989D\u5EA6\u6765\u6E90") },
19982
20078
  updatedAt: isoTime(value.updatedAt, "\u989D\u5EA6\u66F4\u65B0\u65F6\u95F4"),
20079
+ ...value.subscriptionPeriodStart === void 0 ? {} : { subscriptionPeriodStart: isoTime(value.subscriptionPeriodStart, "\u8BA2\u9605\u5468\u671F\u5F00\u59CB\u65F6\u95F4") },
20080
+ ...value.subscriptionPeriodEnd === void 0 ? {} : { subscriptionPeriodEnd: isoTime(value.subscriptionPeriodEnd, "\u8BA2\u9605\u5468\u671F\u7ED3\u675F\u65F6\u95F4") },
20081
+ ...value.nextRefreshAt === void 0 ? {} : { nextRefreshAt: isoTime(value.nextRefreshAt, "\u4E0B\u6B21\u81EA\u52A8\u5237\u65B0\u65F6\u95F4") },
20082
+ ...value.stale === void 0 ? {} : { stale: flag2(value.stale, "\u5386\u53F2\u989D\u5EA6\u8BFB\u6570") },
20083
+ ...value.refreshFailures === void 0 ? {} : { refreshFailures: count(value.refreshFailures, "\u8FDE\u7EED\u5237\u65B0\u5931\u8D25\u6B21\u6570") },
20084
+ ...value.autoRefreshPaused === void 0 ? {} : { autoRefreshPaused: flag2(value.autoRefreshPaused, "\u81EA\u52A8\u5237\u65B0\u6682\u505C\u72B6\u6001") },
19983
20085
  ...value.tier === void 0 ? {} : { tier: text(value.tier, "\u5957\u9910\u540D", MAX_TIER) },
19984
- ...value.eligible === void 0 ? {} : { eligible: flag(value.eligible, "\u5957\u9910\u8D44\u683C") },
20086
+ ...value.eligible === void 0 ? {} : { eligible: flag2(value.eligible, "\u5957\u9910\u8D44\u683C") },
19985
20087
  windows: windows.map(parseQuotaWindow),
19986
20088
  balances: {
19987
20089
  ...balances.subscriptionUsd === void 0 ? {} : { subscriptionUsd: amount(balances.subscriptionUsd, "\u8BA2\u9605\u4F59\u989D") },
@@ -20006,11 +20108,15 @@ function parseLoginState(value) {
20006
20108
  function parseStatusModel(value) {
20007
20109
  if (!record2(value)) throw new Error("\u6A21\u578B\u6761\u76EE\u5FC5\u987B\u662F\u5BF9\u8C61");
20008
20110
  const id = text(value.id, "\u6A21\u578B id", 128);
20009
- if (!MODEL_ID.test(id)) throw new Error("\u6A21\u578B id \u542B\u4E0D\u5141\u8BB8\u7684\u5B57\u7B26");
20111
+ if (!MODEL_ID2.test(id)) throw new Error("\u6A21\u578B id \u542B\u4E0D\u5141\u8BB8\u7684\u5B57\u7B26");
20010
20112
  return {
20011
20113
  id,
20012
20114
  name: text(value.name, "\u6A21\u578B\u540D", 128),
20013
- group: member(value.group, MODEL_GROUPS, "\u6A21\u578B\u5206\u7EC4")
20115
+ group: member(value.group, MODEL_GROUPS, "\u6A21\u578B\u5206\u7EC4"),
20116
+ ...value.enabled === void 0 ? {} : { enabled: flag2(value.enabled, "\u6A21\u578B\u542F\u7528\u72B6\u6001") },
20117
+ ...value.contextWindow === void 0 ? {} : { contextWindow: positiveInteger(value.contextWindow, "\u4E0A\u4E0B\u6587\u9884\u7B97", 1e8) },
20118
+ ...value.maxOutputTokens === void 0 ? {} : { maxOutputTokens: positiveInteger(value.maxOutputTokens, "\u6700\u5927\u8F93\u51FA", 1e7) },
20119
+ ...value.reasoningEfforts === void 0 ? {} : { reasoningEfforts: parseEfforts(value.reasoningEfforts) }
20014
20120
  };
20015
20121
  }
20016
20122
  function parseStatusConfig(value) {
@@ -20018,10 +20124,15 @@ function parseStatusConfig(value) {
20018
20124
  const apiKeyEnv = text(value.apiKeyEnv, "\u51ED\u636E\u5F15\u7528\u540D", MAX_ENV_REF);
20019
20125
  if (!ENV_REF.test(apiKeyEnv)) throw new Error("\u51ED\u636E\u5F15\u7528\u540D\u5FC5\u987B\u662F\u5408\u6CD5\u6807\u8BC6\u7B26");
20020
20126
  return {
20021
- emptyTurnGuard: flag(value.emptyTurnGuard, "\u7A7A\u8F6E\u4FDD\u62A4\u5F00\u5173"),
20022
- useDesktopCredential: flag(value.useDesktopCredential, "\u684C\u9762\u767B\u5F55\u6001\u5F00\u5173"),
20127
+ emptyTurnGuard: flag2(value.emptyTurnGuard, "\u7A7A\u8F6E\u4FDD\u62A4\u5F00\u5173"),
20128
+ useDesktopCredential: flag2(value.useDesktopCredential, "\u684C\u9762\u767B\u5F55\u6001\u5F00\u5173"),
20023
20129
  baseURL: text(value.baseURL, "\u63A8\u7406\u5730\u5740", MAX_BASE_URL),
20024
- apiKeyEnv
20130
+ apiKeyEnv,
20131
+ ...value.apiProtocol === void 0 ? {} : { apiProtocol: member(value.apiProtocol, API_PROTOCOL_IDS, "\u63A8\u7406\u534F\u8BAE") },
20132
+ ...value.modelSettings === void 0 ? {} : { modelSettings: parseModelSettingsMap(value.modelSettings) },
20133
+ ...value.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: positiveInteger(value.requestTimeoutMs, "\u8BF7\u6C42\u8D85\u65F6", 36e5) },
20134
+ ...value.streamIdleTimeoutMs === void 0 ? {} : { streamIdleTimeoutMs: positiveInteger(value.streamIdleTimeoutMs, "\u6D41\u7A7A\u95F2\u8D85\u65F6", 18e5) },
20135
+ ...value.quotaRefreshSeconds === void 0 ? {} : { quotaRefreshSeconds: positiveInteger(value.quotaRefreshSeconds, "\u81EA\u52A8\u5237\u65B0\u95F4\u9694", 3600) }
20025
20136
  };
20026
20137
  }
20027
20138
  function parseStatus(value) {
@@ -20036,13 +20147,16 @@ function parseStatus(value) {
20036
20147
  }
20037
20148
  return {
20038
20149
  revision: text(value.revision, "\u914D\u7F6E\u7248\u672C", MAX_REVISION),
20039
- apiKeyConfigured: flag(value.apiKeyConfigured, "\u51ED\u636E\u662F\u5426\u5C31\u7EEA"),
20150
+ apiKeyConfigured: flag2(value.apiKeyConfigured, "\u51ED\u636E\u662F\u5426\u5C31\u7EEA"),
20040
20151
  ...value.sessionSource === void 0 ? {} : { sessionSource: member(value.sessionSource, SESSION_SOURCES, "\u767B\u5F55\u6001\u6765\u6E90") },
20041
20152
  quota: parseQuotaSnapshot(value.quota),
20042
20153
  login: parseLoginState(value.login),
20043
20154
  config: parseStatusConfig(value.config),
20044
20155
  guardedTurns: count(value.guardedTurns, "\u5DF2\u62E6\u622A\u7A7A\u8F6E\u6570"),
20045
- models: parsed
20156
+ models: parsed,
20157
+ ...value.modelsSource === void 0 ? {} : { modelsSource: member(value.modelsSource, ["builtin", "openai"], "\u6A21\u578B\u76EE\u5F55\u6765\u6E90") },
20158
+ ...value.modelsUpdatedAt === void 0 ? {} : { modelsUpdatedAt: isoTime(value.modelsUpdatedAt, "\u6A21\u578B\u66F4\u65B0\u65F6\u95F4") },
20159
+ ...value.modelsError === void 0 ? {} : { modelsError: text(value.modelsError, "\u6A21\u578B\u66F4\u65B0\u63D0\u793A", MAX_MESSAGE) }
20046
20160
  };
20047
20161
  }
20048
20162
  function parseStartLoginRequest(value) {
@@ -20071,7 +20185,9 @@ var CancelLoginRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-coro
20071
20185
  var SaveApiKeyRequestCodec = { mode: "strict", typeSymbol: "dsh-opendesign-corouter#SaveApiKeyRequest", create: () => external_exports.unknown().transform((value) => parseSaveApiKeyRequest(value)) };
20072
20186
  var TYPERT_REMOTE = createTypertRemoteInfo({ package: "dsh-opendesign-corouter", methods: {
20073
20187
  "openDesignCoRouter/status": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: StatusCodec },
20188
+ "openDesignCoRouter/refreshModels": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: StatusCodec },
20074
20189
  "openDesignCoRouter/refreshQuota": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: QuotaSnapshotCodec },
20190
+ "openDesignCoRouter/refreshQuotaNow": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: QuotaSnapshotCodec },
20075
20191
  "openDesignCoRouter/startLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: StartLoginRequestCodec, strictCodec: StartLoginRequestCodec }], result: LoginStateCodec },
20076
20192
  "openDesignCoRouter/loginStatus": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [], result: LoginStateCodec },
20077
20193
  "openDesignCoRouter/cancelLogin": { service: "openDesignCoRouter", invocation: { kind: "direct" }, parameters: [{ name: "input", source: "json", codec: CancelLoginRequestCodec, strictCodec: CancelLoginRequestCodec }], result: LoginStateCodec },
@@ -20089,6 +20205,17 @@ var TABS = [
20089
20205
  { id: "connection", label: "\u8FDE\u63A5\u4E0E\u767B\u5F55" },
20090
20206
  { id: "advanced", label: "\u9AD8\u7EA7" }
20091
20207
  ];
20208
+ var DEFAULT_QUOTA_REFRESH_SECONDS = 60;
20209
+ var MIN_QUOTA_REFRESH_SECONDS = 5;
20210
+ var MAX_QUOTA_REFRESH_SECONDS = 3600;
20211
+ var DEFAULT_REQUEST_TIMEOUT_MS = 18e5;
20212
+ var MIN_REQUEST_TIMEOUT_MS = 1e3;
20213
+ var MAX_REQUEST_TIMEOUT_MS = 36e5;
20214
+ var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
20215
+ var MIN_STREAM_IDLE_TIMEOUT_MS = 1e3;
20216
+ var MAX_STREAM_IDLE_TIMEOUT_MS = 18e5;
20217
+ var MAX_OUTPUT_TOKENS = 1e7;
20218
+ var MAX_CONTEXT_WINDOW2 = 1e8;
20092
20219
  var GENERIC_ERROR = "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
20093
20220
  var LOGIN_LABELS = {
20094
20221
  idle: "\u5C1A\u672A\u5F00\u59CB\u72EC\u7ACB\u767B\u5F55",
@@ -20114,13 +20241,17 @@ function installStyles() {
20114
20241
  .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
20242
  .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
20243
  .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}
20244
+ .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}
20245
+ .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)}
20246
+ .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
20247
  .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}}
20248
+ .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}
20249
+ .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}
20250
+ .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}
20251
+ .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;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}
20252
+ .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}
20253
+ .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))}
20254
+ .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
20255
  `;
20125
20256
  document.head.append(style);
20126
20257
  return () => style.remove();
@@ -20218,19 +20349,65 @@ function formatTime(value) {
20218
20349
  const date5 = new Date(value);
20219
20350
  return Number.isFinite(date5.getTime()) ? date5.toLocaleString("zh-CN", { hour12: false }) : "\u2014";
20220
20351
  }
20352
+ function formatTokens(value) {
20353
+ if (value === void 0 || !Number.isFinite(value)) return "\u2014";
20354
+ return new Intl.NumberFormat("zh-CN").format(value);
20355
+ }
20221
20356
  function windowLabel(seconds) {
20222
20357
  if (seconds % 86400 === 0) return `${seconds / 86400} \u5929\u8D60\u9001\u7A97\u53E3`;
20223
20358
  if (seconds % 3600 === 0) return `${seconds / 3600} \u5C0F\u65F6\u8D60\u9001\u7A97\u53E3`;
20224
20359
  if (seconds % 60 === 0) return `${seconds / 60} \u5206\u949F\u8D60\u9001\u7A97\u53E3`;
20225
20360
  return `${seconds} \u79D2\u8D60\u9001\u7A97\u53E3`;
20226
20361
  }
20227
- function QuotaView({ quota }) {
20362
+ function nextWindowResetAt(windows, now) {
20363
+ let earliest;
20364
+ let earliestMs = Number.POSITIVE_INFINITY;
20365
+ for (const window of windows) {
20366
+ if (window.resetsAt === void 0) continue;
20367
+ const ms = Date.parse(window.resetsAt);
20368
+ if (!Number.isFinite(ms) || ms <= now || ms >= earliestMs) continue;
20369
+ earliest = window.resetsAt;
20370
+ earliestMs = ms;
20371
+ }
20372
+ return earliest;
20373
+ }
20374
+ var SOURCE_LABELS = {
20375
+ desktop: "\u684C\u9762\u767B\u5F55\u6001",
20376
+ login: "\u72EC\u7ACB\u767B\u5F55",
20377
+ environment: "\u73AF\u5883\u51ED\u636E"
20378
+ };
20379
+ var RESET_MODE_LABELS = {
20380
+ activity_triggered: "\u6309\u4F7F\u7528\u89E6\u53D1",
20381
+ anchored_recurring: "\u56FA\u5B9A\u5468\u671F"
20382
+ };
20383
+ function QuotaView({ quota, nextAutoRefreshAt, refreshIntervalSeconds }) {
20228
20384
  if (quota === void 0) return h("p", { className: "odc-hint", role: "status" }, "\u6B63\u5728\u8BFB\u53D6\u989D\u5EA6\u2026");
20229
20385
  const metrics = [
20230
20386
  { label: "\u5957\u9910\u4F59\u989D", value: formatUsd(quota.balances.subscriptionUsd), hint: "\u8BA2\u9605\u5269\u4F59\u53EF\u7528\u91D1\u989D" },
20231
20387
  { label: "\u5145\u503C / \u8D60\u9001\u91D1\u989D", value: formatUsd(quota.balances.rechargeUsd), hint: "\u5145\u503C\u4E0E\u8D60\u9001\u7684\u53EF\u7528\u91D1\u989D" },
20232
20388
  { label: "\u603B\u989D\u5EA6", value: formatUsd(quota.balances.totalUsd), hint: "\u94B1\u5305\u5F53\u524D\u53EF\u7528\u603B\u989D" }
20233
20389
  ];
20390
+ const timing = [
20391
+ {
20392
+ label: "\u8BA2\u9605\u8D26\u671F\u7ED3\u675F",
20393
+ value: formatTime(quota.subscriptionPeriodEnd),
20394
+ hint: `\u8D26\u671F\u5F00\u59CB\uFF1A${formatTime(quota.subscriptionPeriodStart)} \xB7 \u672A\u77E5\u65F6\u95F4\u4E0D\u63A8\u65AD`
20395
+ },
20396
+ {
20397
+ label: "\u4E0B\u6B21\u989D\u5EA6\u6062\u590D",
20398
+ value: formatTime(nextWindowResetAt(quota.windows, Date.now())),
20399
+ hint: "\u53EA\u53D6\u7A97\u53E3 resetsAt \u4E2D\u6700\u65E9\u7684\u672A\u5230\u671F\u65F6\u95F4\uFF1B\u65E0\u7A97\u53E3\u6216\u672A\u77E5\u65F6\u4FDD\u6301 \u2014"
20400
+ },
20401
+ {
20402
+ label: "\u4E0B\u6B21\u81EA\u52A8\u5237\u65B0",
20403
+ value: formatTime(quota.state === "need-login" || quota.autoRefreshPaused ? void 0 : nextAutoRefreshAt),
20404
+ 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`
20405
+ }
20406
+ ];
20407
+ const tierLine = [
20408
+ quota.tier === void 0 ? void 0 : `\u5F53\u524D\u5957\u9910\uFF1A${quota.tier}`,
20409
+ quota.eligible === void 0 ? void 0 : `\u5957\u9910\u8D44\u683C\uFF1A${quota.eligible ? "\u5DF2\u5F00\u901A" : "\u672A\u5F00\u901A"}`
20410
+ ].filter((value) => value !== void 0).join(" \xB7 ");
20234
20411
  return h(
20235
20412
  React.Fragment,
20236
20413
  null,
@@ -20240,10 +20417,25 @@ function QuotaView({ quota }) {
20240
20417
  h(
20241
20418
  "div",
20242
20419
  { 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])
20420
+ h(
20421
+ "div",
20422
+ null,
20423
+ h("h3", null, "\u5957\u9910\u8D60\u9001\u7A97\u53E3"),
20424
+ tierLine === "" ? null : h("p", { className: "odc-hint" }, tierLine)
20425
+ ),
20426
+ h(
20427
+ "div",
20428
+ { className: "odc-actions" },
20429
+ h("span", { className: "odc-label" }, QUOTA_LABELS[quota.state]),
20430
+ quota.stale ? h("span", { className: "odc-chip" }, "\u4E0A\u6B21\u6210\u529F\u8BFB\u6570") : null
20431
+ )
20245
20432
  ),
20246
- quota.windows.length === 0 ? h("p", { className: "odc-metric-value" }, "\u2014") : quota.windows.map((window, index) => h(
20433
+ 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(
20434
+ React.Fragment,
20435
+ null,
20436
+ h("p", { className: "odc-metric-value" }, "\u2014"),
20437
+ h("p", { className: "odc-hint" }, "\u989D\u5EA6\u7A97\u53E3\u6682\u4E0D\u53EF\u786E\u8BA4\uFF1B\u4E0D\u6309 0 \u586B\u5145\u3002")
20438
+ ) : quota.windows.map((window, index) => h(
20247
20439
  "div",
20248
20440
  { className: "odc-window", key: `${window.durationSeconds}-${index}` },
20249
20441
  h(
@@ -20253,7 +20445,7 @@ function QuotaView({ quota }) {
20253
20445
  h("span", { className: "odc-label" }, `\u5DF2\u7528 ${window.percent.toFixed(1)}% \xB7 \u5269\u4F59 ${window.remaining ?? "\u2014"}`)
20254
20446
  ),
20255
20447
  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)}`)
20448
+ 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
20449
  ))
20258
20450
  ),
20259
20451
  h("div", { className: "odc-quota-grid" }, metrics.map((metric) => h(
@@ -20263,11 +20455,77 @@ function QuotaView({ quota }) {
20263
20455
  h("strong", { className: "odc-metric-value" }, metric.value),
20264
20456
  h("span", { className: "odc-hint" }, metric.hint)
20265
20457
  ))),
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]}` : ""}`),
20458
+ h("div", { className: "odc-quota-grid" }, timing.map((metric) => h(
20459
+ "div",
20460
+ { className: "odc-card odc-metric", key: metric.label },
20461
+ h("span", { className: "odc-label" }, metric.label),
20462
+ h("strong", { className: "odc-metric-value odc-metric-text" }, metric.value),
20463
+ h("span", { className: "odc-hint" }, metric.hint)
20464
+ ))),
20465
+ 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` : ""}`),
20466
+ h("p", { className: "odc-hint" }, "\u989D\u5EA6\u6570\u636E\u53EA\u8BFB\u5C55\u793A\uFF0C\u4E0D\u5199\u5165\u8D26\u53F7\u6216\u684C\u9762\u51ED\u636E\uFF1B\u684C\u9762\u767B\u5F55\u6001\u4E0D\u53EF\u7528\u65F6\u4F1A\u81EA\u52A8\u56DE\u9000\u5230\u5DF2\u4FDD\u5B58\u4F1A\u8BDD\u3002"),
20267
20467
  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
20468
  );
20269
20469
  }
20270
- function Overview({ status, busy, onRefresh }) {
20470
+ function writeModelSettings(map2, id, next) {
20471
+ const out = { ...map2 ?? {} };
20472
+ if (next === void 0) {
20473
+ delete out[id];
20474
+ return out;
20475
+ }
20476
+ const entry = {};
20477
+ if (next.enabled !== void 0) entry.enabled = next.enabled;
20478
+ if (next.temperature !== void 0) entry.temperature = next.temperature;
20479
+ if (next.topP !== void 0) entry.topP = next.topP;
20480
+ if (next.maxTokens !== void 0) entry.maxTokens = next.maxTokens;
20481
+ if (next.contextWindow !== void 0) entry.contextWindow = next.contextWindow;
20482
+ if (next.reasoningEffort !== void 0) entry.reasoningEffort = next.reasoningEffort;
20483
+ if (Object.keys(entry).length === 0) delete out[id];
20484
+ else out[id] = entry;
20485
+ return out;
20486
+ }
20487
+ function modelSettingsEntry(map2, id) {
20488
+ return { ...map2?.[id] ?? {} };
20489
+ }
20490
+ function modelEnabled(model, settings, authoritative = true) {
20491
+ if (settings?.enabled !== void 0) return settings.enabled;
20492
+ if (authoritative) return true;
20493
+ return model.enabled !== false;
20494
+ }
20495
+ var DECIMAL_TEXT = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/u;
20496
+ function parseNumberInput(raw, spec) {
20497
+ const text2 = raw.trim();
20498
+ if (text2 === "") {
20499
+ return spec.allowEmpty === true ? { ok: true, value: void 0 } : { ok: false, message: `${spec.label}\u4E0D\u80FD\u4E3A\u7A7A\u3002` };
20500
+ }
20501
+ const integral = spec.integer === true;
20502
+ const value = DECIMAL_TEXT.test(text2) ? Number(text2) : Number.NaN;
20503
+ if (!DECIMAL_TEXT.test(text2) || !Number.isFinite(value) || value < spec.min || value > spec.max || integral && !Number.isSafeInteger(value)) {
20504
+ return {
20505
+ ok: false,
20506
+ 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"}`
20507
+ };
20508
+ }
20509
+ return { ok: true, value };
20510
+ }
20511
+ function protocolEndpoint(baseURL, protocol) {
20512
+ return inferenceEndpoint(baseURL, protocol);
20513
+ }
20514
+ function normaliseQuotaRefreshSeconds(value) {
20515
+ if (value === void 0 || !Number.isSafeInteger(value) || value < MIN_QUOTA_REFRESH_SECONDS || value > MAX_QUOTA_REFRESH_SECONDS) return void 0;
20516
+ return value;
20517
+ }
20518
+ function quotaAutoRefresh(secondsRaw, nextRefreshAt, now, backingOff = false) {
20519
+ const intervalSeconds = normaliseQuotaRefreshSeconds(secondsRaw);
20520
+ if (intervalSeconds === void 0) return void 0;
20521
+ const intervalMs = intervalSeconds * 1e3;
20522
+ const planMs = nextRefreshAt === void 0 ? Number.NaN : Date.parse(nextRefreshAt);
20523
+ const planDelay = Number.isFinite(planMs) ? planMs - now : Number.POSITIVE_INFINITY;
20524
+ const plannedDelay = Math.max(1e3, Math.floor(planDelay) + 500);
20525
+ const delayMs = Number.isFinite(planDelay) && planDelay > 0 ? backingOff ? plannedDelay : Math.min(intervalMs, plannedDelay) : intervalMs;
20526
+ return { delayMs, at: new Date(now + delayMs).toISOString(), intervalSeconds };
20527
+ }
20528
+ function Overview({ status, busy, onRefresh, nextAutoRefreshAt, refreshIntervalSeconds }) {
20271
20529
  return h(
20272
20530
  React.Fragment,
20273
20531
  null,
@@ -20277,14 +20535,211 @@ function Overview({ status, busy, onRefresh }) {
20277
20535
  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
20536
  h("button", { className: "odc-button", type: "button", disabled: busy, onClick: onRefresh }, busy ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0\u989D\u5EA6")
20279
20537
  ),
20280
- h(QuotaView, { quota: status?.quota })
20538
+ h(QuotaView, { quota: status?.quota, nextAutoRefreshAt, refreshIntervalSeconds })
20539
+ );
20540
+ }
20541
+ var NUMERIC_FIELDS = {
20542
+ temperature: { label: "\u6E29\u5EA6", hint: "0\u20132\uFF1B\u7559\u7A7A\u7EE7\u627F\u9ED8\u8BA4\u3002", min: 0, max: 2 },
20543
+ topP: { label: "Top P", hint: "0\u20131\uFF1B\u7559\u7A7A\u7EE7\u627F\u9ED8\u8BA4\u3002", min: 0, max: 1 },
20544
+ 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 },
20545
+ 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_WINDOW2, integer: true }
20546
+ };
20547
+ var NUMERIC_ORDER = ["temperature", "topP", "maxTokens", "contextWindow"];
20548
+ function OptionalNumberField({ id, field, value, disabled, resetToken, onSave }) {
20549
+ const spec = NUMERIC_FIELDS[field];
20550
+ const mounted = useMounted();
20551
+ const [draft, setDraft] = React.useState(void 0);
20552
+ const [error62, setError] = React.useState(void 0);
20553
+ React.useEffect(() => {
20554
+ setDraft(void 0);
20555
+ setError(void 0);
20556
+ }, [value, resetToken]);
20557
+ const commit = async () => {
20558
+ if (disabled || draft === void 0) return;
20559
+ const result = parseNumberInput(draft, {
20560
+ label: spec.label,
20561
+ min: spec.min,
20562
+ max: spec.max,
20563
+ ...spec.integer === true ? { integer: true } : {},
20564
+ allowEmpty: true
20565
+ });
20566
+ if (!result.ok) {
20567
+ setError(result.message);
20568
+ return;
20569
+ }
20570
+ if (result.value === value) {
20571
+ setDraft(void 0);
20572
+ setError(void 0);
20573
+ return;
20574
+ }
20575
+ const saved = await onSave(result.value);
20576
+ if (!mounted.current) return;
20577
+ if (saved) {
20578
+ setDraft(void 0);
20579
+ setError(void 0);
20580
+ } else setError("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20581
+ };
20582
+ return h(
20583
+ "div",
20584
+ { className: "odc-field odc-field-compact" },
20585
+ h("label", { htmlFor: id }, spec.label),
20586
+ h("input", {
20587
+ id,
20588
+ className: "odc-input",
20589
+ type: "number",
20590
+ inputMode: "decimal",
20591
+ min: spec.min,
20592
+ max: spec.max,
20593
+ step: spec.integer === true ? 1 : 0.1,
20594
+ value: draft ?? (value === void 0 ? "" : String(value)),
20595
+ disabled,
20596
+ "aria-invalid": error62 !== void 0,
20597
+ "aria-describedby": `${id}-hint`,
20598
+ onChange: (event) => setDraft(event.target.value),
20599
+ onBlur: () => {
20600
+ void commit();
20601
+ }
20602
+ }),
20603
+ h("small", {
20604
+ className: error62 === void 0 ? "odc-hint" : "odc-hint odc-field-error",
20605
+ id: `${id}-hint`,
20606
+ ...error62 === void 0 ? {} : { role: "alert" }
20607
+ }, error62 ?? spec.hint)
20608
+ );
20609
+ }
20610
+ function EffortField({ id, model, value, disabled, onChange }) {
20611
+ const mounted = useMounted();
20612
+ const [error62, setError] = React.useState(void 0);
20613
+ const listed = model.reasoningEfforts ?? [];
20614
+ const options = value === void 0 || listed.includes(value) ? listed : [...listed, value];
20615
+ const unknown2 = options.length === 0;
20616
+ return h(
20617
+ "div",
20618
+ { className: "odc-field odc-field-compact" },
20619
+ h("label", { htmlFor: id }, "\u9ED8\u8BA4\u601D\u8003\u6DF1\u5EA6"),
20620
+ h("select", {
20621
+ id,
20622
+ className: "odc-input",
20623
+ value: value ?? "",
20624
+ disabled: disabled || unknown2,
20625
+ "aria-describedby": `${id}-hint`,
20626
+ onChange: (event) => {
20627
+ const next = event.target.value;
20628
+ void (async () => {
20629
+ const saved = await onChange(next);
20630
+ if (mounted.current && !saved) setError("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20631
+ })();
20632
+ }
20633
+ }, [
20634
+ h("option", { key: "", value: "" }, "\u7EE7\u627F\u9ED8\u8BA4"),
20635
+ ...options.map((effort2) => h("option", { key: effort2, value: effort2 }, effort2))
20636
+ ]),
20637
+ h("small", {
20638
+ className: error62 === void 0 ? "odc-hint" : "odc-hint odc-field-error",
20639
+ id: `${id}-hint`,
20640
+ ...error62 === void 0 ? {} : { role: "alert" }
20641
+ }, 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"))
20642
+ );
20643
+ }
20644
+ function ModelRow({ model, settings, authoritative, writable, pending, onCommit }) {
20645
+ const id = React.useId();
20646
+ const mounted = useMounted();
20647
+ const [error62, setError] = React.useState(void 0);
20648
+ const [resetToken, setResetToken] = React.useState(0);
20649
+ const enabled = modelEnabled(model, settings, authoritative);
20650
+ const hasOverrides = Object.keys(settings).length > 0;
20651
+ const commit = async (next) => {
20652
+ const saved = await onCommit(next);
20653
+ if (mounted.current && !saved) setError("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20654
+ return saved;
20655
+ };
20656
+ const saveField = async (key, value) => {
20657
+ const next = { ...settings };
20658
+ if (value === void 0) delete next[key];
20659
+ else next[key] = value;
20660
+ setError(void 0);
20661
+ return commit(next);
20662
+ };
20663
+ const reset = async () => {
20664
+ setError(void 0);
20665
+ setResetToken((token) => token + 1);
20666
+ await commit(void 0);
20667
+ };
20668
+ const facts = [
20669
+ `\u4E0A\u4E0B\u6587 ${formatTokens(model.contextWindow)}`,
20670
+ `\u6700\u5927\u8F93\u51FA ${formatTokens(model.maxOutputTokens)}`,
20671
+ ...model.reasoningEfforts === void 0 || model.reasoningEfforts.length === 0 ? [] : [`\u601D\u8003\u6863\u4F4D ${model.reasoningEfforts.join(" / ")}`]
20672
+ ].join(" \xB7 ");
20673
+ return h(
20674
+ "li",
20675
+ { className: "odc-model-item" },
20676
+ h(
20677
+ "div",
20678
+ { className: "odc-model-head" },
20679
+ h(
20680
+ "label",
20681
+ { className: "odc-switch odc-model-switch" },
20682
+ h("input", {
20683
+ type: "checkbox",
20684
+ checked: enabled,
20685
+ disabled: !writable || pending,
20686
+ onChange: (event) => {
20687
+ void commit({ ...settings, enabled: event.target.checked });
20688
+ }
20689
+ }),
20690
+ h("span", null, h("strong", null, model.name), h("code", null, model.id))
20691
+ ),
20692
+ h(
20693
+ "div",
20694
+ { className: "odc-actions" },
20695
+ enabled ? null : h("span", { className: "odc-chip" }, "\u5DF2\u7981\u7528"),
20696
+ h("button", { className: "odc-button odc-button-small", type: "button", disabled: !writable || pending || !hasOverrides, onClick: () => {
20697
+ void reset();
20698
+ } }, "\u91CD\u7F6E")
20699
+ )
20700
+ ),
20701
+ h("p", { className: "odc-model-meta" }, facts),
20702
+ h(
20703
+ "details",
20704
+ { className: "odc-model-details" },
20705
+ h("summary", { "aria-label": `${model.name} \u53C2\u6570\u8BBE\u7F6E` }, "\u53C2\u6570\u8BBE\u7F6E"),
20706
+ h(
20707
+ "div",
20708
+ { className: "odc-model-editor" },
20709
+ NUMERIC_ORDER.map((key) => h(OptionalNumberField, {
20710
+ key,
20711
+ id: `${id}-${key}`,
20712
+ field: key,
20713
+ value: settings[key],
20714
+ disabled: !writable || pending,
20715
+ resetToken,
20716
+ onSave: (value) => saveField(key, value)
20717
+ })),
20718
+ h(EffortField, {
20719
+ id: `${id}-effort`,
20720
+ model,
20721
+ value: settings.reasoningEffort,
20722
+ disabled: !writable || pending,
20723
+ onChange: async (value) => {
20724
+ const next = { ...settings };
20725
+ if (value === "") delete next.reasoningEffort;
20726
+ else next.reasoningEffort = value;
20727
+ setError(void 0);
20728
+ return commit(next);
20729
+ }
20730
+ })
20731
+ )
20732
+ ),
20733
+ error62 !== void 0 ? h("p", { className: "odc-hint odc-field-error", role: "alert" }, error62) : null
20281
20734
  );
20282
20735
  }
20283
- function ModelGroup({ title, group, models }) {
20736
+ function ModelGroup({ title, group, models, settingsMap, authoritative, writable, pending, onCommit }) {
20284
20737
  const [query, setQuery] = React.useState("");
20285
20738
  const id = React.useId();
20286
20739
  const normalized = query.trim().toLocaleLowerCase();
20287
- const filtered = models.filter((model) => model.group === group && (normalized === "" || `${model.name} ${model.id}`.toLocaleLowerCase().includes(normalized)));
20740
+ const groupModels = models.filter((model) => model.group === group);
20741
+ const filtered = groupModels.filter((model) => normalized === "" || `${model.name} ${model.id}`.toLocaleLowerCase().includes(normalized));
20742
+ const enabledCount = groupModels.filter((model) => modelEnabled(model, settingsMap[model.id], authoritative)).length;
20288
20743
  return h(
20289
20744
  "section",
20290
20745
  { className: "odc-card", "aria-labelledby": `${id}-title` },
@@ -20292,7 +20747,7 @@ function ModelGroup({ title, group, models }) {
20292
20747
  "div",
20293
20748
  { className: "odc-card-head" },
20294
20749
  h("h3", { id: `${id}-title` }, title),
20295
- h("span", { className: "odc-model-count" }, `${filtered.length} / ${models.filter((model) => model.group === group).length}`)
20750
+ h("span", { className: "odc-model-count" }, `${enabledCount} / ${groupModels.length} \u5DF2\u542F\u7528`)
20296
20751
  ),
20297
20752
  h("label", { className: "odc-label", htmlFor: `${id}-search` }, `\u641C\u7D22${title}`),
20298
20753
  h("input", {
@@ -20303,25 +20758,280 @@ function ModelGroup({ title, group, models }) {
20303
20758
  placeholder: "\u8F93\u5165\u6A21\u578B\u540D\u79F0\u6216 ID",
20304
20759
  onChange: (event) => setQuery(event.target.value)
20305
20760
  }),
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
- )))
20761
+ 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(ModelRow, {
20762
+ key: model.id,
20763
+ model,
20764
+ settings: modelSettingsEntry(settingsMap, model.id),
20765
+ authoritative,
20766
+ writable,
20767
+ pending,
20768
+ onCommit: (next) => onCommit(model.id, next)
20769
+ })))
20313
20770
  );
20314
20771
  }
20315
- function Models({ models }) {
20772
+ function Models({ status, formSnapshot, setFormField, pendingField, onRefreshModels, refreshingModels, notice }) {
20773
+ const formReady = formSnapshot.status === "ready" && formSnapshot.value !== void 0;
20774
+ const settingsMap = formReady ? formSnapshot.value?.modelSettings ?? {} : status?.config.modelSettings ?? {};
20775
+ const authoritative = formReady || status?.config.modelSettings !== void 0;
20776
+ const writable = formSnapshot.writable;
20777
+ const pending = pendingField === "modelSettings";
20778
+ const latestSettings = React.useRef(settingsMap);
20779
+ React.useEffect(() => {
20780
+ latestSettings.current = settingsMap;
20781
+ }, [settingsMap]);
20782
+ const saving = React.useRef(false);
20783
+ const commit = async (id, next) => {
20784
+ if (!writable || saving.current) return false;
20785
+ saving.current = true;
20786
+ try {
20787
+ return await setFormField("modelSettings", writeModelSettings(latestSettings.current, id, next));
20788
+ } finally {
20789
+ saving.current = false;
20790
+ }
20791
+ };
20792
+ const source = status?.modelsSource === "openai" ? "OpenAI /models \u53D1\u73B0" : status?.modelsSource === "builtin" ? "\u5185\u7F6E\u76EE\u5F55" : "\u5C1A\u672A\u786E\u8BA4";
20793
+ const updated = status?.modelsUpdatedAt === void 0 ? "" : ` \xB7 \u66F4\u65B0\u4E8E ${formatTime(status.modelsUpdatedAt)}`;
20794
+ const models = status?.models ?? [];
20316
20795
  return h(
20317
20796
  React.Fragment,
20318
20797
  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
20798
  h(
20799
+ "div",
20800
+ { className: "odc-card-head" },
20801
+ h(
20802
+ "div",
20803
+ null,
20804
+ h("h3", null, "\u53EF\u7528\u6A21\u578B"),
20805
+ 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")
20806
+ ),
20807
+ h(
20808
+ "div",
20809
+ { className: "odc-actions" },
20810
+ h("span", { className: "odc-label", role: "status" }, `\u6765\u6E90\uFF1A${source}${updated}`),
20811
+ h("button", {
20812
+ className: "odc-button",
20813
+ type: "button",
20814
+ disabled: refreshingModels || status === void 0 || status.apiKeyConfigured === false,
20815
+ onClick: onRefreshModels
20816
+ }, refreshingModels ? "\u6B63\u5728\u5237\u65B0\u6A21\u578B\u2026" : "\u4ECE OpenAI \u5237\u65B0\u6A21\u578B")
20817
+ )
20818
+ ),
20819
+ 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,
20820
+ status?.modelsError !== void 0 ? h("p", { className: "odc-notice", "data-kind": "error", role: "alert" }, status.modelsError) : null,
20821
+ h(NoticeView, { notice }),
20822
+ 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
20823
  "div",
20322
20824
  { 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 ?? [] })
20825
+ h(ModelGroup, { title: "\u5957\u9910\u8D60\u9001 Provider", group: "plan", models, settingsMap, authoritative, writable, pending, onCommit: commit }),
20826
+ h(ModelGroup, { title: "\u4ED8\u8D39 Provider", group: "paid", models, settingsMap, authoritative, writable, pending, onCommit: commit })
20827
+ )
20828
+ );
20829
+ }
20830
+ function NumberField({ id, label, hint, value, min, max, integer: integer2, disabled, onCommit }) {
20831
+ const mounted = useMounted();
20832
+ const [draft, setDraft] = React.useState(String(value));
20833
+ const [error62, setError] = React.useState(void 0);
20834
+ React.useEffect(() => {
20835
+ setDraft(String(value));
20836
+ setError(void 0);
20837
+ }, [value]);
20838
+ const commit = async () => {
20839
+ const result = parseNumberInput(draft, { label, min, max, ...integer2 ? { integer: true } : {} });
20840
+ if (!result.ok) {
20841
+ setError(result.message);
20842
+ return;
20843
+ }
20844
+ if (result.value === void 0 || result.value === value) {
20845
+ setDraft(String(value));
20846
+ setError(void 0);
20847
+ return;
20848
+ }
20849
+ const saved = await onCommit(result.value);
20850
+ if (!mounted.current) return;
20851
+ if (saved) setError(void 0);
20852
+ else setError("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5\u3002");
20853
+ };
20854
+ return h(
20855
+ "div",
20856
+ { className: "odc-field" },
20857
+ h("label", { htmlFor: id }, label),
20858
+ h("input", {
20859
+ id,
20860
+ className: "odc-input",
20861
+ type: "number",
20862
+ inputMode: "numeric",
20863
+ min,
20864
+ max,
20865
+ step: integer2 ? 1 : 0.1,
20866
+ value: draft,
20867
+ disabled,
20868
+ "aria-invalid": error62 !== void 0,
20869
+ "aria-describedby": `${id}-hint`,
20870
+ onChange: (event) => setDraft(event.target.value),
20871
+ onBlur: () => {
20872
+ void commit();
20873
+ }
20874
+ }),
20875
+ h("small", {
20876
+ className: error62 === void 0 ? "odc-hint" : "odc-hint odc-field-error",
20877
+ id: `${id}-hint`,
20878
+ ...error62 === void 0 ? {} : { role: "alert" }
20879
+ }, error62 ?? hint)
20880
+ );
20881
+ }
20882
+ function Advanced({ status, formSnapshot, setFormField, pendingField }) {
20883
+ const [apiKeyEnv, setApiKeyEnv] = React.useState(status?.config.apiKeyEnv ?? "");
20884
+ const [keyNotice, setKeyNotice] = React.useState(null);
20885
+ const mounted = useMounted();
20886
+ React.useEffect(() => {
20887
+ setApiKeyEnv(formSnapshot.value?.apiKeyEnv ?? status?.config.apiKeyEnv ?? "");
20888
+ }, [formSnapshot.value?.apiKeyEnv, status?.config.apiKeyEnv]);
20889
+ const writable = formSnapshot.writable;
20890
+ const configuredProtocol = formSnapshot.value?.apiProtocol ?? status?.config.apiProtocol;
20891
+ const protocol = configuredProtocol !== void 0 && API_PROTOCOL_OPTIONS.some((option) => option.id === configuredProtocol) ? configuredProtocol : "chat-completions";
20892
+ const baseURL = formSnapshot.value?.baseURL ?? status?.config.baseURL ?? "";
20893
+ const requestTimeout = formSnapshot.value?.requestTimeoutMs ?? status?.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
20894
+ const streamIdleTimeout = formSnapshot.value?.streamIdleTimeoutMs ?? status?.config.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
20895
+ const refreshSeconds = formSnapshot.value?.quotaRefreshSeconds ?? status?.config.quotaRefreshSeconds ?? DEFAULT_QUOTA_REFRESH_SECONDS;
20896
+ const selected = API_PROTOCOL_OPTIONS.find((option) => option.id === protocol);
20897
+ const saveKeyRef = async () => {
20898
+ const value = apiKeyEnv.trim();
20899
+ if (value === "") {
20900
+ setKeyNotice({ kind: "error", text: "\u914D\u7F6E\u503C\u4E0D\u80FD\u4E3A\u7A7A\u3002" });
20901
+ return;
20902
+ }
20903
+ setKeyNotice(null);
20904
+ try {
20905
+ const accepted = await setFormField("apiKeyEnv", value);
20906
+ 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" });
20907
+ } catch {
20908
+ if (mounted.current) setKeyNotice({ kind: "error", text: GENERIC_ERROR });
20909
+ }
20910
+ };
20911
+ return h(
20912
+ React.Fragment,
20913
+ null,
20914
+ h(
20915
+ "section",
20916
+ { className: "odc-card" },
20917
+ h("h3", null, "\u7A7A\u8F6E\u76D1\u63A7"),
20918
+ 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"),
20919
+ h("div", { style: { marginTop: 14 } }, h(Switch, {
20920
+ checked: formSnapshot.value?.emptyTurnGuard ?? status?.config.emptyTurnGuard ?? true,
20921
+ disabled: !writable || pendingField === "emptyTurnGuard",
20922
+ label: "\u542F\u7528\u7A7A\u8F6E\u76D1\u63A7",
20923
+ 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`,
20924
+ onChange: (checked) => {
20925
+ void setFormField("emptyTurnGuard", checked);
20926
+ }
20927
+ }))
20928
+ ),
20929
+ h(
20930
+ "section",
20931
+ { className: "odc-card" },
20932
+ h("h3", null, "\u63A8\u7406\u534F\u8BAE"),
20933
+ 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"),
20934
+ h(
20935
+ "div",
20936
+ { className: "odc-field" },
20937
+ h("label", { htmlFor: "odc-api-protocol" }, "API \u534F\u8BAE"),
20938
+ h("select", {
20939
+ id: "odc-api-protocol",
20940
+ className: "odc-input",
20941
+ value: protocol,
20942
+ disabled: !writable || pendingField === "apiProtocol",
20943
+ onChange: (event) => {
20944
+ void setFormField("apiProtocol", event.target.value);
20945
+ }
20946
+ }, API_PROTOCOL_OPTIONS.map((option) => h("option", { key: option.id, value: option.id }, option.name))),
20947
+ h("small", { className: "odc-hint" }, selected?.description ?? "\u9009\u62E9\u4E0E\u7F51\u5173\u517C\u5BB9\u7684\u63A8\u7406\u534F\u8BAE\u3002")
20948
+ ),
20949
+ h(
20950
+ "div",
20951
+ { className: "odc-readonly" },
20952
+ h(
20953
+ "div",
20954
+ null,
20955
+ h("span", { className: "odc-label" }, "\u5F53\u524D\u7F51\u5173\u5730\u5740\uFF08\u53EA\u8BFB\uFF09"),
20956
+ h("code", { className: "odc-code" }, baseURL === "" ? "\u2014" : baseURL)
20957
+ ),
20958
+ h(
20959
+ "div",
20960
+ null,
20961
+ h("span", { className: "odc-label" }, "\u5F53\u524D\u63A8\u7406\u8DEF\u5F84\uFF08\u53EA\u8BFB\uFF09"),
20962
+ h("code", { className: "odc-code" }, protocolEndpoint(baseURL, protocol))
20963
+ ),
20964
+ 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")
20965
+ )
20966
+ ),
20967
+ h(
20968
+ "section",
20969
+ { className: "odc-card" },
20970
+ h("h3", null, "\u51ED\u636E\u4E0E\u7F51\u5173"),
20971
+ 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"),
20972
+ h(
20973
+ "div",
20974
+ { className: "odc-field" },
20975
+ h("label", { htmlFor: "odc-api-key-env" }, "API Key \u73AF\u5883\u53D8\u91CF / \u51ED\u636E\u5F15\u7528\u540D"),
20976
+ h("input", {
20977
+ id: "odc-api-key-env",
20978
+ className: "odc-input",
20979
+ type: "text",
20980
+ value: apiKeyEnv,
20981
+ disabled: !writable,
20982
+ autoCapitalize: "off",
20983
+ spellCheck: false,
20984
+ onChange: (event) => setApiKeyEnv(event.target.value)
20985
+ }),
20986
+ 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")
20987
+ ),
20988
+ h(
20989
+ "div",
20990
+ { className: "odc-actions", style: { marginTop: 10 } },
20991
+ h("button", { className: "odc-button", type: "button", disabled: !writable || pendingField === "apiKeyEnv", onClick: () => {
20992
+ void saveKeyRef();
20993
+ } }, "\u4FDD\u5B58\u5F15\u7528\u540D")
20994
+ ),
20995
+ h(NoticeView, { notice: keyNotice })
20996
+ ),
20997
+ h(
20998
+ "section",
20999
+ { className: "odc-card" },
21000
+ h("h3", null, "\u8BF7\u6C42\u65F6\u9650\u4E0E\u81EA\u52A8\u5237\u65B0"),
21001
+ 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"),
21002
+ h(NumberField, {
21003
+ id: "odc-request-timeout",
21004
+ label: "\u8BF7\u6C42\u603B\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
21005
+ hint: "\u4ECE\u53D1\u51FA\u8BF7\u6C42\u5230\u54CD\u5E94\u7ED3\u675F\u7684\u4E0A\u9650\uFF0C1000\u20133600000\u3002",
21006
+ value: requestTimeout,
21007
+ min: MIN_REQUEST_TIMEOUT_MS,
21008
+ max: MAX_REQUEST_TIMEOUT_MS,
21009
+ integer: true,
21010
+ disabled: !writable || pendingField === "requestTimeoutMs",
21011
+ onCommit: (value) => setFormField("requestTimeoutMs", value)
21012
+ }),
21013
+ h(NumberField, {
21014
+ id: "odc-stream-idle-timeout",
21015
+ label: "\u6D41\u5F0F\u7A7A\u95F2\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
21016
+ hint: "\u6D41\u5F0F\u54CD\u5E94\u65E0\u65B0\u6570\u636E\u7684\u6700\u957F\u7B49\u5F85\uFF0C1000\u20131800000\u3002",
21017
+ value: streamIdleTimeout,
21018
+ min: MIN_STREAM_IDLE_TIMEOUT_MS,
21019
+ max: MAX_STREAM_IDLE_TIMEOUT_MS,
21020
+ integer: true,
21021
+ disabled: !writable || pendingField === "streamIdleTimeoutMs",
21022
+ onCommit: (value) => setFormField("streamIdleTimeoutMs", value)
21023
+ }),
21024
+ h(NumberField, {
21025
+ id: "odc-quota-refresh",
21026
+ label: "\u989D\u5EA6\u81EA\u52A8\u5237\u65B0\u95F4\u9694\uFF08\u79D2\uFF09",
21027
+ hint: `5\u20133600\uFF1B\u9ED8\u8BA4 ${DEFAULT_QUOTA_REFRESH_SECONDS} \u79D2\uFF0C\u9875\u9762\u5173\u95ED\u540E\u505C\u6B62\u3002`,
21028
+ value: refreshSeconds,
21029
+ min: MIN_QUOTA_REFRESH_SECONDS,
21030
+ max: MAX_QUOTA_REFRESH_SECONDS,
21031
+ integer: true,
21032
+ disabled: !writable || pendingField === "quotaRefreshSeconds",
21033
+ onCommit: (value) => setFormField("quotaRefreshSeconds", value)
21034
+ })
20325
21035
  )
20326
21036
  );
20327
21037
  }
@@ -20483,79 +21193,6 @@ function Connection({ status, formSnapshot, setFormField, pendingField, caller,
20483
21193
  )
20484
21194
  );
20485
21195
  }
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
21196
  function tabAfter(current, key) {
20560
21197
  const index = TABS.findIndex((tab) => tab.id === current);
20561
21198
  if (key === "Home") return TABS[0].id;
@@ -20571,11 +21208,15 @@ function SettingsPage({ caller, form }) {
20571
21208
  const [error62, setError] = React.useState();
20572
21209
  const [loading, setLoading] = React.useState(true);
20573
21210
  const [refreshing, setRefreshing] = React.useState(false);
21211
+ const [refreshingModels, setRefreshingModels] = React.useState(false);
21212
+ const [modelsNotice, setModelsNotice] = React.useState(null);
20574
21213
  const [pendingField, setPendingField] = React.useState(null);
20575
21214
  const [formNotice, setFormNotice] = React.useState(null);
21215
+ const [scheduleTick, setScheduleTick] = React.useState(0);
20576
21216
  const statusVersion = React.useRef(0);
20577
21217
  const quotaVersion = React.useRef(0);
20578
21218
  const loginVersion = React.useRef(0);
21219
+ const modelsVersion = React.useRef(0);
20579
21220
  const tabRefs = React.useRef([]);
20580
21221
  const baseId = React.useId();
20581
21222
  const formSnapshot = useFormSnapshot(form);
@@ -20593,14 +21234,18 @@ function SettingsPage({ caller, form }) {
20593
21234
  }
20594
21235
  }, [caller, mounted]);
20595
21236
  const refreshBusy = React.useRef(false);
20596
- const refresh = React.useCallback(async () => {
20597
- if (refreshBusy.current) return;
21237
+ const forcedRefreshPending = React.useRef(false);
21238
+ const refresh = React.useCallback(async (force = false) => {
21239
+ if (refreshBusy.current) {
21240
+ if (force) forcedRefreshPending.current = true;
21241
+ return;
21242
+ }
20598
21243
  refreshBusy.current = true;
20599
21244
  const version2 = ++quotaVersion.current;
20600
21245
  setRefreshing(true);
20601
21246
  setError(void 0);
20602
21247
  try {
20603
- const quota = await caller("refreshQuota");
21248
+ const quota = await caller(force ? "refreshQuotaNow" : "refreshQuota");
20604
21249
  if (!mounted.current || version2 !== quotaVersion.current) return;
20605
21250
  setStatus((previous) => previous === void 0 ? previous : { ...previous, quota });
20606
21251
  await loadStatus();
@@ -20610,6 +21255,10 @@ function SettingsPage({ caller, form }) {
20610
21255
  if (mounted.current && version2 === quotaVersion.current) {
20611
21256
  refreshBusy.current = false;
20612
21257
  setRefreshing(false);
21258
+ if (forcedRefreshPending.current) {
21259
+ forcedRefreshPending.current = false;
21260
+ void refresh(true);
21261
+ }
20613
21262
  }
20614
21263
  }
20615
21264
  }, [caller, loadStatus, mounted]);
@@ -20619,7 +21268,9 @@ function SettingsPage({ caller, form }) {
20619
21268
  statusVersion.current += 1;
20620
21269
  quotaVersion.current += 1;
20621
21270
  loginVersion.current += 1;
21271
+ modelsVersion.current += 1;
20622
21272
  refreshBusy.current = false;
21273
+ forcedRefreshPending.current = false;
20623
21274
  };
20624
21275
  }, [refresh]);
20625
21276
  const setFormField = React.useCallback(async (field, value) => {
@@ -20628,6 +21279,7 @@ function SettingsPage({ caller, form }) {
20628
21279
  try {
20629
21280
  const accepted = await form.set(field, value);
20630
21281
  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" });
21282
+ if (accepted && (field === "useDesktopCredential" || field === "quotaRefreshSeconds")) await refresh(true);
20631
21283
  return accepted;
20632
21284
  } catch {
20633
21285
  if (mounted.current) setFormNotice({ kind: "error", text: GENERIC_ERROR });
@@ -20635,7 +21287,29 @@ function SettingsPage({ caller, form }) {
20635
21287
  } finally {
20636
21288
  if (mounted.current) setPendingField(null);
20637
21289
  }
20638
- }, [form, mounted]);
21290
+ }, [form, mounted, refresh]);
21291
+ const refreshModelsBusy = React.useRef(false);
21292
+ const refreshModels = React.useCallback(async () => {
21293
+ if (refreshModelsBusy.current) return;
21294
+ refreshModelsBusy.current = true;
21295
+ const version2 = ++modelsVersion.current;
21296
+ setRefreshingModels(true);
21297
+ setModelsNotice(null);
21298
+ try {
21299
+ const next = await caller("refreshModels");
21300
+ if (!mounted.current || version2 !== modelsVersion.current) return;
21301
+ setStatus(next);
21302
+ setError(void 0);
21303
+ 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);
21304
+ } catch {
21305
+ 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" });
21306
+ } finally {
21307
+ if (mounted.current && version2 === modelsVersion.current) {
21308
+ setRefreshingModels(false);
21309
+ refreshModelsBusy.current = false;
21310
+ }
21311
+ }
21312
+ }, [caller, mounted]);
20639
21313
  const loginState = status?.login.state;
20640
21314
  const loginActive = loginState !== void 0 && loginInFlight(loginState);
20641
21315
  React.useEffect(() => {
@@ -20664,17 +21338,54 @@ function SettingsPage({ caller, form }) {
20664
21338
  loginVersion.current += 1;
20665
21339
  };
20666
21340
  }, [caller, loadStatus, loginActive, mounted, refresh]);
21341
+ const configuredSeconds = formSnapshot.value?.quotaRefreshSeconds ?? status?.config.quotaRefreshSeconds;
21342
+ const refreshIntervalSeconds = configuredSeconds === void 0 ? DEFAULT_QUOTA_REFRESH_SECONDS : normaliseQuotaRefreshSeconds(configuredSeconds);
21343
+ const quotaNextRefreshAt = status?.quota.nextRefreshAt;
21344
+ const schedule = React.useMemo(
21345
+ () => status?.quota.state === "need-login" || status?.quota.autoRefreshPaused ? void 0 : quotaAutoRefresh(refreshIntervalSeconds, quotaNextRefreshAt, Date.now(), Boolean(status?.quota.refreshFailures)),
21346
+ [refreshIntervalSeconds, quotaNextRefreshAt, status?.quota.state, status?.quota.autoRefreshPaused, status?.quota.refreshFailures, scheduleTick]
21347
+ );
21348
+ React.useEffect(() => {
21349
+ if (schedule === void 0) return;
21350
+ const timer = setTimeout(() => {
21351
+ void refresh().finally(() => {
21352
+ if (mounted.current) setScheduleTick((tick) => tick + 1);
21353
+ });
21354
+ }, schedule.delayMs);
21355
+ return () => clearTimeout(timer);
21356
+ }, [schedule, refresh, mounted]);
20667
21357
  const selectTab = (next) => {
20668
21358
  setTab(next);
20669
21359
  if (next === "overview" && tab !== "overview") void refresh();
20670
21360
  };
20671
21361
  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 });
21362
+ if (tab === "overview") {
21363
+ content = h(Overview, {
21364
+ status,
21365
+ busy: refreshing,
21366
+ onRefresh: () => {
21367
+ void refresh(true);
21368
+ },
21369
+ nextAutoRefreshAt: schedule?.at,
21370
+ refreshIntervalSeconds
21371
+ });
21372
+ } else if (tab === "models") {
21373
+ content = h(Models, {
21374
+ status,
21375
+ formSnapshot,
21376
+ setFormField,
21377
+ pendingField,
21378
+ onRefreshModels: () => {
21379
+ void refreshModels();
21380
+ },
21381
+ refreshingModels,
21382
+ notice: modelsNotice
21383
+ });
21384
+ } else if (tab === "connection") {
21385
+ content = h(Connection, { status, formSnapshot, setFormField, pendingField, caller, loadStatus, refreshAfterLogin: refresh });
21386
+ } else {
21387
+ content = h(Advanced, { status, formSnapshot, setFormField, pendingField });
21388
+ }
20678
21389
  return h(
20679
21390
  "div",
20680
21391
  { className: "odc-settings" },