ai-zero-token 2.0.7 → 2.0.8

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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { randomUUID } from "node:crypto";
3
3
  import fs from "node:fs/promises";
4
+ import { networkInterfaces } from "node:os";
4
5
  import path from "node:path";
5
6
  import { Readable } from "node:stream";
6
7
  import { promisify } from "node:util";
@@ -24,6 +25,7 @@ const adminUiIndexPath = path.join(adminUiDistDir, "index.html");
24
25
  const BYTES_PER_MIB = 1024 * 1024;
25
26
  const MAX_GATEWAY_REQUEST_LOGS = 100;
26
27
  const MAX_CODEX_RESPONSE_PROFILE_BINDINGS = 5e3;
28
+ const CODEX_STREAM_DRAIN_AFTER_CLIENT_CLOSE_MS = 3e4;
27
29
  const DEFAULT_ROUTE_BODY_LIMIT_BYTES = 128 * BYTES_PER_MIB;
28
30
  const CODEX_COMPACT_BODY_LIMIT_BYTES = 256 * BYTES_PER_MIB;
29
31
  const gunzipAsync = promisify(gunzip);
@@ -252,20 +254,47 @@ function isObjectRecord(value) {
252
254
  function tokenNumber(value) {
253
255
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
254
256
  }
257
+ function sumTokenNumbers(value, keys) {
258
+ if (!value) {
259
+ return null;
260
+ }
261
+ let total = 0;
262
+ let seen = false;
263
+ for (const key of keys) {
264
+ const item = tokenNumber(value[key]);
265
+ if (item !== null) {
266
+ total += item;
267
+ seen = true;
268
+ }
269
+ }
270
+ return seen ? total : null;
271
+ }
255
272
  function normalizeTokenUsage(value) {
256
273
  if (!isObjectRecord(value)) {
257
274
  return null;
258
275
  }
259
276
  const inputTokens = tokenNumber(value.input_tokens ?? value.prompt_tokens);
260
277
  const outputTokens = tokenNumber(value.output_tokens ?? value.completion_tokens);
261
- const totalTokens = tokenNumber(value.total_tokens) ?? (inputTokens !== null || outputTokens !== null ? (inputTokens ?? 0) + (outputTokens ?? 0) : null);
262
- if (inputTokens === null && outputTokens === null && totalTokens === null) {
278
+ const inputDetails = isObjectRecord(value.input_tokens_details) ? value.input_tokens_details : null;
279
+ const promptDetails = isObjectRecord(value.prompt_tokens_details) ? value.prompt_tokens_details : null;
280
+ const cacheCreation = isObjectRecord(value.cache_creation) ? value.cache_creation : null;
281
+ const openAiCachedTokens = tokenNumber(inputDetails?.cached_tokens ?? promptDetails?.cached_tokens);
282
+ const cacheReadTokens = openAiCachedTokens ?? tokenNumber(value.cache_read_input_tokens ?? value.cached_tokens);
283
+ const cacheCreationTokens = tokenNumber(value.cache_creation_input_tokens ?? value.cache_creation_tokens) ?? tokenNumber(inputDetails?.cache_creation_tokens ?? promptDetails?.cache_creation_tokens) ?? sumTokenNumbers(cacheCreation, ["ephemeral_5m_input_tokens", "ephemeral_1h_input_tokens"]);
284
+ const inputIncludesCacheRead = openAiCachedTokens !== null;
285
+ const inferredTotalTokens = inputTokens !== null || outputTokens !== null || cacheReadTokens !== null || cacheCreationTokens !== null ? (inputTokens ?? 0) + (outputTokens ?? 0) + (inputIncludesCacheRead ? 0 : cacheReadTokens ?? 0) + (cacheCreationTokens ?? 0) : null;
286
+ const totalTokens = tokenNumber(value.total_tokens) ?? inferredTotalTokens;
287
+ const uncachedInputTokens = inputTokens !== null ? inputIncludesCacheRead ? Math.max(0, inputTokens - (cacheReadTokens ?? 0)) : inputTokens : null;
288
+ if (inputTokens === null && outputTokens === null && totalTokens === null && cacheReadTokens === null && cacheCreationTokens === null) {
263
289
  return null;
264
290
  }
265
291
  return {
266
292
  inputTokens,
293
+ uncachedInputTokens,
267
294
  outputTokens,
268
- totalTokens
295
+ totalTokens,
296
+ cacheCreationTokens,
297
+ cacheReadTokens
269
298
  };
270
299
  }
271
300
  function extractTokenUsage(value, depth = 0) {
@@ -306,6 +335,40 @@ function imageUsageToTokenUsage(usage) {
306
335
  totalTokens: usage.total_tokens
307
336
  };
308
337
  }
338
+ function buildResponsesUsagePayload(usage) {
339
+ if (!usage) {
340
+ return void 0;
341
+ }
342
+ const inputTokens = tokenNumber(usage.inputTokens) ?? 0;
343
+ const outputTokens = tokenNumber(usage.outputTokens) ?? 0;
344
+ const totalTokens = tokenNumber(usage.totalTokens) ?? inputTokens + outputTokens;
345
+ const cacheReadTokens = tokenNumber(usage.cacheReadTokens);
346
+ const cacheCreationTokens = tokenNumber(usage.cacheCreationTokens);
347
+ return {
348
+ input_tokens: inputTokens,
349
+ output_tokens: outputTokens,
350
+ total_tokens: totalTokens,
351
+ ...cacheReadTokens !== null ? { input_tokens_details: { cached_tokens: cacheReadTokens } } : {},
352
+ ...cacheCreationTokens !== null ? { cache_creation_input_tokens: cacheCreationTokens } : {}
353
+ };
354
+ }
355
+ function buildChatCompletionsUsagePayload(usage) {
356
+ if (!usage) {
357
+ return void 0;
358
+ }
359
+ const promptTokens = tokenNumber(usage.inputTokens) ?? 0;
360
+ const completionTokens = tokenNumber(usage.outputTokens) ?? 0;
361
+ const totalTokens = tokenNumber(usage.totalTokens) ?? promptTokens + completionTokens;
362
+ const cacheReadTokens = tokenNumber(usage.cacheReadTokens);
363
+ const cacheCreationTokens = tokenNumber(usage.cacheCreationTokens);
364
+ return {
365
+ prompt_tokens: promptTokens,
366
+ completion_tokens: completionTokens,
367
+ total_tokens: totalTokens,
368
+ ...cacheReadTokens !== null ? { prompt_tokens_details: { cached_tokens: cacheReadTokens } } : {},
369
+ ...cacheCreationTokens !== null ? { cache_creation_input_tokens: cacheCreationTokens } : {}
370
+ };
371
+ }
309
372
  function extractUsageErrorType(details, statusCode) {
310
373
  const error = isObjectRecord(details?.error) ? details.error : null;
311
374
  const upstreamErrorCode = error?.upstreamErrorCode;
@@ -792,6 +855,7 @@ function summarizeCodexChatBody(body) {
792
855
  model: body.model ?? "default",
793
856
  stream: body.stream,
794
857
  store: body.store,
858
+ hasPromptCacheKey: typeof body.prompt_cache_key === "string" && body.prompt_cache_key.trim().length > 0,
795
859
  inputItems: Array.isArray(body.input) ? body.input.length : void 0,
796
860
  tools: Array.isArray(body.tools) ? body.tools.length : void 0,
797
861
  toolNames: toolNames.slice(0, 50),
@@ -932,10 +996,12 @@ function summarizeImageEditRequestForLog(body) {
932
996
  };
933
997
  }
934
998
  function buildResponseApiBody(result, includeRaw) {
999
+ const usage = buildResponsesUsagePayload(extractTokenUsage(result.raw));
935
1000
  const responseBody = {
936
1001
  object: "response",
937
1002
  provider: result.provider,
938
1003
  model: result.model,
1004
+ ...usage ? { usage } : {},
939
1005
  output_text: result.text,
940
1006
  output: [
941
1007
  {
@@ -960,11 +1026,13 @@ function buildResponseApiBody(result, includeRaw) {
960
1026
  }
961
1027
  function buildChatCompletionsBody(result) {
962
1028
  const hasToolCalls = result.toolCalls.length > 0;
1029
+ const usage = buildChatCompletionsUsagePayload(extractTokenUsage(result.raw));
963
1030
  const body = {
964
1031
  id: `chatcmpl_${randomUUID().replace(/-/g, "")}`,
965
1032
  object: "chat.completion",
966
1033
  created: Math.floor(Date.now() / 1e3),
967
1034
  model: result.model,
1035
+ ...usage ? { usage } : {},
968
1036
  choices: [
969
1037
  {
970
1038
  index: 0,
@@ -1002,7 +1070,7 @@ function buildChatCompletionChunk(params) {
1002
1070
  ]
1003
1071
  };
1004
1072
  }
1005
- function sendChatCompletionsStream(reply, result) {
1073
+ function sendChatCompletionsStream(reply, result, includeUsage = false) {
1006
1074
  const id = `chatcmpl_${randomUUID().replace(/-/g, "")}`;
1007
1075
  const created = Math.floor(Date.now() / 1e3);
1008
1076
  reply.raw.writeHead(200, {
@@ -1052,6 +1120,17 @@ function sendChatCompletionsStream(reply, result) {
1052
1120
  delta: {},
1053
1121
  finishReason: result.toolCalls.length > 0 ? "tool_calls" : "stop"
1054
1122
  }));
1123
+ const usage = includeUsage ? buildChatCompletionsUsagePayload(extractTokenUsage(result.raw)) : void 0;
1124
+ if (usage) {
1125
+ writeChatCompletionsSseEvent(reply, {
1126
+ id,
1127
+ object: "chat.completion.chunk",
1128
+ created,
1129
+ model: result.model,
1130
+ choices: [],
1131
+ usage
1132
+ });
1133
+ }
1055
1134
  reply.raw.write("data: [DONE]\n\n");
1056
1135
  reply.raw.end();
1057
1136
  }
@@ -1136,6 +1215,57 @@ function resolveOrigin(request) {
1136
1215
  }
1137
1216
  return "http://127.0.0.1:8787";
1138
1217
  }
1218
+ function isLoopbackHost(host) {
1219
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
1220
+ }
1221
+ function isPrivateIpv4(address) {
1222
+ if (address.startsWith("10.")) {
1223
+ return true;
1224
+ }
1225
+ if (address.startsWith("192.168.")) {
1226
+ return true;
1227
+ }
1228
+ const match = address.match(/^172\.(\d+)\./);
1229
+ if (!match) {
1230
+ return false;
1231
+ }
1232
+ const second = Number.parseInt(match[1] ?? "", 10);
1233
+ return second >= 16 && second <= 31;
1234
+ }
1235
+ function getLanIpv4Addresses() {
1236
+ const seen = /* @__PURE__ */ new Set();
1237
+ const addresses = [];
1238
+ const interfaces = networkInterfaces();
1239
+ for (const [name, details] of Object.entries(interfaces)) {
1240
+ for (const detail of details ?? []) {
1241
+ const family = String(detail.family);
1242
+ const isIpv4 = family === "IPv4" || family === "4";
1243
+ if (!isIpv4 || detail.internal || seen.has(detail.address)) {
1244
+ continue;
1245
+ }
1246
+ if (detail.address === "0.0.0.0" || detail.address.startsWith("127.") || detail.address.startsWith("169.254.")) {
1247
+ continue;
1248
+ }
1249
+ seen.add(detail.address);
1250
+ addresses.push({
1251
+ address: detail.address,
1252
+ label: name,
1253
+ private: isPrivateIpv4(detail.address)
1254
+ });
1255
+ }
1256
+ }
1257
+ return addresses.sort((left, right) => Number(right.private) - Number(left.private) || left.address.localeCompare(right.address, "en")).map(({ address, label }) => ({ address, label }));
1258
+ }
1259
+ function createShareAddress(protocol, host, port, label) {
1260
+ const origin = `${protocol}://${host}:${port}`;
1261
+ return {
1262
+ host,
1263
+ label,
1264
+ adminUrl: `${origin}/`,
1265
+ baseUrl: `${origin}/v1`,
1266
+ codexBaseUrl: `${origin}/codex/v1`
1267
+ };
1268
+ }
1139
1269
  function normalizeError(error) {
1140
1270
  return error instanceof Error ? error : new Error(String(error));
1141
1271
  }
@@ -1169,7 +1299,8 @@ function createSseStreamStats() {
1169
1299
  bytes: 0,
1170
1300
  completed: false,
1171
1301
  responseIds: /* @__PURE__ */ new Set(),
1172
- tokenUsage: null
1302
+ tokenUsage: null,
1303
+ parseErrorCount: 0
1173
1304
  };
1174
1305
  }
1175
1306
  function extractSseResponseId(value) {
@@ -1186,6 +1317,9 @@ function extractSseResponseId(value) {
1186
1317
  }
1187
1318
  return void 0;
1188
1319
  }
1320
+ function isSseTerminalUsageEvent(eventType) {
1321
+ return eventType === "response.completed" || eventType === "response.done" || eventType === "response.failed" || eventType === "response.incomplete";
1322
+ }
1189
1323
  function trackSseChunk(stats, chunk) {
1190
1324
  const text = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk).toString("utf8") : String(chunk);
1191
1325
  stats.bytes += Buffer.byteLength(text);
@@ -1207,14 +1341,15 @@ function trackSseChunk(stats, chunk) {
1207
1341
  if (responseId) {
1208
1342
  stats.responseIds.add(responseId);
1209
1343
  }
1210
- const tokenUsage = extractTokenUsage(parsed);
1344
+ const tokenUsage = isSseTerminalUsageEvent(eventType) ? extractTokenUsage(parsed) : null;
1211
1345
  if (tokenUsage) {
1212
1346
  stats.tokenUsage = tokenUsage;
1213
1347
  }
1214
1348
  } catch {
1349
+ stats.parseErrorCount += 1;
1215
1350
  }
1216
1351
  }
1217
- if (eventType === "response.completed") {
1352
+ if (eventType === "response.completed" || eventType === "response.done") {
1218
1353
  stats.completed = true;
1219
1354
  stats.terminalEvent = eventType;
1220
1355
  } else if (eventType === "response.failed" || eventType === "response.incomplete") {
@@ -1226,6 +1361,24 @@ function trackSseChunk(stats, chunk) {
1226
1361
  stats.buffer = stats.buffer.slice(-65536);
1227
1362
  }
1228
1363
  }
1364
+ function sseTokenUsageStatus(stats, statusCode) {
1365
+ if (stats.tokenUsage) {
1366
+ return "captured";
1367
+ }
1368
+ if (statusCode < 200 || statusCode >= 400) {
1369
+ return "upstream_error";
1370
+ }
1371
+ if (stats.parseErrorCount > 0 && !stats.terminalEvent) {
1372
+ return "parse_failed";
1373
+ }
1374
+ if (!stats.terminalEvent) {
1375
+ return "missing_terminal";
1376
+ }
1377
+ if (isSseTerminalUsageEvent(stats.terminalEvent)) {
1378
+ return "terminal_without_usage";
1379
+ }
1380
+ return "not_returned";
1381
+ }
1229
1382
  function createApp(params) {
1230
1383
  const defaultBodyLimit = params?.bodyLimit ?? DEFAULT_ROUTE_BODY_LIMIT_BYTES;
1231
1384
  const codexCompactBodyLimit = Math.max(defaultBodyLimit, CODEX_COMPACT_BODY_LIMIT_BYTES);
@@ -1292,6 +1445,7 @@ function createApp(params) {
1292
1445
  accountLabel: entry.account,
1293
1446
  planType: profile?.quota?.planType,
1294
1447
  tokenUsage: log.usage?.tokenUsage,
1448
+ tokenUsageStatus: log.usage?.tokenUsageStatus,
1295
1449
  imageCount: log.usage?.imageCount,
1296
1450
  imageRoute: log.usage?.imageRoute ?? "none",
1297
1451
  errorType: log.usage?.errorType ?? extractUsageErrorType(log.details, entry.statusCode)
@@ -1330,6 +1484,7 @@ function createApp(params) {
1330
1484
  data: gatewayRequestLogs
1331
1485
  }));
1332
1486
  app.get("/_gateway/admin/usage", async () => ctx.usageService.getSummary());
1487
+ app.post("/_gateway/admin/usage/reset", async () => ctx.usageService.backupAndReset());
1333
1488
  async function buildAdminConfig(request) {
1334
1489
  const [status, models, modelCatalog, versionStatus, settings, profile, profiles, codexStatus, usage] = await Promise.all([
1335
1490
  ctx.authService.getStatus(),
@@ -1469,6 +1624,26 @@ function createApp(params) {
1469
1624
  };
1470
1625
  });
1471
1626
  app.get("/_gateway/admin/config", async (request) => buildAdminConfig(request));
1627
+ app.get("/_gateway/admin/share", async (request) => {
1628
+ const status = await ctx.authService.getStatus();
1629
+ const protocol = request.protocol === "https" ? "https" : "http";
1630
+ const port = request.raw.socket.localPort || status.serverPort;
1631
+ const serverHost = status.serverHost || "0.0.0.0";
1632
+ const lanReachable = serverHost === "0.0.0.0" || serverHost === "::" || !isLoopbackHost(serverHost);
1633
+ const addresses = getLanIpv4Addresses().map((item) => createShareAddress(protocol, item.address, port, item.label));
1634
+ const requestHost = request.headers.host?.replace(/:\d+$/u, "");
1635
+ if (requestHost && !isLoopbackHost(requestHost) && !addresses.some((item) => item.host === requestHost)) {
1636
+ addresses.unshift(createShareAddress(protocol, requestHost, port, "\u5F53\u524D\u8BBF\u95EE\u5730\u5740"));
1637
+ }
1638
+ return {
1639
+ primary: lanReachable ? addresses[0] ?? null : null,
1640
+ addresses,
1641
+ local: createShareAddress(protocol, "127.0.0.1", port, "\u672C\u673A"),
1642
+ serverHost,
1643
+ serverPort: port,
1644
+ lanReachable
1645
+ };
1646
+ });
1472
1647
  app.post("/_gateway/admin/login", async (request) => {
1473
1648
  await ctx.authService.login("openai-codex");
1474
1649
  await ctx.authService.syncActiveProfileQuota("openai-codex", {
@@ -1841,6 +2016,8 @@ function createApp(params) {
1841
2016
  const abortController = new AbortController();
1842
2017
  let streamFinished = false;
1843
2018
  let headersCommitted = false;
2019
+ let clientDisconnected = false;
2020
+ let clientDrainTimer = null;
1844
2021
  let profile = null;
1845
2022
  let retryCount = 0;
1846
2023
  let failureRecorded = false;
@@ -1850,7 +2027,15 @@ function createApp(params) {
1850
2027
  let adventureFallbackReason;
1851
2028
  reply.raw.on("close", () => {
1852
2029
  if (!streamFinished) {
1853
- abortController.abort();
2030
+ clientDisconnected = true;
2031
+ if (!headersCommitted) {
2032
+ abortController.abort();
2033
+ return;
2034
+ }
2035
+ clientDrainTimer = setTimeout(() => {
2036
+ abortController.abort();
2037
+ }, CODEX_STREAM_DRAIN_AFTER_CLIENT_CLOSE_MS);
2038
+ clientDrainTimer.unref?.();
1854
2039
  }
1855
2040
  });
1856
2041
  try {
@@ -2044,14 +2229,39 @@ function createApp(params) {
2044
2229
  headersCommitted = true;
2045
2230
  reply.raw.flushHeaders?.();
2046
2231
  const streamStats = createSseStreamStats();
2232
+ const writeChunkToClient = async (chunk) => {
2233
+ if (clientDisconnected || reply.raw.destroyed || reply.raw.writableEnded) {
2234
+ clientDisconnected = true;
2235
+ return;
2236
+ }
2237
+ try {
2238
+ if (!reply.raw.write(chunk)) {
2239
+ await new Promise((resolve) => {
2240
+ const cleanup = () => {
2241
+ reply.raw.off("drain", cleanup);
2242
+ reply.raw.off("close", cleanup);
2243
+ resolve();
2244
+ };
2245
+ reply.raw.once("drain", cleanup);
2246
+ reply.raw.once("close", cleanup);
2247
+ });
2248
+ }
2249
+ } catch {
2250
+ clientDisconnected = true;
2251
+ }
2252
+ };
2047
2253
  for await (const chunk of Readable.fromWeb(upstream.body)) {
2048
2254
  trackSseChunk(streamStats, chunk);
2049
- if (!reply.raw.write(chunk)) {
2050
- await new Promise((resolve) => reply.raw.once("drain", resolve));
2051
- }
2255
+ await writeChunkToClient(chunk);
2052
2256
  }
2053
2257
  streamFinished = true;
2054
- reply.raw.end();
2258
+ if (clientDrainTimer) {
2259
+ clearTimeout(clientDrainTimer);
2260
+ clientDrainTimer = null;
2261
+ }
2262
+ if (!clientDisconnected && !reply.raw.destroyed && !reply.raw.writableEnded) {
2263
+ reply.raw.end();
2264
+ }
2055
2265
  for (const responseId of streamStats.responseIds) {
2056
2266
  rememberCodexResponseProfile(responseId, profile);
2057
2267
  }
@@ -2093,17 +2303,25 @@ function createApp(params) {
2093
2303
  completed: streamStats.completed,
2094
2304
  terminalEvent: streamStats.terminalEvent,
2095
2305
  bytes: streamStats.bytes,
2096
- usageCaptured: Boolean(streamStats.tokenUsage)
2306
+ usageCaptured: Boolean(streamStats.tokenUsage),
2307
+ tokenUsageStatus: sseTokenUsageStatus(streamStats, upstream.status),
2308
+ parseErrorCount: streamStats.parseErrorCount,
2309
+ clientDisconnected
2097
2310
  }
2098
2311
  },
2099
2312
  usage: {
2100
2313
  profile,
2101
2314
  tokenUsage: streamStats.tokenUsage,
2315
+ tokenUsageStatus: sseTokenUsageStatus(streamStats, upstream.status),
2102
2316
  imageRoute: codexImageRoute
2103
2317
  }
2104
2318
  });
2105
2319
  return reply;
2106
2320
  } catch (error) {
2321
+ if (clientDrainTimer) {
2322
+ clearTimeout(clientDrainTimer);
2323
+ clientDrainTimer = null;
2324
+ }
2107
2325
  const quota = error.quota;
2108
2326
  if (profile && !failureRecorded) {
2109
2327
  await ctx.authService.recordProfileRequestFailure(profile.profileId, error, quota, "openai-codex", {
@@ -2532,7 +2750,9 @@ function createApp(params) {
2532
2750
  artifactCount: result.artifacts.length
2533
2751
  });
2534
2752
  if (parsed.data.stream) {
2535
- sendChatCompletionsStream(reply, result);
2753
+ const rawStreamOptions = parsed.data.stream_options;
2754
+ const streamOptions = isObjectRecord(rawStreamOptions) ? rawStreamOptions : null;
2755
+ sendChatCompletionsStream(reply, result, streamOptions?.include_usage === true);
2536
2756
  return reply;
2537
2757
  }
2538
2758
  return buildChatCompletionsBody(result);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-zero-token",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Local-first OpenAI-compatible AI CLI and gateway with Codex OAuth, multi-account management, and gpt-image-2 image generation/editing.",
5
5
  "license": "MIT",
6
6
  "author": "AI Zero Token Contributors",
@@ -1 +0,0 @@
1
- :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#f8fafc;--panel:#fff;--panel-soft:#f8fafc;--line:#e2e8f0;--line-strong:#cbd5e1;--text:#0f172a;--text-soft:#334155;--text-muted:#64748b;--brand:#635bff;--brand-strong:#4f46e5;--brand-soft:#635bff1a;--blue:#3b82f6;--blue-soft:#3b82f61f;--green:#22c55e;--green-soft:#22c55e1f;--orange:#f59e0b;--orange-soft:#f59e0b1f;--red:#ef4444;--red-soft:#ef44441f;--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line);--shadow:0 2px 10px #0f172a0f;--shadow-sm:var(--shadow);--radius:16px;--radius-sm:12px;--radius-xs:10px}*{box-sizing:border-box}html{background:var(--bg)}body{min-width:0;min-height:100vh;color:var(--text);background:radial-gradient(circle at top left, #635bff14, transparent 28%), radial-gradient(circle at right top, #3b82f60f, transparent 32%), var(--bg);margin:0;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,sans-serif}button,input,select,textarea{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button{cursor:pointer}a{color:inherit;text-decoration:none}.app-shell{grid-template-columns:236px minmax(0,1fr);align-items:start;gap:14px;width:min(100vw - 32px,1540px);max-width:none;margin:6px auto 18px;display:grid}.sidebar,.card,.trend-card,.summary-card,.account-card,.log-table-wrap,.tester-tabs,.service-card,.update-panel{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0}.sidebar{border-radius:18px;gap:14px;min-width:0;max-height:calc(100vh - 20px);padding:14px 12px;display:grid;position:sticky;top:10px;overflow:auto}.brand{align-items:center;gap:12px;padding:2px 4px 4px;display:flex}.brand-mark{width:34px;height:34px;color:var(--brand);background:linear-gradient(#635bff24,#635bff0a);border:1px solid #635bff33;border-radius:12px;flex:none;place-items:center;display:grid;overflow:hidden}.brand-mark img{width:100%;height:100%;display:block}.brand strong{font-size:14px;line-height:1.25;display:block}.brand span{color:var(--text-muted);margin-top:5px;font-size:10px;line-height:1.5;display:block}.nav{gap:4px;display:grid}.nav-item{width:100%;min-height:36px;color:var(--text-soft);text-align:left;background:0 0;border:1px solid #0000;border-radius:11px;align-items:center;gap:10px;padding:0 11px;display:flex}.nav-item.is-active,.nav-item:hover{color:var(--brand);background:#635bff14;border-color:#635bff1a;font-weight:650}.sidebar-links{gap:12px;margin-top:auto}.sidebar-status{gap:12px;padding:11px}.sidebar-status-summary{grid-template-columns:16px minmax(0,1fr);align-items:center;gap:9px;min-width:0;display:grid}.sidebar-status-summary strong{font-size:14px;line-height:1.25;display:block}.sidebar-status-summary span:last-child{color:var(--text-muted);text-overflow:ellipsis;white-space:nowrap;margin-top:2px;font-size:11px;line-height:1.3;display:block;overflow:hidden}.sidebar-base-url{border:1px solid var(--line);width:100%;min-width:0;color:var(--text);text-align:left;background:#f8fafc;border-radius:11px;gap:5px;padding:9px;display:grid}.sidebar-base-url span{color:var(--text-muted);font-size:10px;font-weight:800}.sidebar-base-url code{min-width:0;color:var(--text);overflow-wrap:anywhere;font-size:11px;line-height:1.45}.sidebar-kv-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.sidebar-kv{background:#f8fafcd1;border-radius:11px;min-width:0;padding:8px 9px}.sidebar-kv.wide{grid-column:1/-1}.sidebar-kv span{color:var(--text-muted);font-size:10px;font-weight:800;line-height:1.2;display:block}.sidebar-kv strong{color:var(--text);overflow-wrap:anywhere;margin-top:4px;font-size:12px;line-height:1.35;display:block}.sidebar-link-list{gap:7px;display:grid}.sidebar-link{border:1px solid var(--line);width:100%;min-height:34px;color:var(--text-soft);text-align:left;background:#fff;border-radius:11px;justify-content:flex-start;align-items:center;gap:8px;padding:0 12px;font-size:12px;font-weight:700;display:inline-flex}.sidebar-link:hover{border-color:var(--line-strong);background:#f8fafc}.main{gap:16px;min-width:0;display:grid}.page-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-bottom:2px;display:flex}.topbar{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:12px;display:grid}.page-title h1{margin:0;font-size:27px;line-height:1.15}.page-kicker{color:var(--brand);letter-spacing:0;margin-bottom:8px;font-size:12px;font-weight:850;display:inline-flex}.page-title p{color:var(--text-muted);margin:8px 0 0;font-size:14px;line-height:1.6}.top-actions,.section-actions,.button-row,.example-row{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.top-actions .btn-primary,.top-actions .btn-secondary,.top-actions .btn-danger{border-radius:10px;min-height:36px;padding:0 12px;font-size:12px}.route-loading{place-items:center;min-height:56vh;display:grid}.route-loading-card{border:1px solid var(--line);min-width:220px;box-shadow:var(--shadow-sm);text-align:center;background:#ffffffeb;border-radius:14px;grid-template-columns:1fr;justify-items:center;gap:12px;padding:14px 16px;display:grid}.route-loading-card strong{font-size:14px;line-height:1.3;display:block}.route-loading-card p{color:var(--text-muted);margin:4px 0 0;font-size:12px;line-height:1.4}.route-loading-bar{background:linear-gradient(90deg,#635bff3d,#22c55eb8,#f97316e0) 0 0/200% 100%;border-radius:999px;width:92px;height:4px;animation:1.1s ease-in-out infinite route-loading-bar}@keyframes route-loading-bar{0%{background-position:0%}50%{background-position:100%}to{background-position:0%}}.btn-primary,.btn-secondary,.btn-danger{border:1px solid var(--line);white-space:nowrap;min-height:40px;color:var(--text-soft);background:#fff;border-radius:12px;justify-content:center;align-items:center;gap:8px;padding:0 14px;font-size:13px;font-weight:700;display:inline-flex;box-shadow:0 1px #0f172a05}.btn-primary{color:#fff;border-color:var(--brand);background:var(--brand)}.btn-primary:hover{background:var(--brand-strong)}.btn-secondary:hover{border-color:var(--line-strong);background:#f8fafc}.btn-danger{color:#ef4444;background:var(--red-soft);border-color:#ef44442e}.btn-danger:hover{background:#ef444429}.btn-primary:disabled,.btn-secondary:disabled,.btn-danger:disabled{cursor:not-allowed;opacity:.58}.icon-only{width:42px;padding:0}.summary-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;display:grid}.overview-summary-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.summary-card{background:linear-gradient(145deg, #fffffffa, #f8fbfff5), var(--surface);border-color:#94a3b83d;border-radius:12px;grid-template-columns:24px minmax(0,1fr);align-content:start;gap:9px;min-height:84px;padding:12px 13px;display:grid;position:relative;overflow:hidden;box-shadow:0 14px 28px #0f172a12,inset 0 1px #ffffffe6}.summary-card:before{content:"";background:var(--brand);opacity:.75;width:3px;position:absolute;inset:0 auto 0 0}.summary-card:after{content:"";pointer-events:none;background:radial-gradient(circle at 12% 0,#4053ff14,#0000 34%);position:absolute;inset:0}.summary-card.tone-blue:before{background:var(--blue)}.summary-card.tone-green:before{background:var(--green)}.summary-card.tone-orange:before{background:var(--orange)}.summary-card.tone-red:before{background:var(--red)}.summary-card.tone-slate:before{background:var(--text-muted)}.summary-card>div:last-child{z-index:1;gap:4px;min-width:0;display:grid;position:relative}.summary-card span{color:var(--text-muted);font-size:11px;font-weight:760;line-height:1.2}.summary-card strong{letter-spacing:0;overflow-wrap:break-word;min-width:0;font-size:19px;line-height:1.08;display:block}.summary-card.compact-value strong{overflow-wrap:anywhere;font-size:16px;line-height:1.15}.usage-summary{gap:5px;min-width:0;display:grid}.usage-summary-row{grid-template-columns:42px minmax(0,1fr);align-items:center;gap:7px;min-width:0;display:grid}.usage-summary-row span{min-width:0;height:auto;color:var(--blue);background:0 0;border-radius:0;justify-content:flex-start;align-items:center;font-size:10px;font-weight:850;display:inline-flex}.usage-summary-row span:before{content:"";width:5px;height:5px;box-shadow:0 0 0 3px color-mix(in srgb, currentColor 14%, transparent);background:currentColor;border-radius:999px;margin-right:4px}.usage-summary-row:nth-child(2) span{color:var(--green)}.summary-card.compact-value .usage-summary-row strong{white-space:nowrap;text-overflow:ellipsis;overflow-wrap:normal;min-width:0;font-size:12px;line-height:1.25;overflow:hidden}.summary-card p{color:var(--text-muted);margin:0;font-size:11px;line-height:1.32}.summary-card p:empty{display:none}.summary-icon{z-index:1;width:20px;height:20px;color:var(--brand);background:var(--brand-soft);border-radius:999px;place-items:center;display:grid;position:relative;box-shadow:inset 0 0 0 1px #ffffffb8}.summary-icon.blue{color:var(--blue);background:var(--blue-soft)}.summary-icon.green{color:var(--green);background:var(--green-soft)}.summary-icon.orange{color:var(--orange);background:var(--orange-soft)}.main-grid{grid-template-columns:minmax(0,1.8fr) minmax(360px,.95fr);align-items:start;gap:22px;display:grid}.overview-grid,.tester-route-grid{grid-template-columns:minmax(0,1.18fr) minmax(420px,.82fr);align-items:start;gap:22px;display:grid}.tester-route-grid{grid-template-columns:minmax(0,1fr)}.tester-route-grid .tester-card{width:100%}.card,.trend-card,.log-table-wrap,.update-panel{border-radius:20px;padding:20px}.section-head{justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:18px;display:flex}.section-head.compact{margin-bottom:14px}.section-head h2,.section-head h3{margin:0;font-size:20px;line-height:1.25}.section-head p{color:var(--text-muted);margin:7px 0 0;font-size:13px;line-height:1.6}.trend-card{gap:14px;display:grid}.chart-wrap{border:1px solid var(--line);background:linear-gradient(#f8fafc99,#fff);border-radius:16px;width:100%;padding:14px;overflow:hidden}.chart-legend{color:var(--text-muted);flex-wrap:wrap;gap:14px;font-size:12px;display:flex}.legend-item{align-items:center;gap:8px;display:inline-flex}.legend-swatch{border-radius:999px;width:10px;height:10px}.legend-swatch.purple{background:#635bff}.legend-swatch.blue{background:#3b82f6}.trend-svg{width:100%;height:210px;margin-top:10px;display:block}.trend-labels{color:var(--text-muted);grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-top:10px;font-size:11px;line-height:1.4;display:grid}.filter-row{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:18px;display:flex}.search-box{border:1px solid var(--line);min-width:0;min-height:40px;color:var(--text-muted);background:#fff;border-radius:12px;flex:220px;align-items:center;gap:10px;padding:0 12px;display:flex}.search-box input{width:100%;min-width:0;color:var(--text);background:0 0;border:0;outline:none}.input,.control,.textarea{border:1px solid var(--line);width:100%;color:var(--text);background:#fff;border-radius:12px;outline:none}.input,.control{min-height:40px;padding:0 12px}.filter-row .control{flex:0 0 156px;min-width:156px}.textarea{resize:vertical;min-height:180px;padding:14px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.55}.field{color:var(--text-soft);gap:8px;font-size:13px;font-weight:700;display:grid}.hint,.status-inline{color:var(--text-muted);margin:9px 0;font-size:13px;line-height:1.6}.account-selected-count{color:var(--text-muted);white-space:nowrap;font-size:12px;font-weight:700}.service-card{border-radius:16px;gap:12px;padding:14px;display:grid}.sidebar-status{border:1px solid var(--line);background:#fff}.sidebar-status.tone-green{border-color:#22c55e2e}.sidebar-status.tone-orange{background:#fffbebf2;border-color:#f59e0b3d}.sidebar-status.tone-red{background:#fef2f2f2;border-color:#ef444433}.sidebar-status .service-head span{color:var(--text-muted);font-size:11px;font-weight:700}.sidebar-meta-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.sidebar-meta{background:#f8fafce0;border-radius:11px;min-width:0;padding:8px 10px}.sidebar-meta span{color:var(--text-muted);font-size:10px;font-weight:800;line-height:1.2;display:block}.sidebar-meta strong{color:var(--text);overflow-wrap:anywhere;margin-top:4px;font-size:12px;line-height:1.35;display:block}.endpoint-card .compact-grid{grid-template-columns:1fr;gap:12px}.service-head{justify-content:space-between;align-items:center;gap:12px;display:flex}.service-list{gap:10px;display:grid}.compact-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.service-row{min-width:0}.service-row label{color:var(--text-muted);margin-bottom:4px;font-size:12px;display:block}.service-row strong,.service-row code{color:var(--text);word-break:break-word;overflow-wrap:anywhere;font-size:13px;line-height:1.5}.meta-grid .service-row label{margin-bottom:0;font-size:10px;line-height:1.4}.meta-grid .service-row strong,.meta-grid .service-row code{color:var(--text-soft);font-size:11px;line-height:1.45}.status-dot{background:var(--green);border-radius:999px;width:8px;height:8px;box-shadow:0 0 0 4px #22c55e24}.status-dot.offline{background:var(--orange);box-shadow:0 0 0 4px #f59e0b24}.update-panel{background:linear-gradient(135deg,#fffbebfa,#fffffff5),#fff;border-color:#f59e0b40;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;display:grid}.strong-update-panel{box-shadow:0 10px 22px #b4530917, var(--shadow-sm);border-color:#f59e0b57}.update-mark{color:#b45309;background:#fff7ed;border:1px solid #f59e0b3d;border-radius:12px;place-items:center;width:36px;height:36px;display:grid;box-shadow:inset 0 1px #ffffffc7}.update-copy{gap:4px;min-width:0;display:grid}.update-title-row{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}.update-title-row strong{font-size:16px;line-height:1.25}.update-copy p{color:var(--text-muted);flex-wrap:wrap;align-items:center;gap:6px;margin:0;font-size:13px;line-height:1.45;display:flex}.update-title-row span{color:var(--text-muted);font-size:13px;font-weight:700}.update-panel code{color:#92400e;overflow-wrap:anywhere;background:#fff;border:1px solid #f59e0b29;border-radius:8px;max-width:100%;padding:4px 7px;font-size:12px;line-height:1.35}.update-actions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:10px;display:flex}.update-panel .btn-primary,.update-panel .btn-secondary{white-space:nowrap;flex:none;align-items:center;gap:7px;min-height:36px;padding-inline:12px;text-decoration:none;display:inline-flex}.modal-backdrop,.drawer-backdrop,.loading-cover{z-index:20;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:#0f172a6b;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.loading-cover{z-index:30;color:var(--text);background:#f8fafcc2;gap:12px}.modal-card{border:1px solid var(--line);width:min(760px,100%);max-height:min(760px,100vh - 40px);box-shadow:var(--shadow);background:#fff;border-radius:18px;overflow:auto}.modal-card.wide{width:min(980px,100%)}.modal-head{z-index:1;border-bottom:1px solid var(--line);background:#fffffff5;justify-content:space-between;align-items:center;gap:12px;padding:18px 20px;display:flex;position:sticky;top:0}.modal-head h3{margin:0}.modal-body{padding:20px}.modal-grid{gap:16px;display:grid}.modal-section{border:1px solid var(--line);background:#f8fafc;border-radius:14px;gap:12px;padding:16px;display:grid}.modal-section h4{margin:0}.modal-section p,.contact-notes span{color:var(--text-muted);margin:0;font-size:13px;line-height:1.6}.import-textarea{min-height:220px}.zip-import-box,.zip-import-preview{border:1px solid var(--line);background:#f8fafcd1;border-radius:12px;gap:10px;padding:12px;display:grid}.zip-import-box{grid-template-columns:minmax(0,1fr) auto;align-items:center}.zip-import-box strong,.zip-import-preview strong{color:var(--text);font-size:13px;line-height:1.25}.zip-import-box p,.zip-import-preview p,.zip-import-preview span{color:var(--text-muted);margin:0;font-size:12px;line-height:1.5}.zip-import-trigger{cursor:pointer;position:relative;overflow:hidden}.zip-import-trigger input{opacity:0;pointer-events:none;position:absolute;inset:0}.zip-import-preview.ready{background:#f0fdf4bd;border-color:#22c55e38}.zip-import-preview.error{background:#fef2f2c2;border-color:#ef444438}.zip-import-preview .btn-primary{width:fit-content}.contact-notes{gap:14px;display:grid}.contact-note,.contact-qr{gap:6px;display:grid}.contact-note{border:1px solid var(--line);background:var(--panel-soft);border-radius:16px;padding:14px 16px}.contact-note strong{font-size:14px;line-height:1.4}.contact-note span,.contact-note a,.contact-note code{color:var(--text-soft);word-break:break-word;font-size:13px;line-height:1.6}.contact-qr{border:1px solid var(--line);background:#fff;border-radius:20px;gap:10px;padding:14px}.contact-qr img{border:1px solid var(--line);background:var(--panel-soft);border-radius:16px;width:100%;max-width:420px;height:auto;display:block}.contact-qr span{color:var(--text-muted);font-size:12px;line-height:1.6}.image-preview-stage{background:#0f172a;border-radius:14px;place-items:center;min-height:320px;display:grid;overflow:hidden}.image-preview-stage img{max-width:100%;max-height:min(70vh,680px);display:block}.preview-modal-meta{color:var(--text-soft);word-break:break-word;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;font-size:13px;line-height:1.7;display:flex}.spin{animation:.9s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.launch-page{grid-template-columns:minmax(420px,.82fr) minmax(520px,1.18fr);align-items:stretch;gap:16px;min-height:calc(100vh - 172px);display:grid}.launch-copy,.launch-visual{border:1px solid var(--line);min-width:0;box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:20px}.launch-copy{align-content:center;gap:20px;padding:28px;display:grid}.launch-identity{gap:18px;display:grid}.launch-app-icon{background:linear-gradient(180deg, #ffffffe6, #f8fafcd1), var(--surface);border:1px solid #635bff2e;border-radius:24px;place-items:center;width:96px;height:96px;display:grid;box-shadow:0 22px 44px #0f172a1f,0 0 0 8px #635bff0f}.launch-app-icon img{width:100%;height:100%;display:block}.launch-copy h2{max-width:560px;color:var(--text);letter-spacing:0;margin:0;font-size:44px;line-height:1.02}.launch-copy p{max-width:520px;color:var(--text-soft);margin:0;font-size:15px;line-height:1.8}.launch-metrics{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;display:grid}.launch-metrics div{border:1px solid var(--line);background:#f8fafcd6;border-radius:14px;gap:8px;min-width:0;padding:14px;display:grid}.launch-metrics svg{color:var(--brand)}.launch-metrics span{color:var(--text-muted);font-size:11px;font-weight:800}.launch-metrics strong{min-width:0;color:var(--text);overflow-wrap:anywhere;font-size:18px;line-height:1.2}.launch-actions{flex-wrap:wrap;gap:10px;display:flex}.launch-actions .btn-secondary,.launch-actions a.btn-secondary{text-decoration:none}.launch-download-note{color:var(--text-muted);margin:-2px 0 0;font-size:12px;line-height:1.6}.launch-pills{flex-wrap:wrap;gap:8px;display:flex}.launch-pills span{min-height:28px;color:var(--text-muted);background:#635bff14;border-radius:999px;align-items:center;padding:0 10px;font-size:12px;font-weight:700;display:inline-flex}.launch-status{border-top:1px solid var(--line);grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;margin-top:8px;padding-top:20px;display:grid}.launch-visual{background:radial-gradient(circle at 24% 22%,#635bff26,#0000 34%),radial-gradient(circle at 82% 78%,#22c55e1f,#0000 32%),linear-gradient(#fffffff5,#f8fafcf0),#fff;place-items:center;padding:18px;display:grid;overflow:hidden}.launch-visual-stage{aspect-ratio:1.2;place-items:center;width:min(760px,100%);display:grid;position:relative}.launch-visual-mark{z-index:2;border-radius:34px;width:min(160px,24%);position:absolute;top:5%;left:4%;box-shadow:0 24px 54px #0f172a2e,0 0 0 10px #ffffffc7}.launch-visual-dashboard{object-fit:contain;filter:drop-shadow(0 28px 50px #0f172a24);width:92%;max-height:min(560px,100vh - 250px);display:block}.left-column,.right-column{gap:18px;min-width:0;display:grid}.account-grid{justify-content:stretch;align-items:start;gap:16px;width:100%;display:grid}.account-stat-strip{flex-wrap:wrap;gap:8px;margin:-2px 0 2px;display:flex}.account-stat-pill{border:1px solid var(--line);min-height:30px;color:var(--text-soft);cursor:pointer;background:#fff;border-radius:999px;align-items:center;gap:7px;padding:0 10px;font-size:12px;font-weight:750;display:inline-flex}.account-stat-pill strong{color:var(--text);font-size:13px}.account-stat-pill:hover,.account-stat-pill.is-active{background:#f5f3ff;border-color:#635bff57}.account-stat-pill.tone-green{color:#15803d;background:#f0fdf4;border-color:#16a34a2e}.account-stat-pill.tone-red{color:#b91c1c;background:#fef2f2;border-color:#ef444438}.account-stat-pill.tone-orange{color:#b45309;background:#fff7ed;border-color:#f59e0b3d}.account-stat-pill.tone-blue{color:#1d4ed8;background:#eff6ff;border-color:#2563eb2e}.account-stat-pill.tone-brand{color:#4f46e5;background:#f5f3ff;border-color:#635bff33}.account-stat-pill.tone-muted{color:var(--text-muted);background:#f8fafc}.account-grid.profile-count-1{grid-template-columns:minmax(320px,520px)}.account-grid.profile-count-2{grid-template-columns:repeat(2,minmax(320px,1fr))}.account-grid.profile-count-3{grid-template-columns:repeat(3,minmax(300px,1fr))}.account-grid.profile-count-many{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}.account-card{--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line);--usage-color:#16a34a;--usage-soft:#16a34a1f;border-color:var(--plan-border);border-radius:16px;grid-template-rows:none;align-content:start;align-self:start;gap:12px;min-width:0;padding:14px;display:grid;position:relative;overflow:hidden}.account-card:before{content:"";background:var(--plan-color);height:3px;position:absolute;inset:0 0 auto}.account-card.plan-free{--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line)}.account-card.plan-plus{--plan-color:#635bff;--plan-soft:#635bff1c;--plan-border:#635bff33}.account-card.plan-pro{--plan-color:#4f46e5;--plan-soft:#4f46e51c;--plan-border:#4f46e538}.account-card.plan-team{--plan-color:#0f766e;--plan-soft:#0f766e1c;--plan-border:#0f766e38}.account-card.plan-premium{--plan-color:#d97706;--plan-soft:#d977061f;--plan-border:#d9770657;box-shadow:0 10px 26px #b453091a, var(--shadow)}.account-card.plan-enterprise{--plan-color:#a16207;--plan-soft:#a1620724;--plan-border:#47556947;box-shadow:0 12px 28px #0f172a1a, var(--shadow)}.account-card.is-auth-invalid{--plan-color:#ef4444;--plan-soft:#ef444417;--plan-border:#ef444452;border-color:var(--plan-border);box-shadow:0 12px 28px #b91c1c14, var(--shadow);background:linear-gradient(#fef2f2e6,#fffffffa 46%),#fff}.account-card.is-auth-invalid:before{background:#ef4444;height:3px}.account-card.is-auth-invalid .usage-corner{color:#991b1b;background:linear-gradient(135deg,#fef2f2,#fee2e2);border-color:#ef444447;box-shadow:0 8px 18px #b91c1c1a,inset 0 1px #ffffffd1}.account-card.is-auth-invalid .avatar{color:#dc2626;background:#fff;border-color:#ef444475;box-shadow:0 0 0 3px #ef44441a}.account-card.is-auth-invalid .progress-bar{opacity:.72}.account-card.is-auth-invalid .usage-status-row,.account-card.is-auth-invalid .progress-track{background:#fef2f2ad}.account-card.is-auth-invalid .account-actions .btn-secondary:disabled{opacity:.86;color:#991b1b;background:#fff;border-color:#ef44442e}.account-head{justify-content:space-between;align-items:flex-start;gap:12px;padding-top:8px;display:flex}.account-title{flex:1;gap:6px;min-width:0;display:grid}.account-name{align-items:center;gap:8px;min-width:0;display:flex}.account-name strong{-webkit-line-clamp:2;text-overflow:ellipsis;white-space:normal;overflow-wrap:anywhere;-webkit-box-orient:vertical;min-width:0;font-size:13px;line-height:1.35;display:-webkit-box;overflow:hidden}.account-icon-btn{border:1px solid var(--line);width:24px;height:24px;color:var(--text-muted);background:#fff;border-radius:999px;flex:none;justify-content:center;align-items:center;padding:0;display:flex}.account-icon-btn:hover{color:var(--plan-color);border-color:var(--plan-color);background:var(--plan-soft)}.avatar{background:var(--panel-soft);border:1px solid var(--plan-color);width:24px;height:24px;box-shadow:0 0 0 3px var(--plan-soft);color:var(--plan-color);border-radius:999px;flex:none;place-items:center;font-size:11px;font-weight:700;display:grid}.badge-row{flex-wrap:wrap;gap:6px;display:flex}.account-select{border:1px solid var(--line);min-height:28px;color:var(--text-muted);cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;background:#fff;border-radius:8px;align-items:center;gap:6px;margin-top:28px;padding:0 8px;font-size:12px;font-weight:600;display:inline-flex}.account-select input{width:14px;height:14px;margin:0}.badge{white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;min-height:22px;padding:0 8px;font-size:11px;font-weight:600;display:inline-flex}.badge.brand{color:var(--plan-color);background:var(--plan-soft)}.usage-corner{color:#047857;letter-spacing:0;pointer-events:none;z-index:1;background:linear-gradient(135deg,#ecfdf5,#d1fae5);border:1px solid #10b98147;border-radius:999px;align-items:center;gap:5px;min-height:24px;padding:0 10px 0 8px;font-size:10px;font-weight:800;line-height:22px;display:inline-flex;position:absolute;top:10px;right:12px;box-shadow:0 8px 18px #10b9811f,inset 0 1px #ffffffd1}.usage-corner:before{content:"";background:currentColor;border-radius:999px;flex:none;width:6px;height:6px;box-shadow:0 0 0 3px #10b9811f}.usage-corner span{line-height:1}.usage-corner.codex-only{color:#1d4ed8;background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#2563eb3d;box-shadow:0 8px 18px #2563eb1f,inset 0 1px #ffffffd1}.usage-corner.codex-only:before{box-shadow:0 0 0 3px #2563eb1f}.usage-corner.dual{color:#4f46e5;background:linear-gradient(135deg,#f5f3ff,#ede9fe);border-color:#635bff40;box-shadow:0 8px 18px #635bff21,inset 0 1px #ffffffd1}.usage-corner.dual:before{box-shadow:0 0 0 3px #635bff1f}.badge.green{color:#15803d;background:var(--green-soft)}.badge.orange{color:#b45309;background:var(--orange-soft)}.badge.red{color:#dc2626;background:var(--red-soft)}.badge.muted{color:var(--text-muted);background:#f1f5f9}.account-metrics{gap:10px;display:grid}.quota-row{gap:6px;display:grid}.quota-line{color:var(--text-soft);justify-content:space-between;align-items:center;gap:10px;font-size:11px;line-height:1.45;display:flex}.quota-line span{min-width:0}.quota-line strong{color:var(--text);flex-shrink:0;font-size:12px}.progress-track{background:#eef2f7;border-radius:999px;width:100%;height:5px;overflow:hidden}.progress-bar{border-radius:inherit;background:var(--brand);height:100%}.progress-bar.blue{background:var(--blue)}.progress-bar.orange{background:var(--orange)}.progress-bar.red{background:#f43f5e}.usage-status-row{background:var(--panel-soft);color:var(--text-muted);border-radius:10px;flex-wrap:nowrap;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;font-size:11px;line-height:1.4;display:flex}.usage-status{white-space:nowrap;align-items:center;gap:5px;min-width:0;font-weight:700;display:inline-flex}.usage-status svg{width:12px;height:12px;color:var(--text-muted);flex:none}.usage-dot{background:#cbd5e1;border-radius:999px;flex:none;width:6px;height:6px}.usage-dot.active{background:#22c55e;box-shadow:0 0 0 3px #22c55e1f}.usage-state-text{color:var(--text-muted);font-weight:700}.usage-status.is-active .usage-state-text{color:#15803d}.compact-meta-row{min-width:0;color:var(--text-muted);gap:8px;font-size:11px;line-height:1.45;display:grid}.compact-reset-list{flex-wrap:nowrap;align-items:center;gap:10px;min-width:0;display:flex}.compact-meta-item{flex:1 1 0;align-items:baseline;gap:5px;min-width:0;display:flex}.compact-meta-item label{color:var(--text-muted);white-space:nowrap;font-size:10px;line-height:1.4}.compact-meta-item strong{color:var(--text-soft);text-align:left;overflow-wrap:anywhere;font-size:11px;line-height:1.4}.compact-meta-actions{justify-content:center;align-items:center;gap:10px;margin-top:2px;display:flex}.compact-meta-actions:before,.compact-meta-actions:after{content:"";background:var(--line);flex:auto;min-width:18px;height:1px}.details-toggle{min-height:24px;color:var(--brand);white-space:nowrap;cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;gap:5px;padding:0 6px;font-size:11px;font-weight:700;display:inline-flex}.details-toggle:hover{color:#4338ca}.details-toggle svg{width:12px;height:12px;transition:transform .16s}.details-toggle.is-expanded svg{transform:rotate(180deg)}.meta-grid{border-top:1px solid var(--line);grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px;padding-top:10px;display:grid}.meta-item{gap:3px;min-width:0;display:grid}.meta-grid .service-row label{color:var(--text-muted);font-size:10px;line-height:1.4}.meta-grid .service-row strong,.meta-grid .service-row span,.meta-grid .service-row code{color:var(--text-soft);word-break:break-word;overflow-wrap:anywhere;font-size:11px;line-height:1.45}.account-actions{flex-wrap:wrap;gap:8px;margin-top:0;display:flex}.account-actions .btn-secondary,.account-actions .btn-danger{border-radius:10px;flex:120px;min-height:36px;padding:0 12px;font-size:12px}.account-actions .btn-secondary.is-current{opacity:1;color:#047857;cursor:default;background:linear-gradient(135deg,#f0fdf4,#dcfce7);border-color:#10b9815c;position:relative;box-shadow:inset 0 1px #fffc,0 6px 14px #10b98114}.account-actions .btn-secondary.is-current:before{content:"";background:#22c55e;border-radius:999px;flex:none;width:7px;height:7px;box-shadow:0 0 0 3px #22c55e1f}.account-actions .btn-secondary.is-current.codex{color:#1d4ed8;background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#2563eb52;box-shadow:inset 0 1px #fffc,0 6px 14px #2563eb14}.account-actions .btn-secondary.is-current.codex:before{background:#3b82f6;box-shadow:0 0 0 3px #3b82f61f}.usage-page{gap:16px;display:grid}.usage-actions{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#ffffffeb;border-radius:14px;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;display:flex}.usage-actions>div{gap:4px;min-width:0;display:grid}.usage-actions span{color:var(--text-muted);font-size:12px;font-weight:800}.usage-actions code{min-width:0;color:var(--text-soft);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.usage-summary-grid{grid-template-columns:repeat(6,minmax(150px,1fr))}.usage-scope-grid,.usage-dimension-grid{grid-template-columns:repeat(3,minmax(240px,1fr));gap:14px;display:grid}.usage-dimension-grid{grid-template-columns:repeat(2,minmax(320px,1fr))}.usage-scope-card,.usage-table-card{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:16px}.usage-scope-card{gap:14px;padding:16px;display:grid}.usage-scope-card>div:first-child{gap:5px;display:grid}.usage-scope-card span,.usage-table-head span{color:var(--text-muted);font-size:12px;font-weight:800}.usage-scope-card strong{color:var(--text);font-size:24px;line-height:1.1}.usage-scope-card p{color:var(--text-muted);margin:0;font-size:13px;line-height:1.5}.usage-scope-card dl{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin:0;display:grid}.usage-scope-card dl div{background:var(--panel-soft);border-radius:10px;gap:3px;min-width:0;padding:9px;display:grid}.usage-scope-card dt{color:var(--text-muted);font-size:11px;font-weight:800}.usage-scope-card dd{color:var(--text);margin:0;font-size:13px;font-weight:850}.usage-scope-footer{padding-top:2px}.usage-table-card{min-width:0;overflow:hidden}.usage-daily-card{grid-column:1/-1}.usage-table-head{justify-content:space-between;align-items:center;gap:12px;padding:14px 16px 10px;display:flex}.usage-table-head h3{color:var(--text);margin:0;font-size:15px}.usage-table-scroll{overflow-x:auto}.usage-table{border-collapse:collapse;width:100%;min-width:620px;font-size:12px}.usage-table th,.usage-table td{border-top:1px solid var(--line);text-align:left;white-space:nowrap;padding:10px 12px}.usage-table th{color:var(--text-muted);background:#f8fafc;font-weight:850}.usage-table td{color:var(--text-soft)}.usage-table td:first-child{max-width:260px;color:var(--text);text-overflow:ellipsis;overflow:hidden}.usage-empty{color:var(--text-muted);padding:18px 16px;font-size:13px}@media (width<=1100px){.usage-summary-grid,.usage-scope-grid,.usage-dimension-grid{grid-template-columns:repeat(2,minmax(220px,1fr))}}@media (width<=720px){.usage-actions,.usage-scope-grid,.usage-dimension-grid,.usage-summary-grid{grid-template-columns:1fr}.usage-actions{flex-direction:column;align-items:stretch}}.tester-card{gap:16px;display:grid}.tester-tabs,.tester-result-tabs{border-radius:14px;gap:4px;padding:6px;display:flex;overflow-x:auto}.tester-tabs{grid-template-columns:repeat(5,minmax(92px,1fr));gap:6px;display:grid}.tester-result-tabs{box-shadow:none;background:0 0;border:0;padding:0}.tab-btn{min-height:38px;color:var(--text-muted);white-space:nowrap;background:0 0;border:0;border-radius:10px;padding:0 14px;font-weight:800}.tab-btn.is-active{color:var(--brand);background:#fff;box-shadow:0 6px 16px #0f172a0f}.tester-workbench{grid-template-columns:minmax(360px,.92fr) minmax(360px,1.08fr);align-items:stretch;gap:16px;display:grid}.tester-pane{border:1px solid var(--line);background:linear-gradient(#fff,#f8fafc9e);border-radius:16px;align-content:start;gap:12px;min-width:0;padding:14px;display:grid}.tester-request-pane{grid-template-rows:auto auto minmax(180px,1fr) auto auto}.tester-body-field{grid-template-rows:auto minmax(0,1fr);min-height:0;display:grid}.tester-textarea{resize:vertical;height:100%;min-height:180px}.tester-actions-bar{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px;display:grid}.tester-actions-group{gap:10px;min-width:0;display:grid}.tester-actions-bar .example-row{justify-content:flex-start;gap:8px}.tester-copy-row{flex-wrap:wrap;gap:8px;display:flex}.tester-copy-row-top{margin-top:-4px}.tester-actions-bar .btn-secondary{border-radius:10px;min-height:34px;padding:0 10px;font-size:12px}.tester-response-pane{grid-template-rows:auto minmax(0,1fr)}.tester-result-head{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;display:grid}.tester-result-head .status-inline{text-align:right;white-space:nowrap;text-overflow:ellipsis;max-width:220px;margin:0;overflow:hidden}.edit-upload-row{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px 12px;margin-top:-2px;display:grid}.edit-upload-mode{gap:6px;min-width:0;display:grid}.edit-upload-mode>span{color:var(--text-muted);font-size:11px;font-weight:800;line-height:1.25}.edit-upload-toggle{flex-wrap:wrap;gap:6px;display:flex}.edit-upload-toggle .tab-btn{min-height:34px;padding:0 12px}.upload-btn{cursor:pointer;white-space:nowrap}.upload-btn input{display:none}.edit-upload-row span{color:var(--text-muted);grid-column:1/-1;font-size:12px;line-height:1.5}.pre{color:#e2e8f0;white-space:pre-wrap;overflow-wrap:anywhere;background:#0f172a;border-radius:12px;width:100%;min-height:312px;max-height:420px;margin:0;padding:16px;font-size:12px;line-height:1.6;overflow:auto}.preview-panel{min-height:312px}.preview-empty,.empty-state{border:1px dashed var(--line-strong);text-align:center;min-height:140px;color:var(--text-muted);background:#f8fafc;border-radius:14px;place-items:center;padding:20px;display:grid}.preview-grid{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:12px;display:grid}.preview-card{gap:8px;margin:0;display:grid}.preview-card button{border:1px solid var(--line);background:#fff;border-radius:12px;padding:0;overflow:hidden}.preview-card img{aspect-ratio:1;object-fit:cover;width:100%;display:block}.preview-card figcaption{color:var(--text-muted);overflow-wrap:anywhere;font-size:12px}.preview-actions{flex-wrap:wrap;gap:8px;display:flex}.preview-actions a{border:1px solid var(--line);min-height:34px;color:var(--text-soft);border-radius:10px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;font-weight:600;display:inline-flex}.pre.tester-log{min-height:200px}.image-bed-page{gap:16px;display:grid}.image-bed-workbench{grid-template-columns:minmax(0,1.45fr) minmax(320px,.8fr);align-items:start;gap:14px;display:grid}.image-bed-upload-panel,.image-bed-side-card,.image-bed-gallery-section,.image-bed-help-section{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff2}.image-bed-upload-panel{background:linear-gradient(135deg,#fffffffa,#f6f8ffeb),#fff;border-radius:18px;grid-template-rows:auto minmax(220px,1fr) auto;gap:16px;min-height:440px;padding:22px;display:grid;overflow:hidden}.image-bed-upload-panel.is-dragging{border-color:#635bff6b;box-shadow:0 18px 45px #635bff1f}.image-bed-upload-copy{gap:7px;max-width:680px;display:grid}.image-bed-upload-copy span{color:var(--brand);font-size:12px;font-weight:850;line-height:1.3}.image-bed-upload-copy h2{color:var(--text);margin:0;font-size:28px;line-height:1.16}.image-bed-upload-copy p{color:var(--text-muted);margin:0;font-size:13px;line-height:1.65}.image-bed-upload-copy strong{color:var(--text);font-weight:800}.upload-dropzone{min-height:220px;color:var(--text-soft);text-align:center;cursor:pointer;background:linear-gradient(#ffffffb8,#f8fafcb8),#f8fafce0;border:1px dashed #635bff52;border-radius:16px;align-content:center;place-items:center;gap:10px;padding:24px;transition:border-color .16s,background .16s,transform .16s;display:grid;position:relative}.upload-dropzone:hover,.upload-dropzone.is-dragging{border-color:var(--brand);background:#635bff12;transform:translateY(-1px)}.upload-dropzone.is-disabled{opacity:.84}.upload-dropzone input{opacity:0;pointer-events:none;width:1px;height:1px;position:absolute}.upload-dropzone-icon{width:52px;height:52px;color:var(--brand);background:#635bff1a;border:1px solid #635bff29;border-radius:16px;place-items:center;display:grid}.upload-dropzone strong{color:var(--text);font-size:18px;line-height:1.3}.upload-dropzone span{max-width:360px;color:var(--text-muted);font-size:13px;line-height:1.5}.upload-progress-block,.image-bed-latest,.image-bed-upload-note{background:#635bff0d;border:1px solid #635bff24;border-radius:15px;min-height:72px}.upload-progress-block{gap:10px;padding:14px 16px;display:grid}.upload-progress-head{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.upload-progress-head strong{color:var(--text);font-size:13px;line-height:1.35}.upload-progress-head span{color:var(--text-muted);overflow-wrap:anywhere;font-size:11px;line-height:1.35}.upload-progress-track{background:#94a3b833;border-radius:999px;height:10px;overflow:hidden}.upload-progress-fill{border-radius:inherit;background:linear-gradient(90deg, var(--brand), #10b981);height:100%;transition:width .18s}.image-bed-latest{grid-template-columns:54px minmax(0,1fr) auto;align-items:center;gap:12px;padding:10px;display:grid}.image-bed-latest-preview{background:#f8fafc;border:0;border-radius:13px;width:54px;height:54px;padding:0;overflow:hidden}.image-bed-latest-preview img{object-fit:cover;width:100%;height:100%;display:block}.image-bed-latest-info{gap:3px;min-width:0;display:grid}.image-bed-latest-info span{color:var(--text-muted);font-size:10px;font-weight:850;line-height:1.2}.image-bed-latest-info strong,.image-bed-latest-info code{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.image-bed-latest-info strong{color:var(--text);font-size:13px}.image-bed-latest-info code{color:var(--text-muted);font-size:11px}.image-bed-upload-note{color:var(--text-muted);align-items:center;padding:14px 16px;font-size:13px;line-height:1.5;display:flex}.image-bed-side-stack{gap:14px;display:grid}.image-bed-side-card{border-radius:16px;align-content:start;gap:13px;padding:18px;display:grid}.image-bed-side-card h4,.image-bed-gallery-section h4{color:var(--text);margin:0;font-size:16px;line-height:1.3}.image-bed-side-card p{color:var(--text-muted);margin:0;font-size:12px;line-height:1.6}.image-bed-section-head{justify-content:space-between;align-items:center;gap:10px;display:flex}.image-bed-section-head p{color:var(--text-muted);margin:4px 0 0;font-size:12px;line-height:1.45}.image-bed-status-dot{background:#cbd5e1;border-radius:999px;width:10px;height:10px;box-shadow:0 0 0 4px #cbd5e138}.image-bed-status-dot.is-ok{background:#10b981;box-shadow:0 0 0 4px #10b98129}.image-bed-status-dot.is-warn{background:#f59e0b;box-shadow:0 0 0 4px #f59e0b29}.image-bed-connection-label{color:var(--text);overflow-wrap:anywhere;font-size:15px;line-height:1.4}.image-bed-link-button{color:var(--brand);background:0 0;border:0;padding:0;font-size:12px;font-weight:800}.image-bed-token-actions{grid-template-columns:1fr auto;gap:10px;display:grid}.image-bed-token-hint{color:var(--text-muted);margin:-2px 0 0;font-size:11px;font-weight:650;line-height:1.5}.image-bed-token-hint code{color:var(--text);font-weight:800}.image-bed-token-summary{border:1px solid var(--line);background:#f8fafccc;border-radius:14px;justify-content:space-between;align-items:center;gap:12px;min-height:58px;padding:12px;display:flex}.image-bed-token-summary span{color:var(--text-muted);font-size:10px;font-weight:850;line-height:1.25;display:block}.image-bed-token-summary strong{color:var(--text);margin-top:4px;font-size:14px;line-height:1.35;display:block}.image-bed-target-list{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;display:grid}.image-bed-target-list div{background:#f8fafcd1;border-radius:12px;min-width:0;padding:10px}.image-bed-target-list span{color:var(--text-muted);font-size:10px;font-weight:850;line-height:1.25;display:block}.image-bed-target-list strong{color:var(--text);overflow-wrap:anywhere;margin-top:5px;font-size:12px;line-height:1.35;display:block}.image-bed-gallery-section{border-radius:18px;gap:14px;padding:18px;display:grid}.image-bed-empty{min-height:136px;color:var(--text-muted);text-align:center;background:#f8fafcd1;border-radius:15px;place-items:center;padding:24px;font-size:13px;line-height:1.6;display:grid}.image-bed-results-grid{grid-template-columns:repeat(auto-fill,minmax(132px,150px));align-items:start;gap:12px;display:grid}.image-bed-result-card{border:1px solid var(--line);background:#fff;border-radius:14px;align-content:start;gap:7px;margin:0;display:grid;position:relative;overflow:hidden;box-shadow:0 8px 18px #0f172a0d}.image-bed-preview-button{aspect-ratio:1/.78;background:#f8fafc;border:0;padding:0;position:relative;overflow:hidden}.image-bed-preview-button img{object-fit:cover;width:100%;height:100%;transition:transform .18s;display:block}.image-bed-preview-button:hover img{transform:scale(1.03)}.image-bed-result-card figcaption{gap:3px;min-width:0;padding:0 10px 10px;display:grid}.image-bed-result-card figcaption strong{color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:1.4;overflow:hidden}.image-bed-result-card figcaption span,.image-bed-result-card figcaption code{color:var(--text-muted);overflow-wrap:anywhere;font-size:10px;line-height:1.4}.image-bed-result-actions{gap:6px;display:flex;position:absolute;top:7px;right:7px}.image-bed-card-action{width:30px;height:30px;color:var(--text-soft);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:#ffffffe0;border:1px solid #ffffffb8;border-radius:10px;justify-content:center;align-items:center;display:inline-flex;box-shadow:0 8px 18px #0f172a1a}.image-bed-card-action:hover{color:var(--brand);background:#fff;border-color:#635bff33}.image-bed-card-action.is-danger{color:#ef4444}.image-bed-card-action.is-danger:hover{color:#dc2626;border-color:#ef444438}.image-bed-card-action:disabled{cursor:not-allowed;opacity:.62}.image-bed-load-more{width:100%}.image-bed-help-section{border-radius:16px;padding:0;overflow:hidden}.image-bed-help-section summary{cursor:pointer;color:var(--text);padding:15px 18px;font-size:14px;font-weight:800}.image-bed-help-intro{color:var(--text-muted);margin:0;padding:0 20px 14px;font-size:13px;line-height:1.6}.image-bed-help-intro code{color:var(--text);font-weight:800}.image-bed-steps{color:var(--text-soft);gap:11px;margin:0;padding:0 20px 16px 36px;font-size:13px;line-height:1.6;display:grid}.image-bed-steps li{min-width:0}.image-bed-steps strong,.image-bed-steps code{color:var(--text);font-weight:750}.image-bed-help-facts{border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin:0 20px 16px;display:grid}.image-bed-help-facts div{border-top:1px solid #94a3b829;grid-template-columns:110px minmax(0,1fr);gap:12px;min-width:0;padding:12px 0;display:grid}.image-bed-help-facts div:first-child{border-top:0}.image-bed-help-facts dt{color:var(--text);font-size:12px;font-weight:850;line-height:1.4}.image-bed-help-facts dd{color:var(--text-muted);margin:0;font-size:12px;line-height:1.55}.image-bed-help-facts code{color:var(--text);font-weight:800}.image-bed-help-section .hint{margin:0;padding:0 20px 18px}@media (width<=1120px){.image-bed-workbench{grid-template-columns:1fr}.image-bed-side-stack{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width<=760px){.image-bed-upload-panel,.image-bed-gallery-section{border-radius:16px;padding:16px}.image-bed-upload-copy h2{font-size:23px}.image-bed-side-stack,.image-bed-target-list,.image-bed-token-actions,.image-bed-help-facts div,.image-bed-latest,.image-bed-result-actions{grid-template-columns:1fr}.image-bed-latest-preview{aspect-ratio:1/.62;width:100%;height:auto}}.network-page{gap:18px;display:grid}.network-command-center{box-shadow:var(--shadow);background:linear-gradient(135deg,#fff 0%,#f8fbff 58%,#f4fbf7 100%);border:1px solid #94a3b838;border-radius:20px;grid-template-columns:minmax(0,1fr) 260px;align-items:stretch;gap:18px;padding:22px;display:grid;overflow:hidden}.network-command-center.green{border-color:#22c55e33}.network-command-center.orange{border-color:#f59e0b38}.network-command-center.red{border-color:#ef444438}.network-command-copy{align-content:center;gap:10px;min-width:0;display:grid}.network-eyebrow{width:fit-content;color:var(--brand);align-items:center;gap:7px;font-size:12px;font-weight:850;line-height:1.2;display:inline-flex}.network-command-copy h2{color:var(--text);letter-spacing:0;margin:0;font-size:31px;line-height:1.12}.network-command-copy p{max-width:760px;color:var(--text-muted);margin:0;font-size:13px;line-height:1.65}.network-command-status{flex-wrap:wrap;align-items:center;gap:8px 12px;margin-top:2px;display:flex}.network-command-status>span:not(.network-chip){color:var(--text-muted);align-items:center;gap:6px;font-size:12px;font-weight:720;display:inline-flex}.network-toolbar-status{color:var(--text-soft)}.access-score-card{background:#ffffffc7;border:1px solid #94a3b82e;border-radius:16px;align-content:center;gap:10px;min-width:0;padding:18px;display:grid;box-shadow:inset 0 1px #ffffffe0}.access-score-card>span{color:var(--text-muted);font-size:11px;font-weight:850;line-height:1.2}.access-score-card strong{color:var(--text);letter-spacing:0;font-size:38px;line-height:1}.access-score-card p{color:var(--text-muted);margin:0;font-size:12px;font-weight:700;line-height:1.45}.access-score-bar{background:#94a3b829;border-radius:999px;width:100%;height:8px;position:relative;overflow:hidden}.access-score-bar i{border-radius:inherit;background:linear-gradient(90deg,#22c55e,#3b82f6);min-width:0;max-width:100%;height:100%;display:block}.access-score-card .btn-secondary{width:100%}.network-error{color:var(--red);background:#ef444412;border:1px solid #ef444429;border-radius:12px;margin:0;padding:10px 12px;font-size:12px;font-weight:750;line-height:1.5}.signal-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;display:grid}.signal-card{background:#fff;border:1px solid #94a3b833;border-radius:16px;grid-template-columns:34px minmax(0,1fr);align-content:start;gap:12px;min-width:0;min-height:122px;padding:15px;display:grid;box-shadow:0 10px 22px #0f172a0d}.signal-card-icon{width:34px;height:34px;color:var(--blue);background:var(--blue-soft);border-radius:11px;place-items:center;display:grid}.signal-card span,.network-metric span,.network-block span{color:var(--text-muted);font-size:11px;font-weight:820;line-height:1.2;display:block}.signal-card strong,.network-metric strong,.network-block strong{min-width:0;color:var(--text);overflow-wrap:anywhere;margin-top:6px;font-size:15px;line-height:1.25;display:block}.signal-card p,.network-metric p,.network-block p{color:var(--text-muted);overflow-wrap:anywhere;margin:7px 0 0;font-size:12px;line-height:1.45}.signal-card.tone-green,.network-metric.tone-green,.network-block.tone-green{background:linear-gradient(#fff,#f0fdf4c7);border-color:#22c55e2e}.signal-card.tone-green .signal-card-icon{color:var(--green);background:var(--green-soft)}.signal-card.tone-orange,.network-metric.tone-orange,.network-block.tone-orange{background:linear-gradient(#fff,#fffbebd6);border-color:#f59e0b33}.signal-card.tone-orange .signal-card-icon{color:var(--orange);background:var(--orange-soft)}.signal-card.tone-red,.network-metric.tone-red,.network-block.tone-red{background:linear-gradient(#fff,#fef2f2db);border-color:#ef444433}.signal-card.tone-red .signal-card-icon{color:var(--red);background:var(--red-soft)}.signal-card.tone-slate,.network-metric.tone-slate,.network-block.tone-slate{background:linear-gradient(#fff,#f8fafce6)}.network-workspace{grid-template-columns:minmax(560px,1.16fr) minmax(360px,.84fr);align-items:start;gap:18px;display:grid}.network-section{gap:14px;display:grid}.network-platform-panel{padding:20px}.network-side{gap:18px;min-width:0;display:grid}.platform-summary-strip{flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-left:auto;display:inline-flex}.platform-summary-chip{background:#fff;border:1px solid #94a3b82e;border-radius:12px;gap:3px;min-width:66px;padding:9px 11px;display:grid}.platform-summary-chip strong{color:var(--text);font-size:20px;font-weight:900;line-height:1}.platform-summary-chip span{color:var(--text-muted);font-size:10px;font-weight:820;line-height:1.1}.platform-summary-chip.green{background:#f0fdf4cc;border-color:#22c55e33}.platform-summary-chip.orange{background:#fffbebdb;border-color:#f59e0b33}.platform-summary-chip.red{background:#fef2f2db;border-color:#ef444433}.platform-access-header,.platform-access-row{grid-template-columns:minmax(150px,1.05fr) 108px 92px minmax(140px,1fr);align-items:center;column-gap:16px;display:grid}.platform-access-header{color:var(--text-muted);padding:0 16px;font-size:11px;font-weight:830;line-height:1}.platform-access-list{background:#fff;border:1px solid #94a3b82e;border-radius:15px;gap:0;display:grid;overflow:hidden}.platform-access-row{background:#fff;border-top:1px solid #94a3b821;min-width:0;padding:14px 16px 14px 18px;transition:background .14s;position:relative}.platform-access-row:first-child{border-top:0}.platform-access-row:hover{background:#f8fafcd1}.platform-access-row:before{content:"";background:#94a3b861;border-radius:999px;width:3px;position:absolute;inset:12px auto 12px 0}.platform-access-row.green{border-left:0}.platform-access-row.green:before{background:var(--green)}.platform-access-row.orange{border-left:0}.platform-access-row.orange:before{background:var(--orange)}.platform-access-row.red{border-left:0}.platform-access-row.red:before{background:var(--red)}.platform-access-main{align-items:center;gap:12px;min-width:0;display:flex}.platform-access-main>div{gap:3px;min-width:0;display:grid}.platform-access-main strong{color:var(--text);font-size:15px;font-weight:850;line-height:1.2}.platform-access-main span,.platform-access-result>span,.platform-access-latency span{color:var(--text-muted);overflow-wrap:anywhere;font-size:12px;font-weight:700;line-height:1.35}.platform-access-result,.platform-access-latency{justify-items:start;gap:5px;min-width:0;display:grid}.platform-access-latency strong{color:var(--text);font-size:14px;font-weight:850;line-height:1.2}.platform-access-detail{min-width:0}.platform-access-detail span{color:var(--text-muted);overflow-wrap:anywhere;font-size:12px;font-weight:720;line-height:1.45;display:block}.platform-icon{color:#fff;background:linear-gradient(135deg,#64748b,#334155);border-radius:12px;flex:none;place-items:center;width:38px;height:38px;font-size:13px;font-weight:900;display:grid;box-shadow:inset 0 1px #ffffff47}.platform-icon img{filter:brightness(0)invert();width:19px;height:19px;display:block}.platform-icon.green{background:linear-gradient(135deg,#22c55e,#16a34a)}.platform-icon.orange{background:linear-gradient(135deg,#f59e0b,#d97706)}.platform-icon.red{background:linear-gradient(135deg,#ef4444,#dc2626)}.platform-icon.platform-google{background:conic-gradient(from 35deg,#4285f4,#34a853 32%,#fbbc05 58%,#ea4335 78%,#4285f4)}.platform-icon.platform-chatgpt{background:linear-gradient(135deg,#10a37f,#0f766e 62%,#064e3b)}.platform-icon.platform-claude{background:linear-gradient(135deg,#d97706,#b45309 58%,#78350f)}.platform-icon.platform-youtube{background:linear-gradient(135deg,#f03,#dc2626 58%,#991b1b)}.platform-icon.platform-x{background:linear-gradient(135deg,#020617,#111827 58%,#475569)}.access-badge,.network-chip{white-space:nowrap;border:1px solid #0000;border-radius:999px;align-items:center;gap:6px;width:fit-content;min-height:26px;padding:0 10px;font-size:11px;font-weight:830;line-height:1;display:inline-flex}.access-badge.green,.network-chip.green{color:var(--green);background:#22c55e17;border-color:#22c55e2b}.access-badge.orange,.network-chip.orange{color:var(--orange);background:#f59e0b17;border-color:#f59e0b2e}.access-badge.red,.network-chip.red{color:var(--red);background:#ef444417;border-color:#ef44442e}.access-badge.blue,.network-chip.blue{color:var(--blue);background:#3b82f617;border-color:#3b82f62e}.access-badge.slate,.network-chip.slate{color:var(--text-muted);background:#64748b17;border-color:#64748b29}.network-list{gap:10px;display:grid}.network-list.compact-list{margin-top:2px}.network-list-item{background:#ffffffd1;border:1px solid #94a3b829;border-radius:13px;gap:8px;min-width:0;padding:12px;display:grid}.network-list-item p{color:var(--text-soft);overflow-wrap:anywhere;margin:0;font-size:12px;line-height:1.48}.network-dual{grid-template-columns:1fr;gap:10px;display:grid}.network-three-up{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;display:grid}.network-metric,.network-block{background:#fff;border:1px solid #94a3b82e;border-radius:13px;min-width:0;padding:12px}.network-environment-panel{padding:20px}.dns-chip-row{flex-wrap:wrap;gap:8px;display:flex}.dns-chip{border:1px solid #0000;border-radius:999px;align-items:center;min-height:27px;padding:0 10px;font-size:11px;font-weight:800;display:inline-flex}.dns-chip.green{color:var(--green);background:#22c55e14;border-color:#22c55e29}.dns-chip.orange{color:var(--orange);background:#f59e0b14;border-color:#f59e0b29}.network-empty-state{min-height:132px;color:var(--text-muted);text-align:center;background:#f8fafcb8;border:1px dashed #94a3b847;border-radius:14px;place-items:center;padding:18px;font-size:13px;font-weight:760;display:grid}.network-page .section-head.compact h3{font-size:18px}.network-page .section-head.compact{margin-bottom:0}@media (width<=1180px){.network-command-center,.signal-grid,.network-workspace{grid-template-columns:1fr}.platform-summary-strip{justify-content:flex-start;margin-left:0}}@media (width<=860px){.network-command-center{padding:18px}.network-command-copy h2{font-size:25px}.platform-access-header{display:none}.platform-access-row{grid-template-columns:1fr;align-items:start;row-gap:10px;padding:14px 15px 14px 18px}.network-dual,.network-three-up{grid-template-columns:1fr}}.table-scroller{border:1px solid var(--line);background:#fff;border-radius:14px;overflow-x:auto}table{border-collapse:collapse;width:100%;min-width:720px}th,td{border-bottom:1px solid var(--line);text-align:left;vertical-align:top;color:var(--text-soft);padding:13px 12px;font-size:13px}th{color:var(--text-muted);background:#f8fafc;font-size:12px;font-weight:800}tbody tr:last-child td{border-bottom:0}td code{color:var(--text)}.method-pill,.status-pill{white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;min-height:24px;padding:0 8px;font-size:11px;font-weight:800;display:inline-flex}.method-pill{color:#334155;background:#f1f5f9}.method-post{color:#4f46e5;background:#635bff1a}.method-get,.status-pill.is-ok{color:#047857;background:#22c55e1f}.status-pill.is-error{color:#dc2626;background:#ef44441f}.table-footer{color:var(--text-muted);margin-top:10px;font-size:12px}.log-toolbar{flex-wrap:wrap;gap:10px;margin-bottom:16px;display:flex}.log-search{flex:280px}.filter-chip{border:1px solid var(--line);background:#fff;border-radius:12px;align-items:center;gap:8px;min-height:40px;padding:0 12px;display:inline-flex}.filter-chip select{color:var(--text-soft);background:0 0;border:0;outline:none;font-size:13px;font-weight:700}.table-scroller tbody tr{cursor:pointer}.table-scroller tbody tr.is-selected{background:#635bff0a}.log-detail-panel{border:1px solid var(--line);background:#fffffff0;border-radius:16px;margin-top:16px;padding:18px 20px}.log-detail-head{justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:14px;display:flex}.log-detail-head h3{margin:0}.log-detail-head p{color:var(--text-muted);margin:6px 0 0;font-size:13px}.log-detail-grid{grid-template-columns:minmax(220px,.7fr) minmax(0,1.3fr);gap:14px;display:grid}.log-detail-meta{gap:10px;display:grid}.log-detail-meta div{gap:4px;display:grid}.log-detail-meta span{color:var(--text-muted);font-size:12px}.log-detail-meta strong{color:var(--text-soft);font-size:13px}.log-detail-pre{min-height:220px;max-height:320px}.switch-line{color:var(--text-soft);align-items:flex-start;gap:10px;font-size:13px;font-weight:700;line-height:1.5;display:flex}.settings-page{gap:18px;display:grid}.settings-page-head{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.settings-page-head-actions-only{justify-content:flex-end}.settings-page-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.codex-provider-button{border-color:#2563eb3d;transition:background .16s,border-color .16s,color .16s,box-shadow .16s}.codex-provider-button.is-inactive{color:#1d4ed8;background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#2563eb52;box-shadow:inset 0 1px #fffc,0 6px 14px #2563eb14}.codex-provider-button.is-inactive:hover{background:linear-gradient(135deg,#dbeafe,#bfdbfe);border-color:#2563eb6b}.codex-provider-button.is-active{color:#b91c1c;background:linear-gradient(135deg,#fff1f2,#fee2e2);border-color:#ef444447;box-shadow:inset 0 1px #fffc,0 6px 14px #ef444414}.codex-provider-button.is-active:hover{background:linear-gradient(135deg,#ffe4e6,#fecaca);border-color:#ef444461}.codex-provider-button.is-busy{color:var(--text-muted);border-color:var(--line);background:#f8fafc}.settings-page-footer-actions{justify-content:flex-end;margin-top:2px}.settings-grid{grid-template-columns:repeat(4,minmax(220px,1fr));gap:14px;display:grid}.settings-section{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:16px;gap:13px;padding:18px 20px;display:grid}.settings-section h4{margin:0;font-size:16px}.codex-provider-section{grid-column:1/-1;gap:16px}.codex-provider-head{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.codex-provider-head .hint{max-width:720px;margin:8px 0 0}.codex-provider-mode-row{flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.codex-provider-mode-copy{flex:420px;gap:8px;min-width:0;display:grid}.codex-provider-mode-title{color:var(--text);font-size:13px;font-weight:850}.codex-provider-mode-copy .hint{max-width:720px;margin:0}.codex-provider-controls{grid-template-columns:284px minmax(320px,1fr) auto;align-items:end;gap:14px;display:grid}.codex-mode-toggle,.codex-provider-mode-toggle{border:1px solid var(--line);background:#f1f5f9;border-radius:12px;flex:0 320px;grid-template-columns:1fr 1fr;gap:6px;min-width:320px;min-height:42px;padding:5px;display:grid}.codex-mode-option,.codex-provider-mode-option{min-width:0;color:var(--text-soft);font:inherit;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:9px;justify-content:center;align-items:center;gap:7px;font-size:13px;font-weight:850;display:inline-flex}.codex-mode-option.is-active,.codex-provider-mode-option.is-active{color:#1d4ed8;box-shadow:var(--shadow-sm);background:#fff;border-color:#bfdbfe}.codex-url-field{min-width:0}.codex-url-input{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px}.codex-url-input[readonly]{color:var(--text-soft);background:#f8fafc}.codex-provider-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.codex-provider-meta-strip{border:1px solid var(--line);background:#f8fafc;border-radius:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));display:grid;overflow:hidden}.codex-provider-meta-strip>div{border-right:1px solid var(--line);gap:8px;min-width:0;padding:14px 16px;display:grid}.codex-provider-meta-strip>div:last-child{border-right:0}.codex-provider-meta-strip span{color:var(--text-muted);font-size:12px;font-weight:850}.codex-provider-meta-strip code,.codex-provider-section .hint code{color:var(--text);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.codex-provider-meta-strip strong{color:#9a3412;font-size:12px;line-height:1.45}.codex-provider-meta-strip .is-warning{background:#fff7ed}.free-image-section{background:snow;border-color:#fecaca}.free-image-section h4{color:#b91c1c}.free-image-warning{color:#991b1b;background:#fef2f2;border:1px solid #fca5a5;border-radius:10px;margin:0;padding:11px 12px;font-size:13px;line-height:1.6}.free-image-warning strong{color:#7f1d1d;margin-right:3px}.auto-switch-exclusion-section{grid-column:1/-1}.auto-switch-exclusion-head{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.auto-switch-exclusion-head .hint{max-width:720px;margin:8px 0 0}.auto-switch-counts{flex-wrap:wrap;justify-content:flex-end;gap:8px;display:flex}.count-pill,.auto-switch-state-pill{border:1px solid var(--line);white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;min-height:28px;padding:0 12px;font-size:12px;font-weight:800;display:inline-flex}.count-pill.is-included,.auto-switch-state-pill.is-included{color:#047857;background:#ecfdf5;border-color:#bbf7d0}.count-pill.is-excluded,.auto-switch-state-pill.is-excluded{color:#c2410c;background:#fff7ed;border-color:#fed7aa}.auto-switch-search{border:1px solid var(--line);height:42px;color:var(--text-muted);background:#f8fafc;border-radius:10px;align-items:center;gap:10px;padding:0 13px;display:flex}.auto-switch-search input{width:100%;min-width:0;color:var(--text);font:inherit;background:0 0;border:0;outline:0}.auto-switch-profile-list{gap:10px;max-height:342px;padding-right:4px;display:grid;overflow:auto}.auto-switch-profile-row{border:1px solid var(--line);cursor:pointer;background:#fbfcff;border-radius:12px;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:14px;min-height:70px;padding:14px 16px;display:grid}.auto-switch-profile-row.is-excluded{background:#fff7ed;border-color:#fed7aa}.auto-switch-profile-main{gap:5px;min-width:0;display:grid}.auto-switch-profile-main strong,.auto-switch-profile-main span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.auto-switch-profile-main strong{color:var(--text);font-size:14px}.auto-switch-profile-main span{color:var(--text-muted);font-size:12px;line-height:1.4}.auto-switch-empty{border:1px dashed var(--line-strong);min-height:90px;color:var(--text-muted);background:#f8fafc;border-radius:12px;place-items:center;font-size:13px;display:grid}@media (width<=860px){.codex-provider-head,.codex-provider-mode-row,.auto-switch-exclusion-head,.auto-switch-profile-row{grid-template-columns:1fr}.codex-provider-head,.codex-provider-mode-row,.auto-switch-exclusion-head{display:grid}.codex-provider-mode-row{gap:10px}.codex-provider-controls,.codex-provider-meta-strip{grid-template-columns:1fr}.codex-provider-mode-toggle{width:100%;min-width:0}.codex-provider-actions{justify-content:flex-start}.codex-provider-meta-strip>div{border-right:0;border-bottom:1px solid var(--line)}.codex-provider-meta-strip>div:last-child{border-bottom:0}.auto-switch-counts{justify-content:flex-start}.auto-switch-profile-row{align-items:start}.auto-switch-state-pill{justify-self:flex-start}}@media (width<=1380px){.main-grid{grid-template-columns:1fr}.right-column{grid-template-columns:minmax(0,1fr)}.tester-card{order:-1}}@media (width<=1120px){.app-shell{grid-template-columns:1fr;width:calc(100vw - 20px);padding:10px}.sidebar{max-height:none;position:relative;top:0}.nav{grid-template-columns:repeat(6,minmax(max-content,1fr));padding-bottom:2px;overflow-x:auto}.nav-item{justify-content:center}.sidebar-status{display:none}.topbar{grid-template-columns:1fr}.top-actions{justify-content:flex-start}}@media (width<=900px){.app-shell{gap:12px;padding:0}.sidebar,.card,.trend-card,.log-table-wrap,.update-panel{border-left:0;border-right:0;border-radius:0}.sidebar{padding:16px 12px}.brand span,.sidebar-status{display:none}.nav{grid-template-columns:none;grid-auto-columns:max-content;grid-auto-flow:column}.nav-item{width:auto}.main{gap:12px}.topbar{padding:0 12px}.page-title h1{font-size:25px}.top-actions{flex-wrap:nowrap;padding-bottom:4px;overflow-x:auto}.summary-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;padding:0 12px}.summary-card{min-height:82px;padding:14px}.summary-card strong{font-size:19px}.main-grid{gap:12px}.section-head,.update-panel,.settings-drawer-footer{display:grid}.update-panel{grid-template-columns:auto minmax(0,1fr);align-items:start}.update-actions{grid-column:1/-1;grid-template-columns:1fr;justify-content:stretch;display:grid}.update-panel .btn-primary,.update-panel .btn-secondary{justify-content:center;width:100%}.section-actions,.button-row,.example-row{justify-content:flex-start}.filter-row{display:flex}.filter-row .control{flex:100%;min-width:0}.account-grid,.account-grid.profile-count-1,.account-grid.profile-count-2,.account-grid.profile-count-3,.account-grid.profile-count-many,.meta-grid{grid-template-columns:1fr}.account-card{border-radius:14px;padding:16px}.account-head{flex-direction:column;align-items:stretch}.account-select{width:max-content;margin-top:0}.compact-grid{grid-template-columns:1fr}.account-actions{display:flex}.tester-textarea{min-height:150px}.pre{max-height:320px}.modal-backdrop,.drawer-backdrop{padding:0}.modal-card,.settings-drawer{border-radius:0;width:100%;height:100%;max-height:none}.modal-body,.settings-drawer-body,.settings-drawer-head,.settings-drawer-footer{padding:16px}}@media (width<=460px){.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.btn-primary,.btn-secondary,.btn-danger{min-height:40px;padding:0 12px}.card,.trend-card,.log-table-wrap{padding:16px 12px}}@media (width<=1120px){body{min-width:0}.app-shell{grid-template-columns:1fr;gap:14px;width:calc(100vw - 24px);padding:0}.sidebar{border-radius:24px;gap:14px;max-height:none;padding:22px 18px;position:relative;top:0}.brand span,.sidebar-status{display:none}.nav{grid-template-columns:repeat(5,minmax(max-content,1fr));grid-auto-columns:max-content;grid-auto-flow:column;padding-bottom:2px;overflow-x:auto}.nav-item{justify-content:center;width:auto}.topbar{grid-template-columns:1fr;padding:0}.page-title p{max-width:680px}.top-actions{flex-wrap:wrap;justify-content:flex-start;overflow:visible}.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr));padding:0}.overview-grid,.tester-route-grid,.main-grid{grid-template-columns:1fr}.sidebar,.card,.trend-card,.log-table-wrap,.update-panel{border-left:1px solid var(--line);border-right:1px solid var(--line);border-radius:20px}.launch-page{grid-template-columns:1fr}.launch-visual{min-height:420px}.account-grid.profile-count-2{grid-template-columns:repeat(2,minmax(340px,1fr))}.account-grid.profile-count-3{grid-template-columns:repeat(3,minmax(320px,1fr))}.account-grid.profile-count-many{grid-template-columns:repeat(auto-fit,minmax(340px,1fr))}.meta-grid,.compact-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@@ -1,5 +0,0 @@
1
- import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./earth-DFdZaQIi.js";import{t as a}from"./refresh-cw-CAAH2rqe.js";import{t as o}from"./search-B2hz41D3.js";import{f as s,i as c,m as l,o as u,s as d,w as f}from"./profiles-C5SmQvju.js";import{_ as p,p as m,r as h}from"./index-CCiBaGwU.js";var g=t(`monitor-cog`,[[`path`,{d:`M12 17v4`,key:`1riwvh`}],[`path`,{d:`m14.305 7.53.923-.382`,key:`1mlnsw`}],[`path`,{d:`m15.228 4.852-.923-.383`,key:`82mpwg`}],[`path`,{d:`m16.852 3.228-.383-.924`,key:`ln4sir`}],[`path`,{d:`m16.852 8.772-.383.923`,key:`1dejw0`}],[`path`,{d:`m19.148 3.228.383-.924`,key:`192kgf`}],[`path`,{d:`m19.53 9.696-.382-.924`,key:`fiavlr`}],[`path`,{d:`m20.772 4.852.924-.383`,key:`1j8mgp`}],[`path`,{d:`m20.772 7.148.924.383`,key:`zix9be`}],[`path`,{d:`M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7`,key:`1tnzv8`}],[`path`,{d:`M8 21h8`,key:`1ev6f3`}],[`circle`,{cx:`18`,cy:`6`,r:`3`,key:`1h7g24`}]]),ee=t(`plug-zap`,[[`path`,{d:`M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z`,key:`goz73y`}],[`path`,{d:`m2 22 3-3`,key:`19mgm9`}],[`path`,{d:`M7.5 13.5 10 11`,key:`7xgeeb`}],[`path`,{d:`M10.5 16.5 13 14`,key:`10btkg`}],[`path`,{d:`m18 3-4 4h6l-4 4`,key:`16psg9`}]]),te=t(`unplug`,[[`path`,{d:`m19 5 3-3`,key:`yk6iyv`}],[`path`,{d:`m2 22 3-3`,key:`19mgm9`}],[`path`,{d:`M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z`,key:`goz73y`}],[`path`,{d:`M7.5 13.5 10 11`,key:`7xgeeb`}],[`path`,{d:`M10.5 16.5 13 14`,key:`10btkg`}],[`path`,{d:`m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z`,key:`1snsnr`}]]),_=e(n(),1),v=r();function y(e){return e===`ai-zero-token`?`ai-zero-token`:`openai`}function b(e){return e===`openai`?`openai`:`AI Zero Token`}function x(e){return e===`openai`?`保留 Codex 原生历史`:`新的 provider 历史`}function ne(e){return e===`openai`?`openai_base_url`:`[model_providers.ai-zero-token]`}function S(e){let t=e.trim();if(!t)throw Error(`请填写 Codex 网关 URL。`);/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(t)||(t=`http://${t}`);let n;try{n=new URL(t)}catch{throw Error(`Codex 网关 URL 格式错误,请填写 http(s) 地址或 IP:端口。`)}if(n.protocol!==`http:`&&n.protocol!==`https:`)throw Error(`Codex 网关 URL 只支持 http 或 https。`);n.hash=``,n.search=``;let r=n.pathname.replace(/\/+$/g,``);return!r||r===`/`||r===`/v1`?n.pathname=`/codex/v1`:r.endsWith(`/codex`)?n.pathname=`${r}/v1`:n.pathname=r,n.toString().replace(/\/+$/g,``)}function C(e){try{return S(e)}catch{return e.trim().replace(/\/+$/g,``)}}function w(e){return e?.codexBaseUrl||`http://127.0.0.1:8787/codex/v1`}function re(e){return{defaultModel:e.settings.defaultModel,proxyEnabled:e.settings.networkProxy.enabled,proxyUrl:e.settings.networkProxy.url,proxyNoProxy:e.settings.networkProxy.noProxy||`localhost,127.0.0.1,::1`,autoSwitchEnabled:e.settings.autoSwitch.enabled,autoSwitchExcludedProfileIds:e.settings.autoSwitch.excludedProfileIds||[],quotaSyncConcurrency:String(e.settings.runtime?.quotaSyncConcurrency||3),freeAccountWebGenerationEnabled:!!e.settings.image?.freeAccountWebGenerationEnabled,serverPort:String(e.settings.server.port||8787)}}function ie(e){return[l(e,!0),e.email||``,e.accountId,e.profileId,c(e)].join(` `).toLowerCase()}function T(e){let[t,n]=(0,_.useState)({defaultModel:``,proxyEnabled:!1,proxyUrl:``,proxyNoProxy:`localhost,127.0.0.1,::1`,autoSwitchEnabled:!1,autoSwitchExcludedProfileIds:[],quotaSyncConcurrency:`3`,freeAccountWebGenerationEnabled:!1,serverPort:`8787`}),[r,T]=(0,_.useState)(`local`),[E,D]=(0,_.useState)(`http://127.0.0.1:8787/codex/v1`),[O,ae]=(0,_.useState)(!1),[k,A]=(0,_.useState)(()=>new Set),[j,oe]=(0,_.useState)(``),[M,N]=(0,_.useState)(`openai`),[P,se]=(0,_.useState)(!1),F=k.size>0;(0,_.useEffect)(()=>{!e.config||F||n(re(e.config))},[e.config,F]),(0,_.useEffect)(()=>{if(!e.config||O)return;let t=w(e.config),n=e.config.codex.gatewayProvider?.baseUrl;D(n||t),T(n&&C(n)!==C(t)?`remote`:`local`)},[e.config,O]),(0,_.useEffect)(()=>{!e.config||P||N(y(e.config.codex.gatewayProvider?.providerId))},[e.config,P]);function I(e){n(t=>({...t,...e})),A(t=>{let n=new Set(t);for(let t of Object.keys(e))n.add(t);return n})}function ce(e,n){let r=new Set(t.autoSwitchExcludedProfileIds);n?r.add(e):r.delete(e),I({autoSwitchExcludedProfileIds:Array.from(r)})}function L(t){let n=w(e.config);ae(!0),T(t),t===`local`?D(n):E.trim()||D(n)}function le(){return r===`local`?w(e.config):E}function R(e){se(!0),N(e)}let z=(0,_.useMemo)(()=>new Set(t.autoSwitchExcludedProfileIds),[t.autoSwitchExcludedProfileIds]),B=(0,_.useMemo)(()=>{let t=j.trim().toLowerCase();return(e.config?.profiles||[]).filter(e=>!t||ie(e).includes(t))},[j,e.config?.profiles]),ue=e.config?.profiles.length||0,V=(e.config?.profiles||[]).filter(e=>z.has(e.profileId)).length,de=Math.max(0,ue-V);async function H(n){let r=(...e)=>e.some(e=>k.has(e)),i=Number.parseInt(t.serverPort,10);if(r(`serverPort`)&&(!Number.isInteger(i)||i<1||i>65535)){e.setStatus(`端口必须是 1 到 65535 之间的整数。`);return}let a=Number.parseInt(t.quotaSyncConcurrency,10);if(r(`quotaSyncConcurrency`)&&(!Number.isInteger(a)||a<1||a>32)){e.setStatus(`全局额度刷新并发数必须是 1 到 32 之间的整数。`);return}let o={};r(`defaultModel`)&&(o.defaultModel=t.defaultModel),r(`proxyEnabled`,`proxyUrl`,`proxyNoProxy`)&&(o.networkProxy={enabled:t.proxyEnabled,url:t.proxyUrl,noProxy:t.proxyNoProxy}),r(`autoSwitchEnabled`,`autoSwitchExcludedProfileIds`)&&(o.autoSwitch={},r(`autoSwitchEnabled`)&&(o.autoSwitch.enabled=t.autoSwitchEnabled),r(`autoSwitchExcludedProfileIds`)&&(o.autoSwitch.excludedProfileIds=t.autoSwitchExcludedProfileIds)),r(`quotaSyncConcurrency`)&&(o.runtime={quotaSyncConcurrency:a}),r(`freeAccountWebGenerationEnabled`)&&(o.image={freeAccountWebGenerationEnabled:t.freeAccountWebGenerationEnabled}),r(`serverPort`)&&(o.server={port:i});let s=n?.restart?`restart`:`settings`;e.setBusy(s);try{let t=await m(`/_gateway/admin/settings`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:f(o)});e.setConfig(t),A(new Set),n?.restart?(e.setStatus(`设置已保存,正在重启本地网关...`),await m(`/_gateway/admin/restart`,{method:`POST`}),e.setStatus(`本地网关正在重启,页面会自动恢复。`)):e.setStatus(`设置已保存。`)}catch(t){e.setStatus(h(t))}finally{e.setBusy(null)}}async function fe(){e.setBusy(`proxy`);try{let n=await m(`/_gateway/admin/settings/proxy-test`,{method:`POST`,headers:{"Content-Type":`application/json`},body:f({networkProxy:{enabled:t.proxyEnabled,url:t.proxyUrl,noProxy:t.proxyNoProxy}})});e.setStatus(`代理测试通过: HTTP ${n.status},耗时 ${n.elapsedMs} ms。`)}catch(t){e.setStatus(`代理测试失败: ${h(t)}`)}finally{e.setBusy(null)}}async function pe(){e.setBusy(`models`);try{let t=await m(`/_gateway/models/refresh`,{method:`POST`});await e.refreshConfig({silent:!0});let n=t.catalog?.modelCount??0;e.setStatus(n>0?`Codex 模型列表已从网络同步,共 ${n} 个。`:`Codex 模型列表已从网络同步。`)}catch(t){e.setStatus(h(t))}finally{e.setBusy(null)}}async function U(t){if(t.config?.codexRestartSupported&&window.confirm(t.confirmMessage)){e.setStatus(t.restartingStatus);try{await m(`/_gateway/admin/desktop/restart-codex`,{method:`POST`}),e.setStatus(t.restartedStatus)}catch(n){e.setStatus(`${t.failedStatusPrefix}: ${h(n)}`)}return}e.setStatus(t.deferStatus)}async function me(){e.setBusy(`codex-provider`);try{let t=M,n=b(t),r=S(le()),i=e.config?.codex.gatewayProvider?.baseUrl,a=y(e.config?.codex.gatewayProvider?.providerId),o=!!(e.config?.codex.gatewayProvider?.active&&a!==t),s=!!(e.config?.codex.gatewayProvider?.active&&i&&C(i)!==r);if(e.config?.codex.gatewayProvider?.active&&!s&&!o){let r=await m(`/_gateway/admin/codex/remove-provider`,{method:`POST`,headers:{"Content-Type":`application/json`},body:f({providerId:t})});r.config&&e.setConfig(r.config),r.codexProvider.removed?await U({config:r.config??e.config,confirmMessage:`Codex ${n} 接管已解除,是否现在重启 Codex 客户端?\n\nCodex 通常在启动时读取本机 config.toml,重启后会回到原本的 Codex 配置。`,deferStatus:`已解除 ${n} 接管。重启 Codex 后会回到原本的 Codex 配置。`,restartingStatus:`正在重启 Codex 客户端...`,restartedStatus:`已解除 ${n} 接管,并已重启 Codex 客户端。`,failedStatusPrefix:`已解除 ${n} 接管,但重启 Codex 失败`}):e.setStatus(`未发现当前受管的 Codex provider 配置。`);return}let c=!!e.config?.codex.gatewayProvider?.active,l=await m(`/_gateway/admin/codex/configure-provider`,{method:`POST`,headers:{"Content-Type":`application/json`},body:f({baseUrl:r,providerId:t})});l.config&&e.setConfig(l.config);let u=l.codexProvider.historyMigration?.migratedCount||0,d=u>0?`,已迁移 ${u} 条历史记录`:``;await U({config:l.config??e.config,confirmMessage:t===`openai`?`Codex 接管将使用 openai 历史记录模式,是否现在重启 Codex 客户端?
2
-
3
- 重启后请求仍会走 AI Zero Token 网关,历史记录会继续归在 Codex 原生 openai provider 下。`:`Codex 接管将切换到 AI Zero Token 新 provider,是否现在重启 Codex 客户端?
4
-
5
- 重启后请求仍会走 AI Zero Token 网关,历史记录会归在新的 AI Zero Token provider 下。`,deferStatus:`${c?`已更新`:`已写入`} ${n} 接管配置:${l.codexProvider.baseUrl}${d}。重启 Codex 后生效。`,restartingStatus:`正在重启 Codex 客户端...`,restartedStatus:`${c?`已更新`:`已接管`} ${n} 请求,并已重启 Codex 客户端。`,failedStatusPrefix:`${c?`已更新`:`已接管`} ${n} 请求,但重启 Codex 失败`})}catch(t){e.setStatus(h(t))}finally{e.setBusy(null)}}let W=y(e.config?.codex.gatewayProvider?.providerId),G=b(W),K=ne(M),q=!!e.config?.codex.gatewayProvider?.active,J=e.busy===`codex-provider`,Y=w(e.config),X=C(r===`local`?Y:E),Z=e.config?.codex.gatewayProvider?.baseUrl||``,Q=!!(q&&Z&&C(Z)!==X),$=!!(q&&W!==M),he=[`btn-secondary`,`codex-provider-button`,J?`is-busy`:q&&!Q&&!$?`is-active`:`is-inactive`].join(` `),ge=J?`处理中`:q&&!Q&&!$?`解除 Codex 接管`:q?`更新接管配置`:`写入并接管`,_e=q?G:`未接管`,ve=q?`is-included`:`is-excluded`;return(0,v.jsxs)(`section`,{className:`settings-page`,children:[(0,v.jsx)(`div`,{className:`settings-page-head settings-page-head-actions-only`,children:(0,v.jsx)(`div`,{className:`settings-page-actions`,children:(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:pe,disabled:e.busy===`models`,children:[e.busy===`models`?(0,v.jsx)(p,{className:`spin`,size:16}):(0,v.jsx)(a,{size:16}),`同步 Codex 模型`]})})}),(0,v.jsxs)(`div`,{className:`settings-grid`,children:[(0,v.jsxs)(`section`,{className:`settings-section codex-provider-section`,children:[(0,v.jsxs)(`div`,{className:`codex-provider-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`Codex 请求接管`}),(0,v.jsx)(`p`,{className:`hint`,children:`默认使用 openai 保留 Codex 原生历史;也可以切到 AI Zero Token,写入新的 provider 历史分组。接管地址既可以是本机网关,也可以是远程网关 URL。`})]}),(0,v.jsx)(`span`,{className:`count-pill ${ve}`,children:_e})]}),(0,v.jsxs)(`div`,{className:`codex-provider-mode-row`,children:[(0,v.jsxs)(`div`,{className:`codex-provider-mode-copy`,children:[(0,v.jsx)(`div`,{className:`codex-provider-mode-title`,children:`历史记录模式`}),(0,v.jsxs)(`p`,{className:`hint`,children:[b(M),` · `,x(M)]})]}),(0,v.jsxs)(`div`,{className:`codex-provider-mode-toggle`,role:`group`,"aria-label":`历史记录模式`,children:[(0,v.jsx)(`button`,{className:`codex-provider-mode-option ${M===`openai`?`is-active`:``}`,type:`button`,onClick:()=>R(`openai`),children:`openai`}),(0,v.jsx)(`button`,{className:`codex-provider-mode-option ${M===`ai-zero-token`?`is-active`:``}`,type:`button`,onClick:()=>R(`ai-zero-token`),children:`AI Zero Token`})]})]}),(0,v.jsxs)(`div`,{className:`codex-provider-controls`,children:[(0,v.jsxs)(`div`,{className:`codex-mode-toggle`,role:`group`,"aria-label":`Codex 网关模式`,children:[(0,v.jsxs)(`button`,{className:`codex-mode-option ${r===`local`?`is-active`:``}`,type:`button`,onClick:()=>L(`local`),children:[(0,v.jsx)(g,{size:16}),`本机网关`]}),(0,v.jsxs)(`button`,{className:`codex-mode-option ${r===`remote`?`is-active`:``}`,type:`button`,onClick:()=>L(`remote`),children:[(0,v.jsx)(i,{size:16}),`远程网关`]})]}),(0,v.jsxs)(`label`,{className:`field codex-url-field`,children:[(0,v.jsx)(`span`,{children:`Codex 网关 URL`}),(0,v.jsx)(`input`,{className:`input codex-url-input`,value:r===`local`?Y:E,onChange:e=>{ae(!0),T(`remote`),D(e.target.value)},placeholder:`http://192.168.1.10:8787/codex/v1`,readOnly:r===`local`})]}),(0,v.jsxs)(`div`,{className:`codex-provider-actions`,children:[(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>L(`local`),children:[(0,v.jsx)(g,{size:16}),`使用本机地址`]}),(0,v.jsxs)(`button`,{className:he,type:`button`,onClick:me,disabled:J,children:[J?(0,v.jsx)(p,{className:`spin`,size:16}):q&&!Q?(0,v.jsx)(te,{size:16}):(0,v.jsx)(ee,{size:16}),ge]})]})]}),(0,v.jsxs)(`p`,{className:`hint`,children:[`可直接输入 IP:端口,系统会自动补全为 http://IP:端口/codex/v1。当前将写入 `,(0,v.jsx)(`code`,{children:K}),`:`,(0,v.jsx)(`code`,{children:X||`-`})]}),(0,v.jsxs)(`div`,{className:`codex-provider-meta-strip`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`配置文件`}),(0,v.jsx)(`code`,{children:e.config?.codex.gatewayProvider.path||`~/.codex/config.toml`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`当前状态`}),(0,v.jsx)(`code`,{children:q?`${G} · ${x(W)}`:`未接管`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`写入目标`}),(0,v.jsx)(`code`,{children:K})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`接管地址`}),(0,v.jsx)(`code`,{children:Z||`未写入受管配置`})]}),(0,v.jsxs)(`div`,{className:`is-warning`,children:[(0,v.jsx)(`span`,{children:`远程网关提示`}),(0,v.jsx)(`strong`,{children:`远程请求会消耗对方网关机器上保存的账号额度。`})]})]})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`模型`}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`默认文本模型`}),(0,v.jsx)(`select`,{className:`control`,value:t.defaultModel,onChange:e=>I({defaultModel:e.target.value}),children:(e.config?.models||[]).map(e=>(0,v.jsx)(`option`,{value:e.id,children:e.id},e.id))})]}),(0,v.jsxs)(`p`,{className:`hint`,children:[`模型列表来源:`,e.config?.modelCatalog.source||`-`,`,共 `,e.config?.modelCatalog.modelCount||0,` 个。`]})]}),(0,v.jsxs)(`section`,{className:`settings-section free-image-section`,children:[(0,v.jsx)(`h4`,{children:`Free 账号生图`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:t.freeAccountWebGenerationEnabled,onChange:e=>I({freeAccountWebGenerationEnabled:e.target.checked})}),(0,v.jsx)(`span`,{children:`允许 Free 账号使用 ChatGPT 网页链路生图`})]}),(0,v.jsx)(`p`,{className:`hint`,children:`关闭时,Free 账号生图会继续走原先 Codex Responses 图片工具链路,由上游决定是否可用。`}),(0,v.jsxs)(`p`,{className:`free-image-warning`,children:[(0,v.jsx)(`strong`,{children:`封号风险:`}),`该能力不是官方 API 标准流程,使用 Free 账号生图存在账号风控或封号风险。`,(0,v.jsx)(`strong`,{children:`额度较少:`}),`Free 额度通常较少,当前经验值大约 8 张,实际以上游账号为准。`]})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`上游代理`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:t.proxyEnabled,onChange:e=>I({proxyEnabled:e.target.checked})}),(0,v.jsx)(`span`,{children:`启用 OAuth、模型刷新和接口转发代理`})]}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`代理地址`}),(0,v.jsx)(`input`,{className:`input`,value:t.proxyUrl,onChange:e=>I({proxyUrl:e.target.value}),placeholder:`http://127.0.0.1:7890`})]}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`No Proxy`}),(0,v.jsx)(`input`,{className:`input`,value:t.proxyNoProxy,onChange:e=>I({proxyNoProxy:e.target.value})})]}),(0,v.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:fe,disabled:e.busy===`proxy`,children:`测试代理`})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`端口`}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`网关端口`}),(0,v.jsx)(`input`,{className:`input`,inputMode:`numeric`,type:`number`,min:1,max:65535,value:t.serverPort,onChange:e=>I({serverPort:e.target.value})})]}),(0,v.jsx)(`p`,{className:`hint`,children:`修改后重启本地网关生效,桌面窗口不会退出。若端口被占用,启动时会自动顺延到下一个可用端口。`})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`账号运行策略`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:t.autoSwitchEnabled,onChange:e=>I({autoSwitchEnabled:e.target.checked})}),(0,v.jsx)(`span`,{children:`当前 API 账号额度耗尽后自动切换到下一个仍有额度的账号`})]}),(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`全局额度刷新并发数`}),(0,v.jsx)(`input`,{className:`input`,inputMode:`numeric`,max:32,min:1,type:`number`,value:t.quotaSyncConcurrency,onChange:e=>I({quotaSyncConcurrency:e.target.value})})]}),(0,v.jsx)(`p`,{className:`hint`,children:`手动刷新全部账号额度时使用,默认 3。账号很多可以调高,遇到限流或失败增多时调低。`}),(0,v.jsx)(`p`,{className:`hint`,children:e.status})]}),(0,v.jsxs)(`section`,{className:`settings-section auto-switch-exclusion-section`,children:[(0,v.jsxs)(`div`,{className:`auto-switch-exclusion-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`不参与自动轮换名单`}),(0,v.jsx)(`p`,{className:`hint`,children:`这些账号不会被自动切换选中,也不会在自己额度耗尽时触发自动切走;仍可在账号页手动应用到网关或 Codex。`})]}),(0,v.jsxs)(`div`,{className:`auto-switch-counts`,"aria-label":`自动轮换账号统计`,children:[(0,v.jsxs)(`span`,{className:`count-pill is-included`,children:[`参与 `,de,` 个`]}),(0,v.jsxs)(`span`,{className:`count-pill is-excluded`,children:[`不参与 `,V,` 个`]})]})]}),(0,v.jsxs)(`label`,{className:`auto-switch-search`,children:[(0,v.jsx)(o,{size:16}),(0,v.jsx)(`input`,{value:j,onChange:e=>oe(e.target.value),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,v.jsx)(`div`,{className:`auto-switch-profile-list`,children:B.length===0?(0,v.jsx)(`div`,{className:`auto-switch-empty`,children:`还没有匹配的账号。`}):B.map(t=>{let n=z.has(t.profileId),r=s(t),i=!!(e.config?.codex.accountId&&e.config.codex.accountId===t.accountId),a=u(t)?`登录不可用`:d(t)?`额度耗尽`:``;return(0,v.jsxs)(`label`,{className:`auto-switch-profile-row ${n?`is-excluded`:``}`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:n,onChange:e=>ce(t.profileId,e.target.checked)}),(0,v.jsxs)(`span`,{className:`auto-switch-profile-main`,children:[(0,v.jsx)(`strong`,{children:l(t,e.showEmails)}),(0,v.jsxs)(`span`,{children:[c(t),` · `,r.label,t.isActive?` · 当前 API 使用中`:``,i?` · Codex 使用中`:``,a?` · ${a}`:``]})]}),(0,v.jsx)(`span`,{className:`auto-switch-state-pill ${n?`is-excluded`:`is-included`}`,children:n?`不参与轮换`:`参与轮换`})]},t.profileId)})})]}),(0,v.jsxs)(`section`,{className:`settings-section`,children:[(0,v.jsx)(`h4`,{children:`显示`}),(0,v.jsxs)(`label`,{className:`switch-line`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:e.showEmails,onChange:t=>e.setShowEmails(t.target.checked)}),(0,v.jsx)(`span`,{children:`脱敏模式`})]}),(0,v.jsx)(`p`,{className:`hint`,children:`开启后账号邮箱将以脱敏形式展示。`})]})]}),(0,v.jsxs)(`div`,{className:`settings-page-actions settings-page-footer-actions`,children:[(0,v.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>void H(),disabled:e.busy===`settings`||e.busy===`restart`||!F,children:`保存设置`}),(0,v.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void H({restart:!0}),disabled:e.busy===`settings`||e.busy===`restart`||!F||!e.config?.restartSupported,children:`保存并重启网关`})]})]})}export{T as SettingsPage};