dsh-llm-fallbacks 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,6 +38,238 @@ window.__ModuleLoader__.load({
38
38
  raw: trimmed
39
39
  };
40
40
  }
41
+ /**
42
+ * Frozen preset windows (UTC+8). `liang-*` presets have NO day mask (they
43
+ * apply every day, weekends included); `glm-peak` is Monday–Friday only.
44
+ * The two valleys are `complement: true` of their peak.
45
+ */
46
+ const PRESETS = {
47
+ "liang-peak": {
48
+ windows: [{
49
+ start: "09:00",
50
+ end: "12:00"
51
+ }, {
52
+ start: "14:00",
53
+ end: "18:00"
54
+ }],
55
+ complement: false,
56
+ label: "Liang Peak"
57
+ },
58
+ "liang-valley": {
59
+ windows: [{
60
+ start: "09:00",
61
+ end: "12:00"
62
+ }, {
63
+ start: "14:00",
64
+ end: "18:00"
65
+ }],
66
+ complement: true,
67
+ label: "Liang Valley"
68
+ },
69
+ "glm-peak": {
70
+ windows: [{
71
+ start: "14:00",
72
+ end: "18:00",
73
+ days: [
74
+ 1,
75
+ 2,
76
+ 3,
77
+ 4,
78
+ 5
79
+ ]
80
+ }],
81
+ complement: false,
82
+ label: "GLM Peak"
83
+ },
84
+ "glm-valley": {
85
+ windows: [{
86
+ start: "14:00",
87
+ end: "18:00",
88
+ days: [
89
+ 1,
90
+ 2,
91
+ 3,
92
+ 4,
93
+ 5
94
+ ]
95
+ }],
96
+ complement: true,
97
+ label: "GLM Valley"
98
+ }
99
+ };
100
+ const HHMM_RE$1 = /^([01]\d|2[0-3]):[0-5]\d$/;
101
+ const WEEKDAY_INDEX = {
102
+ Sun: 0,
103
+ Mon: 1,
104
+ Tue: 2,
105
+ Wed: 3,
106
+ Thu: 4,
107
+ Fri: 5,
108
+ Sat: 6
109
+ };
110
+ /** Malformed rows warn once per row INSTANCE (config snapshots are stable
111
+ * across requests — this is what keeps "warn once" from becoming spam). */
112
+ const warnedMalformedRows = /* @__PURE__ */ new WeakSet();
113
+ /** Invalid `tz` values warn once per distinct value. */
114
+ const warnedTimeZones = /* @__PURE__ */ new Set();
115
+ function warnMalformed(row, reason) {
116
+ if (typeof row !== "object" || row === null) {
117
+ console.warn(`llm-fallbacks: skipping malformed time-slot row (${reason})`);
118
+ return;
119
+ }
120
+ if (warnedMalformedRows.has(row)) return;
121
+ warnedMalformedRows.add(row);
122
+ console.warn(`llm-fallbacks: skipping malformed time-slot row (${reason}): ${JSON.stringify(row)}`);
123
+ }
124
+ /** Wall-clock weekday (0=Sunday) + minutes-since-midnight of `now` in `tz`
125
+ * (standard `Intl` timezone rules, DST-safe). */
126
+ function wallClock(now, tz) {
127
+ try {
128
+ const parts = new Intl.DateTimeFormat("en-US", {
129
+ timeZone: tz,
130
+ weekday: "short",
131
+ hour: "2-digit",
132
+ minute: "2-digit",
133
+ hourCycle: "h23"
134
+ }).formatToParts(now);
135
+ let weekday = 0;
136
+ let minutes = 0;
137
+ for (const part of parts) if (part.type === "weekday") weekday = WEEKDAY_INDEX[part.value] ?? 0;
138
+ else if (part.type === "hour") minutes += Number(part.value) * 60;
139
+ else if (part.type === "minute") minutes += Number(part.value);
140
+ return {
141
+ weekday,
142
+ minutes
143
+ };
144
+ } catch (error) {
145
+ if (!warnedTimeZones.has(tz)) {
146
+ warnedTimeZones.add(tz);
147
+ console.warn(`llm-fallbacks: invalid timezone "${tz}" (${error.message}) — slot matching falls back to UTC`);
148
+ }
149
+ return {
150
+ weekday: now.getUTCDay(),
151
+ minutes: now.getUTCHours() * 60 + now.getUTCMinutes()
152
+ };
153
+ }
154
+ }
155
+ /** `HH:mm` → minutes-since-midnight (inputs are regex-validated or frozen
156
+ * constants — this never sees garbage). */
157
+ function minutesOf(hhmm) {
158
+ const [hour, minute] = hhmm.split(":").map(Number);
159
+ return hour * 60 + minute;
160
+ }
161
+ /** P4 containment rule: day mask matches AND the window contains `t`
162
+ * (`end` exclusive; `start > end` wraps midnight — custom rows only). */
163
+ function containsWindow(window, clock) {
164
+ if (!(window.days === void 0 || window.days.length === 0 || window.days.includes(clock.weekday))) return false;
165
+ const start = minutesOf(window.start);
166
+ const end = minutesOf(window.end);
167
+ return start <= end ? start <= clock.minutes && clock.minutes < end : start <= clock.minutes || clock.minutes < end;
168
+ }
169
+ function matchesAnyWindow(windows, clock) {
170
+ return windows.some((window) => containsWindow(window, clock));
171
+ }
172
+ /** Validate one stored row → frozen windows, or `undefined` (warn once +
173
+ * skip). Preset rows reject stored windows/day masks (P4); custom rows
174
+ * require strict `HH:mm` bounds; chains must be non-empty. */
175
+ function describeRow(row) {
176
+ if (!Array.isArray(row.chain) || row.chain.length === 0) {
177
+ warnMalformed(row, "empty chain");
178
+ return;
179
+ }
180
+ if (row.kind === "preset") {
181
+ const preset = row.preset;
182
+ if (typeof preset !== "string" || !Object.hasOwn(PRESETS, preset)) {
183
+ warnMalformed(row, `unknown preset ${JSON.stringify(preset)}`);
184
+ return;
185
+ }
186
+ if (row.start !== void 0 || row.end !== void 0 || row.days !== void 0 && row.days.length > 0) {
187
+ warnMalformed(row, `preset windows are fixed — row "${preset}" cannot carry start/end/days`);
188
+ return;
189
+ }
190
+ return PRESETS[preset];
191
+ }
192
+ if (row.kind === "custom") {
193
+ const { start, end } = row;
194
+ if (typeof start !== "string" || typeof end !== "string" || !HHMM_RE$1.test(start) || !HHMM_RE$1.test(end)) {
195
+ warnMalformed(row, `invalid custom window ${JSON.stringify(start)}-${JSON.stringify(end)} (expected HH:mm)`);
196
+ return;
197
+ }
198
+ return {
199
+ windows: [{
200
+ start,
201
+ end,
202
+ days: row.days
203
+ }],
204
+ complement: false
205
+ };
206
+ }
207
+ warnMalformed(row, `unknown kind ${JSON.stringify(row.kind)}`);
208
+ }
209
+ /** Display label for a winning row (preset rows use the frozen label;
210
+ * custom rows prefer their display name, falling back to the window). */
211
+ function labelOf(row) {
212
+ if (row.kind === "preset" && typeof row.preset === "string" && Object.hasOwn(PRESETS, row.preset)) return PRESETS[row.preset].label;
213
+ return row.name !== void 0 && row.name.trim() !== "" ? row.name : `custom ${row.start}-${row.end}`;
214
+ }
215
+ /**
216
+ * All-day conformance (P6): the all-day chain is conforming when its LAST
217
+ * entry (the tail — the card's 默认模型 panel) is exactly one official V4
218
+ * model — Flash XOR Pro. Leading entries (the card's 默认降级链 block) are
219
+ * the ordered walk before that last-resort fallback. An empty chain or a
220
+ * chain whose tail is not an official V4 model keeps slot rows inert and
221
+ * refuses the virtual-row override/delegate; the v0.2.2 failure walk over
222
+ * the raw chain stays verbatim.
223
+ */
224
+ function isAllDayConforming(chain) {
225
+ if (chain.length < 1) return false;
226
+ const tail = chain[chain.length - 1];
227
+ return tail === "deepseek-official/deepseek-v4-flash" || tail === "deepseek-official/deepseek-v4-pro";
228
+ }
229
+ /**
230
+ * Slot winner + display label (P5): drives 分时切换 detection (per-root-agent
231
+ * last-winner marker, in-process only) and the card / `/fallbacks` status
232
+ * strip. `winner` is the matching row or `'all-day'`; `label` names the
233
+ * slot (frozen preset label or `custom HH:mm-HH:mm`).
234
+ *
235
+ * P6 gate (qc1 F-001): without a conforming all-day
236
+ * (`isAllDayConforming(config.rootChain)`) the winner is ALWAYS `'all-day'`
237
+ * — a legacy multi-model (or empty) chain earns no slot rows, so every
238
+ * surface fed by this resolver reports the inert state and routing stays on
239
+ * the raw `rootChain` (the v0.2.2 walk verbatim).
240
+ */
241
+ function resolveSlotState(config, now, tz) {
242
+ if (!isAllDayConforming(config.rootChain)) return {
243
+ winner: "all-day",
244
+ label: "all-day"
245
+ };
246
+ const clock = wallClock(now, tz);
247
+ const seenPresets = /* @__PURE__ */ new Set();
248
+ const rows = Array.isArray(config.timeSlots) ? config.timeSlots : [];
249
+ for (const row of rows) {
250
+ if (typeof row !== "object" || row === null) {
251
+ warnMalformed(row, "row is not an object");
252
+ continue;
253
+ }
254
+ if (row.kind === "preset" && typeof row.preset === "string") {
255
+ if (seenPresets.has(row.preset)) {
256
+ warnMalformed(row, `duplicate preset "${row.preset}" — only the first row takes effect`);
257
+ continue;
258
+ }
259
+ seenPresets.add(row.preset);
260
+ }
261
+ const descriptor = describeRow(row);
262
+ if (descriptor === void 0) continue;
263
+ if (descriptor.complement ? !matchesAnyWindow(descriptor.windows, clock) : matchesAnyWindow(descriptor.windows, clock)) return {
264
+ winner: row,
265
+ label: labelOf(row)
266
+ };
267
+ }
268
+ return {
269
+ winner: "all-day",
270
+ label: "all-day"
271
+ };
272
+ }
41
273
  //#endregion
42
274
  //#region src/config.ts
43
275
  /**
@@ -63,7 +295,10 @@ window.__ModuleLoader__.load({
63
295
  revertPolicy: "cooldown-expiry",
64
296
  maxSwitchesPerStep: 8,
65
297
  alwaysModeRetryCap: 5,
66
- presets: "bundled"
298
+ presets: "bundled",
299
+ roleAutoMatch: true,
300
+ timeSlots: [],
301
+ tz: "Asia/Shanghai"
67
302
  };
68
303
  /**
69
304
  * Reserved role id: legal as a rule target (`roles.rules[].role`) and as
@@ -114,6 +349,15 @@ window.__ModuleLoader__.load({
114
349
  return typeof entry.id === "string" && typeof entry.overridden === "boolean";
115
350
  });
116
351
  }
352
+ /** Seed-default persona from a revert-seed wire body (issue #59). */
353
+ function revertOutcomePersona(value) {
354
+ if (value === null || typeof value !== "object" || !("outcome" in value)) return void 0;
355
+ const outcome = value.outcome;
356
+ if (outcome === null || typeof outcome !== "object") return void 0;
357
+ if (!("reverted" in outcome) || outcome.reverted !== true) return void 0;
358
+ if (!("persona" in outcome) || typeof outcome.persona !== "string") return void 0;
359
+ return outcome.persona;
360
+ }
117
361
  /**
118
362
  * The provider dropdown's offer set (spec §2.5 D-4): catalog providers whose
119
363
  * settings profile resolves in the describe namespaces — the Models page's
@@ -192,6 +436,25 @@ window.__ModuleLoader__.load({
192
436
  if (presets !== void 0 && presets !== "bundled" && presets !== "none") throw new TypeError("fallbacks descriptor presets must be bundled|none");
193
437
  const enabled = value.enabled;
194
438
  if (enabled !== void 0 && typeof enabled !== "boolean") throw new TypeError("fallbacks descriptor enabled must be a boolean");
439
+ const roleAutoMatch = value.roleAutoMatch;
440
+ if (roleAutoMatch !== void 0 && typeof roleAutoMatch !== "boolean") throw new TypeError("fallbacks descriptor roleAutoMatch must be a boolean");
441
+ const timeSlots = value.timeSlots;
442
+ if (timeSlots !== void 0 && (!Array.isArray(timeSlots) || timeSlots.some((row) => {
443
+ if (!isRecord(row)) return true;
444
+ for (const field of [
445
+ "kind",
446
+ "preset",
447
+ "start",
448
+ "end"
449
+ ]) if (row[field] !== void 0 && typeof row[field] !== "string") return true;
450
+ const chain = row.chain;
451
+ if (chain !== void 0 && (!Array.isArray(chain) || chain.some((entry) => typeof entry !== "string"))) return true;
452
+ const days = row.days;
453
+ if (days !== void 0 && (!Array.isArray(days) || days.some((day) => typeof day !== "number"))) return true;
454
+ return false;
455
+ }))) throw new TypeError("fallbacks descriptor timeSlots must be an array of slot rows (kind/preset/start/end strings, chain string array, days number array)");
456
+ const tz = value.tz;
457
+ if (tz !== void 0 && typeof tz !== "string") throw new TypeError("fallbacks descriptor tz must be a string");
195
458
  return {
196
459
  enabled: enabled ?? defaultFallbacksConfig.enabled,
197
460
  triggerCodes: triggerCodes ?? [...defaultFallbacksConfig.triggerCodes],
@@ -204,7 +467,10 @@ window.__ModuleLoader__.load({
204
467
  revertPolicy: revertPolicy ?? defaultFallbacksConfig.revertPolicy,
205
468
  maxSwitchesPerStep: maxSwitchesPerStep ?? defaultFallbacksConfig.maxSwitchesPerStep,
206
469
  alwaysModeRetryCap: alwaysModeRetryCap ?? defaultFallbacksConfig.alwaysModeRetryCap,
207
- presets: presets ?? defaultFallbacksConfig.presets
470
+ presets: presets ?? defaultFallbacksConfig.presets,
471
+ roleAutoMatch: roleAutoMatch ?? defaultFallbacksConfig.roleAutoMatch,
472
+ timeSlots: timeSlots ?? [...defaultFallbacksConfig.timeSlots ?? []],
473
+ tz: tz ?? defaultFallbacksConfig.tz
208
474
  };
209
475
  }
210
476
  /** The raw selector string a selection serializes to ('' when empty). */
@@ -262,46 +528,6 @@ window.__ModuleLoader__.load({
262
528
  switches.sort((a, b) => b.seq - a.seq);
263
529
  return switches.slice(0, limit);
264
530
  }
265
- /** The config's primary target: the rootChain's first entry (D-6 ③). */
266
- function configPrimaryTarget(config) {
267
- const firstEntry = config.rootChain[0];
268
- if (firstEntry === void 0) return null;
269
- try {
270
- const selector = parseSelector(firstEntry);
271
- return {
272
- provider: selector.provider,
273
- model: selector.model ?? "*"
274
- };
275
- } catch {
276
- return {
277
- provider: firstEntry,
278
- model: "*"
279
- };
280
- }
281
- }
282
- /**
283
- * Derive the status block's "current effective model" (spec §2.5 D-6): ①
284
- * disabled / empty rootChain → unavailable; ② a recent switch exists → the
285
- * latest one's `to`; ③ otherwise → the config's primary target. A **display
286
- * value** — never a live route probe (the section appends the non-probing
287
- * note inline right after the derived value, available case only; the
288
- * unavailable 空态 renders its own copy without the note).
289
- */
290
- function deriveEffectiveModel(config, switches) {
291
- if (!config.enabled || config.rootChain.length === 0) return { kind: "unavailable" };
292
- const latest = switches[0];
293
- if (latest !== void 0) return {
294
- kind: "switched",
295
- provider: latest.to.provider,
296
- model: latest.to.model
297
- };
298
- const target = configPrimaryTarget(config);
299
- if (target === null) return { kind: "unavailable" };
300
- return {
301
- kind: "config",
302
- ...target
303
- };
304
- }
305
531
  /** Serialize one selector row to its wire string (`provider/model` | `provider/*`). */
306
532
  function selectorRowToRaw(row) {
307
533
  const provider = selectionToRaw(row.provider);
@@ -346,13 +572,54 @@ window.__ModuleLoader__.load({
346
572
  }
347
573
  return entries;
348
574
  }
575
+ /** Project the time-slot rows into editable rows (chain selectors classified). */
576
+ function timeSlotsToRows(timeSlots, catalog) {
577
+ return timeSlots.map((row) => ({
578
+ kind: row.kind,
579
+ ...row.preset === void 0 ? {} : { preset: row.preset },
580
+ start: row.start ?? "",
581
+ end: row.end ?? "",
582
+ days: [...row.days ?? []],
583
+ name: row.name ?? "",
584
+ collapsed: true,
585
+ selectors: (row.chain ?? []).map((entry) => entryToSelectorRow(entry, catalog))
586
+ }));
587
+ }
588
+ /** Rebuild the time-slot rows from edited rows; blank selectors drop out.
589
+ * `kind` rides verbatim (a hand-written unknown kind reads back unchanged;
590
+ * save validation rejects it) — the cast asserts the trusted editor shape.
591
+ * `days` is ALWAYS serialized ([] included): schemastery composes absent
592
+ * array fields as `[]`, so the composed config every card load accepts
593
+ * carries `days` on every row — the draft must too, or a clean card would
594
+ * read back dirty. */
595
+ function rowsToTimeSlots(rows) {
596
+ return rows.map((row) => {
597
+ const chain = row.selectors.map(selectorRowToRaw).filter((entry) => entry !== "");
598
+ if (row.kind === "preset") return {
599
+ kind: "preset",
600
+ preset: row.preset,
601
+ days: row.days,
602
+ chain
603
+ };
604
+ return {
605
+ kind: row.kind,
606
+ ...row.preset === void 0 ? {} : { preset: row.preset },
607
+ start: row.start,
608
+ end: row.end,
609
+ days: row.days,
610
+ ...row.name === "" ? {} : { name: row.name },
611
+ chain
612
+ };
613
+ });
614
+ }
349
615
  /** Project the declared roles into editable rows (chain selectors classified). */
350
616
  function rolesToRows(roles, catalog) {
351
617
  return roles.map((role) => ({
352
618
  id: role.id,
353
619
  persona: role.persona,
354
620
  selectors: (role.chain ?? []).map((entry) => entryToSelectorRow(entry, catalog)),
355
- fallback: role.fallback ?? "inherit-root"
621
+ fallback: role.fallback ?? "inherit-root",
622
+ collapsed: true
356
623
  }));
357
624
  }
358
625
  /** Rebuild the declared roles from edited rows; empty selectors drop out. */
@@ -401,16 +668,14 @@ window.__ModuleLoader__.load({
401
668
  /** Project the role rules into editable rows (provider/model classified). */
402
669
  function rulesToRows(rules, catalog) {
403
670
  return rules.map((rule) => ({
404
- origin: rule.origin ?? "",
405
671
  provider: classifyProvider(rule.provider ?? "", catalog),
406
672
  model: classifyModel(rule.provider ?? "", rule.model ?? "", catalog),
407
673
  role: rule.role
408
674
  }));
409
675
  }
410
- /** Rebuild the role rules from edited rows; empty origin/provider/model drop out. */
676
+ /** Rebuild the role rules from edited rows; empty provider/model drop out. */
411
677
  function rowsToRules(rows) {
412
678
  return rows.map((row) => ({
413
- ...row.origin === "" ? {} : { origin: row.origin },
414
679
  ...row.provider === null ? {} : { provider: selectionToRaw(row.provider) },
415
680
  ...row.model === null ? {} : { model: selectionToRaw(row.model) },
416
681
  role: row.role.trim()
@@ -694,12 +959,17 @@ window.__ModuleLoader__.load({
694
959
  * reason }` outcome is still a successful RPC — the post-write read
695
960
  * result (config / legacyKeys / seeds) lands either way, and the revert
696
961
  * button stays disabled while the write is in flight.
962
+ *
963
+ * Returns the seed-default persona when the outcome is `{ reverted:
964
+ * true, persona }` — including the persist no-op (persisted already
965
+ * equals the seed). The card applies that string to the row's **draft**
966
+ * so an unsaved persona edit still snaps back (issue #59).
697
967
  * @param id - the seeded role id; the host matches it by trimmed id
698
968
  * against the seed registry (spec §9.3).
699
969
  */
700
970
  async revertSeed(id) {
701
971
  const state = this.store.getSnapshot();
702
- if (!state.writable || state.status === "saving") return;
972
+ if (!state.writable || state.status === "saving") return void 0;
703
973
  const generation = ++this.writeGeneration;
704
974
  this.store.update((draft) => {
705
975
  draft.status = "saving";
@@ -707,7 +977,7 @@ window.__ModuleLoader__.load({
707
977
  });
708
978
  try {
709
979
  const result = await this.rpc.call("/api", "fallbacks/revert-seed", { args: { id } });
710
- if (generation !== this.writeGeneration) return;
980
+ if (generation !== this.writeGeneration) return void 0;
711
981
  if (!result.ok) throw result.error;
712
982
  const value = result.value;
713
983
  const config = value !== null && typeof value === "object" && "config" in value ? value.config : void 0;
@@ -722,9 +992,11 @@ window.__ModuleLoader__.load({
722
992
  if (Array.isArray(wireSeeds)) seeds = parseSeedsWire(wireSeeds);
723
993
  }
724
994
  this.accept(config, true, legacyKeys, seeds);
995
+ return revertOutcomePersona(value);
725
996
  } catch (error) {
726
- if (generation !== this.writeGeneration) return;
997
+ if (generation !== this.writeGeneration) return void 0;
727
998
  this.fail(error);
999
+ return;
728
1000
  }
729
1001
  }
730
1002
  /** Stop in-flight responses from publishing after plugin disposal. */
@@ -850,15 +1122,65 @@ window.__ModuleLoader__.load({
850
1122
  "advanced.label": "高级选项",
851
1123
  "advanced.expand": "展开高级选项",
852
1124
  "advanced.collapse": "收起高级选项",
853
- "rootChain.label": "root 主代理降级链",
854
- "rootChain.hint": "未配置 = root 不降级",
855
- "rootChain.tooltip": "root 主代理失败时按此有序选择器列表依次降级;未配置时行为与未安装插件一致。",
856
- "rootChain.selector.add": "添加选择器",
1125
+ "roleAutoMatch.label": "启用角色自动匹配",
1126
+ "roleAutoMatch.hint": "规则未命中时,由模型自选最贴近的已声明角色",
1127
+ "roleAutoMatch.tooltip": "规则未命中时,模型会自动从已声明角色(id + persona)中选择最匹配者并注入该角色的链;关闭后未命中规则时按现状回落(inherit / rootChain)。",
1128
+ "rootChain.label": "默认降级链",
1129
+ "rootChain.tooltip": "未命中任何分时槽时先走这条降级链;全部失败后落到下面的默认模型。",
1130
+ "defaultModel.label": "默认模型",
857
1131
  "chains.selector.remove": "删除该选择器",
858
1132
  "chains.selector.providerPlaceholder": "选择 provider",
859
1133
  "chains.selector.modelPlaceholder": "选择 model",
860
1134
  "chains.selector.wildcardLegacy": "该条目为通配(provider/*):选择具体模型后将转为精确条目",
861
1135
  "chains.selector.noModelsStrict": "该 provider 暂无可用模型(目录查询失败),请改选其他 provider。",
1136
+ "mainAgent.label": "主代理",
1137
+ "subagents.label": "子代理",
1138
+ "timeSlots.label": "分时槽设置",
1139
+ "timeSlots.hint": "自上而下第一条命中生效;全时段行固定最后",
1140
+ "timeSlots.tz.label": "时区(UTC±)",
1141
+ "timeSlots.drag": "拖拽排序(或使用上下按钮)",
1142
+ "timeSlots.name": "名称",
1143
+ "timeSlots.expand": "展开该行",
1144
+ "timeSlots.collapse": "收起该行",
1145
+ "timeSlots.tooltip": "命中行的模型链成为 root 生效链(取代全时段链);未命中任何行时使用全时段链。分时切换是路由种子而非失败决策:不消耗冷却、不计入单步切换上限。",
1146
+ "timeSlots.addPreset": "添加预设",
1147
+ "timeSlots.addCustom": "添加自定义时段",
1148
+ "timeSlots.presetPlaceholder": "选择预设",
1149
+ "timeSlots.remove": "删除该时段行",
1150
+ "timeSlots.moveUp": "上移该时段行",
1151
+ "timeSlots.moveDown": "下移该时段行",
1152
+ "timeSlots.start": "开始(HH:mm)",
1153
+ "timeSlots.end": "结束(HH:mm)",
1154
+ "timeSlots.days": "星期",
1155
+ "timeSlots.days.hint": "不勾选 = 每天;可跨午夜",
1156
+ "timeSlots.preset.name": "预设",
1157
+ "timeSlots.preset.windowLabel": "时段(只读)",
1158
+ "timeSlots.preset.chainsOnly": "预设窗口已锁定:仅可编辑模型链",
1159
+ "timeSlots.preset.liang-peak.label": "梁文峰",
1160
+ "timeSlots.preset.liang-peak.window": "09:00–12:00 与 14:00–18:00(每天,UTC+8)",
1161
+ "timeSlots.preset.liang-valley.label": "梁文谷",
1162
+ "timeSlots.preset.liang-valley.window": "Liang Peak 之外的所有时间(每天,UTC+8)",
1163
+ "timeSlots.preset.glm-peak.label": "GLM峰",
1164
+ "timeSlots.preset.glm-peak.window": "周一至周五 14:00–18:00(UTC+8)",
1165
+ "timeSlots.preset.glm-valley.label": "GLM谷",
1166
+ "timeSlots.preset.glm-valley.window": "GLM Peak 之外的所有时间(UTC+8)",
1167
+ "timeSlots.preset.glm.note": "仅配置了 zai-coding-cn 时有效",
1168
+ "timeSlots.preset.highCost": "高消耗",
1169
+ "timeSlots.preset.multiplier": "x{n}",
1170
+ "timeSlots.active": "激活",
1171
+ "timeSlots.preset.glm.unconfigured": "(需配置 zai-coding-cn)",
1172
+ "timeSlots.day.sun": "日",
1173
+ "timeSlots.day.mon": "一",
1174
+ "timeSlots.day.tue": "二",
1175
+ "timeSlots.day.wed": "三",
1176
+ "timeSlots.day.thu": "四",
1177
+ "timeSlots.day.fri": "五",
1178
+ "timeSlots.day.sat": "六",
1179
+ "timeSlots.selector.add": "添加选择器",
1180
+ "allDay.hint": "全天链的最后一档兜底:官方 V4 Flash 或 V4 Pro 二选一",
1181
+ "allDay.flash": "官方 V4 Flash(deepseek-official/deepseek-v4-flash)",
1182
+ "allDay.pro": "官方 V4 Pro(deepseek-official/deepseek-v4-pro)",
1183
+ "allDay.nonconforming": "当前默认模型不合法:请选择官方 V4 Flash 或 V4 Pro 后保存",
862
1184
  "roles.list.label": "角色实体",
863
1185
  "roles.list.hint": "先声明角色,规则才能引用",
864
1186
  "roles.list.tooltip": "角色 id 须匹配 /^[a-z0-9-]{1,32}$/ 且唯一;\"inherit\" 为保留字,不能用作角色 id。",
@@ -876,14 +1198,12 @@ window.__ModuleLoader__.load({
876
1198
  "roles.fallback.none": "仅角色链(不追加 rootChain)",
877
1199
  "roles.add": "添加角色",
878
1200
  "roles.remove": "删除该角色",
1201
+ "roles.expand": "展开该角色",
1202
+ "roles.collapse": "收起该角色",
879
1203
  "roles.selector.add": "添加选择器",
880
1204
  "roles.rules": "角色规则",
881
- "roles.rules.hint": "顺序匹配 origin/provider/model,未命中 → inherit(root 链)",
882
- "roles.rules.tooltip": "规则命中后走对应角色的链;未命中走内置 inherit(rootChain)。",
883
- "roles.rule.origin": "来源",
884
- "roles.rule.origin.any": "任意",
885
- "roles.rule.origin.root": "root",
886
- "roles.rule.origin.subagent": "subagent",
1205
+ "roles.rules.hint": "仅对子代理生效:顺序匹配 provider/model,未命中 → inherit(root 链)",
1206
+ "roles.rules.tooltip": "规则仅对子代理生效(root 请求不匹配规则):命中后走对应角色的链;未命中走内置 inherit(rootChain)。",
887
1207
  "roles.rule.provider": "provider",
888
1208
  "roles.rule.provider.any": "任意",
889
1209
  "roles.rule.model": "model",
@@ -901,6 +1221,14 @@ window.__ModuleLoader__.load({
901
1221
  "validation.ruleRoleUndeclared": "规则引用了未声明的角色 \"{role}\"",
902
1222
  "validation.ruleRoleRequired": "规则未选择角色:请选择目标角色,或删除该行",
903
1223
  "validation.roleChainRequired": "角色 \"{id}\" 未配置模型:请至少添加一条链选择器(模型配置)",
1224
+ "validation.allDayRequired": "默认模型必须二选一:官方 V4 Flash 或 V4 Pro",
1225
+ "validation.slotChainRequired": "分时槽未配置模型:请至少添加一条链选择器",
1226
+ "validation.slotWindow": "分时槽开始/结束时间须为 HH:mm 格式",
1227
+ "validation.slotDays": "星期取值须为 0–6 的整数",
1228
+ "validation.slotKind": "分时槽 kind 须为 \"preset\" 或 \"custom\"",
1229
+ "validation.slotPresetUnknown": "未知的分时槽预设 \"{preset}\"",
1230
+ "validation.slotPresetDuplicate": "预设 \"{preset}\" 已存在:每个预设只能添加一行",
1231
+ "validation.slotPresetFrozen": "预设窗口是冻结代码常量:预设行不能携带 start/end/days",
904
1232
  "validation.selector": "选择器 \"{entry}\" 非法:{message}",
905
1233
  "legacy.banner": "检测到旧格式配置字段({keys}):已按新模型展示,请按 docs/configuration.md 迁移表手工改写;插件不会自动改写配置。",
906
1234
  "catalog.empty": "暂无可用模型:请先在模型页添加模型,添加后此处将自动可选。",
@@ -911,37 +1239,32 @@ window.__ModuleLoader__.load({
911
1239
  "catalog.outside.short": " (目录外)",
912
1240
  "catalog.unconfigured.short": " (未配置)",
913
1241
  "status.title": "运行状态(只读)",
914
- "status.effectiveModel.label": "当前生效模型:",
915
- "status.effectiveModel.unavailable": "fallbacks 未启用(或 rootChain 未配置)",
916
- "status.effectiveModel.note": "配置 + 最近切换推导,非实时路由探测",
917
1242
  "status.switches.label": "最近切换:",
918
1243
  "status.switches.empty": "本会话暂无 fallback 切换。",
919
1244
  "status.switches.error": "切换历史读取失败:{message}",
920
1245
  "status.switches.compact": "最近 {count} 次 · {from} → {to}({role} · {reason})",
1246
+ "status.switches.compact.roleInject": "最近 {count} 次 · {role} → {to}({reason})",
921
1247
  "status.switches.reason.trigger-code": "触发失败码",
922
1248
  "status.switches.reason.always-cap": "always 模式上限",
923
- "status.selectionNote": "说明:web 前端手动选择的模型可能在切换后重新套用(标记载体已随本地 patch 移除)。",
1249
+ "status.switches.reason.role-inject": "角色注入",
924
1250
  "general.title": "模型故障降级",
925
1251
  "general.enabled": "已启用",
926
1252
  "general.disabled": "未启用",
927
1253
  "general.unknown": "未知",
928
1254
  "general.unavailable": "状态通道暂不可达",
929
1255
  "general.switch": "最近切换:{from} → {to}({role} · {reason})",
1256
+ "general.switch.roleInject": "最近切换:{role} → {to}({reason})",
930
1257
  "general.switch.empty": "本会话暂无切换",
931
1258
  "general.error": "状态读取失败:{message}",
932
1259
  "chat.switch.title": "模型已降级",
933
- "chat.switch.summary": "{from} → {to}({role} · {reason})",
1260
+ "chat.switch.summary": "{from} → {to}({reason})",
1261
+ "chat.switch.summary.roleInject": "({reason})",
1262
+ "chat.switch.roleMap": "{role} → {model}",
934
1263
  "defaults.prefix": "默认值",
935
1264
  "save": "保存",
936
1265
  "save.saving": "保存中…",
937
1266
  "save.error": "保存失败:{message}",
938
1267
  "close": "关闭",
939
- "reset": "恢复默认",
940
- "reset.confirmTitle": "恢复默认配置",
941
- "reset.confirm": "恢复后 fallbacks 配置将回到插件默认值,当前编辑内容会丢失。",
942
- "reset.confirm.cancel": "取消",
943
- "reset.confirm.action": "恢复默认",
944
- "reset.saving": "恢复中…",
945
1268
  "loading": "加载中…",
946
1269
  "unavailable": "fallbacks 配置通道暂不可达:以下显示默认配置(或上次读取值),可尝试保存;保存失败会在此处如实提示。",
947
1270
  "error.generic": "出错:{message}"
@@ -984,15 +1307,65 @@ window.__ModuleLoader__.load({
984
1307
  "advanced.label": "Advanced options",
985
1308
  "advanced.expand": "Show advanced options",
986
1309
  "advanced.collapse": "Hide advanced options",
987
- "rootChain.label": "Root agent fallback chain",
988
- "rootChain.hint": "Unset = root does not fall back",
989
- "rootChain.tooltip": "When the root agent fails it falls back down this ordered selector list; unset behaves like an uninstalled plugin.",
990
- "rootChain.selector.add": "Add selector",
1310
+ "roleAutoMatch.label": "Enable role auto-match",
1311
+ "roleAutoMatch.hint": "On rules-miss, the model picks the closest declared role",
1312
+ "roleAutoMatch.tooltip": "When no rule matches, the model auto-selects the best-fit declared role (id + persona) and uses its chain; turn off to keep today's fallback (inherit / rootChain) on a rules-miss.",
1313
+ "rootChain.label": "Default fallback chain",
1314
+ "rootChain.tooltip": "Walked first whenever no time slot matches; if every entry fails, the default model below is the last fallback.",
1315
+ "defaultModel.label": "Default model",
991
1316
  "chains.selector.remove": "Remove this selector",
992
1317
  "chains.selector.providerPlaceholder": "Select provider",
993
1318
  "chains.selector.modelPlaceholder": "Select model",
994
1319
  "chains.selector.wildcardLegacy": "This entry is a wildcard (provider/*): picking a model converts it to an exact entry",
995
1320
  "chains.selector.noModelsStrict": "No models available for this provider (catalog lookup failed); pick another provider.",
1321
+ "mainAgent.label": "Main agent",
1322
+ "subagents.label": "Subagents",
1323
+ "timeSlots.label": "Time slots",
1324
+ "timeSlots.hint": "First match from top to bottom wins; the all-day row is always last",
1325
+ "timeSlots.tz.label": "Timezone (UTC±)",
1326
+ "timeSlots.drag": "Drag to reorder (or use the up/down buttons)",
1327
+ "timeSlots.name": "Name",
1328
+ "timeSlots.expand": "Expand this row",
1329
+ "timeSlots.collapse": "Collapse this row",
1330
+ "timeSlots.tooltip": "A matched row's model chain becomes the effective root chain (replacing the all-day chain); no match uses the all-day chain. A time-slot switch is a routing seed, not a failure decision: it consumes no cooldown and does not count against the per-step switch cap.",
1331
+ "timeSlots.addPreset": "Add preset",
1332
+ "timeSlots.addCustom": "Add custom time slot",
1333
+ "timeSlots.presetPlaceholder": "Select a preset",
1334
+ "timeSlots.remove": "Remove this time-slot row",
1335
+ "timeSlots.moveUp": "Move this time-slot row up",
1336
+ "timeSlots.moveDown": "Move this time-slot row down",
1337
+ "timeSlots.start": "Start (HH:mm)",
1338
+ "timeSlots.end": "End (HH:mm)",
1339
+ "timeSlots.days": "Days",
1340
+ "timeSlots.days.hint": "None selected = every day; may wrap midnight",
1341
+ "timeSlots.preset.name": "Preset",
1342
+ "timeSlots.preset.windowLabel": "Window (read-only)",
1343
+ "timeSlots.preset.chainsOnly": "Preset windows are frozen: only the model chain is editable",
1344
+ "timeSlots.preset.liang-peak.label": "Liang Peak",
1345
+ "timeSlots.preset.liang-peak.window": "09:00–12:00 & 14:00–18:00 (every day, UTC+8)",
1346
+ "timeSlots.preset.liang-valley.label": "Liang Valley",
1347
+ "timeSlots.preset.liang-valley.window": "All times outside Liang Peak (every day, UTC+8)",
1348
+ "timeSlots.preset.glm-peak.label": "GLM Peak",
1349
+ "timeSlots.preset.glm-peak.window": "Monday–Friday 14:00–18:00 (UTC+8)",
1350
+ "timeSlots.preset.glm-valley.label": "GLM Valley",
1351
+ "timeSlots.preset.glm-valley.window": "All times outside GLM Peak (UTC+8)",
1352
+ "timeSlots.preset.glm.note": "Only effective when zai-coding-cn is configured",
1353
+ "timeSlots.preset.highCost": "High Cost",
1354
+ "timeSlots.preset.multiplier": "x{n}",
1355
+ "timeSlots.active": "Active",
1356
+ "timeSlots.preset.glm.unconfigured": " (requires zai-coding-cn)",
1357
+ "timeSlots.day.sun": "Sun",
1358
+ "timeSlots.day.mon": "Mon",
1359
+ "timeSlots.day.tue": "Tue",
1360
+ "timeSlots.day.wed": "Wed",
1361
+ "timeSlots.day.thu": "Thu",
1362
+ "timeSlots.day.fri": "Fri",
1363
+ "timeSlots.day.sat": "Sat",
1364
+ "timeSlots.selector.add": "Add selector",
1365
+ "allDay.hint": "Last-resort fallback of the all-day chain: official V4 Flash or Pro (pick exactly one)",
1366
+ "allDay.flash": "Official V4 Flash (deepseek-official/deepseek-v4-flash)",
1367
+ "allDay.pro": "Official V4 Pro (deepseek-official/deepseek-v4-pro)",
1368
+ "allDay.nonconforming": "The current default model is not valid: pick official V4 Flash or Pro before saving",
996
1369
  "roles.list.label": "Declared roles",
997
1370
  "roles.list.hint": "Declare roles before rules can reference them",
998
1371
  "roles.list.tooltip": "Role ids must match /^[a-z0-9-]{1,32}$/ and be unique; \"inherit\" is reserved and cannot be used as a role id.",
@@ -1010,14 +1383,12 @@ window.__ModuleLoader__.load({
1010
1383
  "roles.fallback.none": "Role chain only (no rootChain)",
1011
1384
  "roles.add": "Add role",
1012
1385
  "roles.remove": "Remove this role",
1386
+ "roles.expand": "Expand this role",
1387
+ "roles.collapse": "Collapse this role",
1013
1388
  "roles.selector.add": "Add selector",
1014
1389
  "roles.rules": "Role rules",
1015
- "roles.rules.hint": "Matches origin/provider/model in order; no match → inherit (root chain)",
1016
- "roles.rules.tooltip": "A matched rule uses that role's chain; no match uses the built-in inherit (rootChain).",
1017
- "roles.rule.origin": "Origin",
1018
- "roles.rule.origin.any": "Any",
1019
- "roles.rule.origin.root": "root",
1020
- "roles.rule.origin.subagent": "subagent",
1390
+ "roles.rules.hint": "Subagents only: matches provider/model in order; no match → inherit (root chain)",
1391
+ "roles.rules.tooltip": "Rules apply to subagents only (root requests never match): a matched rule uses that role's chain; no match uses the built-in inherit (rootChain).",
1021
1392
  "roles.rule.provider": "provider",
1022
1393
  "roles.rule.provider.any": "Any",
1023
1394
  "roles.rule.model": "model",
@@ -1035,6 +1406,14 @@ window.__ModuleLoader__.load({
1035
1406
  "validation.ruleRoleUndeclared": "Rule references undeclared role \"{role}\"",
1036
1407
  "validation.ruleRoleRequired": "Rule has no role selected: pick a target role, or remove the row",
1037
1408
  "validation.roleChainRequired": "Role \"{id}\" has no model config: add at least one chain entry",
1409
+ "validation.allDayRequired": "The default model must be exactly one official V4 model (V4 Flash or V4 Pro)",
1410
+ "validation.slotChainRequired": "Time-slot row has no models: add at least one chain entry",
1411
+ "validation.slotWindow": "Time-slot start/end must use HH:mm format",
1412
+ "validation.slotDays": "Days must be integers 0–6",
1413
+ "validation.slotKind": "Time-slot kind must be \"preset\" or \"custom\"",
1414
+ "validation.slotPresetUnknown": "Unknown time-slot preset \"{preset}\"",
1415
+ "validation.slotPresetDuplicate": "Preset \"{preset}\" already exists: at most one row per preset",
1416
+ "validation.slotPresetFrozen": "Preset windows are frozen code constants: a preset row cannot carry start/end/days",
1038
1417
  "validation.selector": "Invalid selector \"{entry}\": {message}",
1039
1418
  "legacy.banner": "Legacy config fields detected ({keys}): now shown in the new model — rewrite them manually following the migration table in docs/configuration.md (the plugin will not rewrite them automatically).",
1040
1419
  "catalog.empty": "No models yet: add a model on the Models page first; options will appear here automatically.",
@@ -1045,37 +1424,32 @@ window.__ModuleLoader__.load({
1045
1424
  "catalog.outside.short": " (outside catalog)",
1046
1425
  "catalog.unconfigured.short": " (not configured)",
1047
1426
  "status.title": "Runtime status (read-only)",
1048
- "status.effectiveModel.label": "Current effective model: ",
1049
- "status.effectiveModel.unavailable": "Fallbacks disabled (or rootChain not configured)",
1050
- "status.effectiveModel.note": "Derived from configuration and recent switches; not real-time route probing",
1051
1427
  "status.switches.label": "Recent switches: ",
1052
1428
  "status.switches.empty": "No fallback switches in this session yet.",
1053
1429
  "status.switches.error": "Switch history read failed: {message}",
1054
1430
  "status.switches.compact": "last {count} · {from} → {to} ({role} · {reason})",
1431
+ "status.switches.compact.roleInject": "last {count} · {role} → {to} ({reason})",
1055
1432
  "status.switches.reason.trigger-code": "trigger code",
1056
1433
  "status.switches.reason.always-cap": "always-mode cap",
1057
- "status.selectionNote": "Note: a model manually selected in the web front end may be re-applied after a switch (the marker coordination shipped with the local patch has been removed).",
1434
+ "status.switches.reason.role-inject": "role inject",
1058
1435
  "general.title": "Model failover",
1059
1436
  "general.enabled": "Enabled",
1060
1437
  "general.disabled": "Disabled",
1061
1438
  "general.unknown": "Unknown",
1062
1439
  "general.unavailable": "Status channel unavailable",
1063
1440
  "general.switch": "Last switch: {from} → {to} ({role} · {reason})",
1441
+ "general.switch.roleInject": "Last switch: {role} → {to} ({reason})",
1064
1442
  "general.switch.empty": "No switches this session",
1065
1443
  "general.error": "Status read failed: {message}",
1066
1444
  "chat.switch.title": "Model downgraded",
1067
- "chat.switch.summary": "{from} → {to} ({role} · {reason})",
1445
+ "chat.switch.summary": "{from} → {to} ({reason})",
1446
+ "chat.switch.summary.roleInject": "({reason})",
1447
+ "chat.switch.roleMap": "{role} → {model}",
1068
1448
  "defaults.prefix": "Default",
1069
1449
  "save": "Save",
1070
1450
  "save.saving": "Saving…",
1071
1451
  "save.error": "Save failed: {message}",
1072
1452
  "close": "Close",
1073
- "reset": "Reset to defaults",
1074
- "reset.confirmTitle": "Reset to defaults",
1075
- "reset.confirm": "Resetting restores the fallbacks configuration to plugin defaults; your current edits will be lost.",
1076
- "reset.confirm.cancel": "Cancel",
1077
- "reset.confirm.action": "Reset",
1078
- "reset.saving": "Resetting…",
1079
1453
  "loading": "Loading…",
1080
1454
  "unavailable": "The fallbacks config channel is unreachable: showing the default configuration (or the last read value). You can try to save; failures will be reported here.",
1081
1455
  "error.generic": "Error: {message}"
@@ -1084,13 +1458,17 @@ window.__ModuleLoader__.load({
1084
1458
  const NS = "fallbacks";
1085
1459
  /**
1086
1460
  * Reason → locale key map for switch summaries (S-c; shared by the card's
1087
- * status block and the General page status row). The session log is durable
1088
- * and forward-compatible: a reason value outside the current union (a newer
1089
- * plugin wrote it) renders raw instead of falling into a binary else branch.
1461
+ * status block, the General page status row, and the conversation node). All
1462
+ * reasons resolve from ONE key family (`status.switches.reason.*`) — the
1463
+ * shared reason vocabulary must not mix dictionary families (qc1 F-004). The
1464
+ * session log is durable and forward-compatible: a reason value outside the
1465
+ * current union (a newer plugin wrote it) renders raw instead of falling
1466
+ * into a binary else branch.
1090
1467
  */
1091
1468
  const SWITCH_REASON_KEYS = {
1092
1469
  "trigger-code": "status.switches.reason.trigger-code",
1093
- "always-cap": "status.switches.reason.always-cap"
1470
+ "always-cap": "status.switches.reason.always-cap",
1471
+ "role-inject": "status.switches.reason.role-inject"
1094
1472
  };
1095
1473
  /** Human-readable trigger-code labels (spec §4 用户直观性). */
1096
1474
  const TRIGGER_CODE_LABELS = {
@@ -1114,7 +1492,7 @@ window.__ModuleLoader__.load({
1114
1492
  }
1115
1493
  //#endregion
1116
1494
  //#region \0dsh-css:/home/runner/work/dsh-llm-fallbacks/dsh-llm-fallbacks/src/client/FallbacksCard.module.css.mjs
1117
- const css$2 = "\n\n\n._8827595f_card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n\n._8827595f_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._06704203_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._e488d460_header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n\n._e488d460_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n\n._f5dfe084_headText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n._8d39bde6_name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n\n._346f3b69_description {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._631094a0_chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n\n._44836ce8_chevronOpen {\n transform: rotate(180deg);\n}\n\n\n._dbaa7975_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding-bottom: 8px;\n}\n\n._4e39cf17_readOnly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._99bf7f3c_pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n\n._114985b2_footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n\n\n._9860a5b1_notice {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n\n._5006d43e_legacyNotice {\n margin: 12px 0 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n._21918751_error {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._7cea4b2f_noticeRow {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 12px 0 0;\n}\n\n._7cea4b2f_noticeRow ._21918751_error {\n flex: 1;\n min-width: 0;\n margin: 0;\n}\n\n\n._4058c747_form {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 0 0;\n}\n\n\n._9998065c_checkboxRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n._02c2f6df_checkLabel {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 14px;\n line-height: 22px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._f2d47237_checkLabelTitle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 400;\n}\n\n._c1bed8ea_checkLabelDesc {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._06389b18_checkbox {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._94c91bfd_fieldset {\n margin: 0;\n padding: 0;\n border: none;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n\n._67826267_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding: 0;\n border: none;\n min-width: 0;\n}\n\n._13e68c3f_fieldLabel {\n \n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._b116c758_sectionToggle {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 0;\n cursor: pointer;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n._b116c758_sectionToggle:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n\n._b116c758_sectionToggle:disabled {\n cursor: default;\n}\n\n._b116c758_sectionToggle:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 6px;\n}\n\n._a5313fdf_sectionToggleText {\n flex: 1;\n min-width: 0;\n text-align: left;\n}\n\n._4bc809b8_hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._579f813a_defaultNote {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._a6244318_infoHint {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n font-size: 14px;\n line-height: 1;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-interactive-bg-hover);\n cursor: help;\n user-select: none;\n}\n\n._a6244318_infoHint:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._e655c840_infoHintDisabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._a2521bb0_optionRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._a2521bb0_optionRow:hover:has(input:not(:disabled)) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n\n._06389b18_checkbox,\n._a2521bb0_optionRow input {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:focus-visible,\n._a2521bb0_optionRow input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._a2521bb0_optionRow input:disabled {\n cursor: default;\n}\n\n\n._f9d86f7b_input {\n box-sizing: border-box;\n width: 100%;\n height: 32px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n}\n\n._f9d86f7b_input:focus {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n\n._f9d86f7b_input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n._f9d86f7b_input:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n\n._3a9b42fd_inputTextarea {\n height: auto;\n min-height: 72px;\n padding: 6px 10px;\n resize: vertical;\n line-height: 20px;\n}\n\n\n._7b60337a_inputInvalid,\n._7b60337a_inputInvalid:focus {\n border-color: var(--dsw-alias-state-error-primary);\n}\n\n\nselect._f9d86f7b_input {\n max-width: 240px;\n cursor: pointer;\n}\n\n\n._16eb7153_selectInput {\n appearance: none;\n padding-right: 32px;\n \n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n background-size: 12px 12px;\n}\n\n\n._3e95886d_numberFields {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n}\n\n._0cfb5881_list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n}\n\n\n._f5c620d6_editorCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 14px 16px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-module-platform);\n}\n\n._224b1387_cardFoot {\n display: flex;\n justify-content: flex-end;\n}\n\n._8e220e07_ruleGrid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n._24a12a2f_ruleCell {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 120px;\n flex: 1;\n}\n\n._1014c097_ruleCellLabel {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._15a12620_chainSelectors {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n._240cbbf8_selectorRow {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n\n._70effa64_iconButton {\n position: relative;\n box-sizing: border-box;\n appearance: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: none;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n\n._70effa64_iconButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._70effa64_iconButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n._70effa64_iconButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._70effa64_iconButton::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n padding: 3px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n font-size: 11px;\n line-height: 17px;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity .12s;\n}\n\n._70effa64_iconButton:hover::after,\n._70effa64_iconButton:focus-visible::after {\n opacity: 1;\n}\n\n\n._a0968257_iconButtonDanger:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._28202c90_addButton {\n align-self: flex-start;\n}\n\n\n._747fd56d_primaryButton,\n._7e855445_secondaryButton {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 18px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n}\n\n._747fd56d_primaryButton {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n._747fd56d_primaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n._7e855445_secondaryButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n\n._7e855445_secondaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n\n._747fd56d_primaryButton:disabled,\n._7e855445_secondaryButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._747fd56d_primaryButton:focus-visible,\n._7e855445_secondaryButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._fbd045e0_statusBlock {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 14px;\n margin-top: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n}\n\n._d4f70367_statusTitle {\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._8ab300ed_statusLine {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n._e8cedbf5_statusLineLabel {\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._b48dfcba_offNotice {\n margin: 0;\n padding: 12px;\n border: 1px dashed var(--dsw-alias-border-l3);\n border-radius: 8px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n text-align: center;\n}\n\n\n._ec8bd970_resetDialog {\n width: min(480px, 100%);\n}\n\n._8677165c_confirmDanger:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n._8677165c_confirmDanger:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n}\n\n@media (prefers-reduced-motion: reduce) {\n ._70effa64_iconButton::after {\n transition: none;\n }\n}\n";
1495
+ const css$2 = "\n\n\n._8827595f_card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n\n._8827595f_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._06704203_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n\n._e488d460_header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n\n._e488d460_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n\n._f5dfe084_headText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n._8d39bde6_name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n\n._346f3b69_description {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._631094a0_chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n\n._44836ce8_chevronOpen {\n transform: rotate(180deg);\n}\n\n\n._dbaa7975_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding-bottom: 8px;\n}\n\n._4e39cf17_readOnly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._99bf7f3c_pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n\n\n\n\n._9860a5b1_notice {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n\n._5006d43e_legacyNotice {\n margin: 12px 0 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-module-platform);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-business-primary);\n}\n\n._21918751_error {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._7cea4b2f_noticeRow {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 12px 0 0;\n}\n\n._7cea4b2f_noticeRow ._21918751_error {\n flex: 1;\n min-width: 0;\n margin: 0;\n}\n\n\n._4058c747_form {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 0 0;\n}\n\n\n._9998065c_checkboxRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n._02c2f6df_checkLabel {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 14px;\n line-height: 22px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._f2d47237_checkLabelTitle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 400;\n}\n\n._c1bed8ea_checkLabelDesc {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._06389b18_checkbox {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._94c91bfd_fieldset {\n margin: 0;\n padding: 0;\n border: none;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n\n._67826267_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding: 0;\n border: none;\n min-width: 0;\n}\n\n._13e68c3f_fieldLabel {\n \n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._ba240d82_sectionHeading {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n._ddbc1b49_sectionHeadingText {\n flex: 1;\n min-width: 0;\n}\n\n\n._c41f9db7_sectionActions {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n}\n\n\n._b116c758_sectionToggle {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 0;\n cursor: pointer;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n._b116c758_sectionToggle:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n}\n\n._b116c758_sectionToggle:disabled {\n cursor: default;\n}\n\n._b116c758_sectionToggle:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 6px;\n}\n\n._a5313fdf_sectionToggleText {\n flex: 1;\n min-width: 0;\n text-align: left;\n}\n\n._4bc809b8_hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n._579f813a_defaultNote {\n font-size: 12px;\n line-height: 18px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._b1962759_slotPresetName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n\n._a6244318_infoHint {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n font-size: 14px;\n line-height: 1;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-interactive-bg-hover);\n cursor: help;\n user-select: none;\n}\n\n._a6244318_infoHint:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._e655c840_infoHintDisabled {\n opacity: 0.4;\n cursor: default;\n}\n\n\n._a2521bb0_optionRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n._a2521bb0_optionRow:hover:has(input:not(:disabled)) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n\n._06389b18_checkbox,\n._a2521bb0_optionRow input {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._06389b18_checkbox:focus-visible,\n._a2521bb0_optionRow input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._a2521bb0_optionRow input:disabled {\n cursor: default;\n}\n\n\n._c4ecbdf3_dayRow {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n._0330c1ed_dayCell {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n border-radius: 6px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n._0330c1ed_dayCell:hover:has(input:not(:disabled)) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n._0330c1ed_dayCell input {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n._0330c1ed_dayCell input:disabled {\n cursor: default;\n}\n\n._0330c1ed_dayCell input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._f9d86f7b_input {\n box-sizing: border-box;\n width: 100%;\n height: 32px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n}\n\n._f9d86f7b_input:focus {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n\n._f9d86f7b_input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n._f9d86f7b_input:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n\n._3a9b42fd_inputTextarea {\n height: auto;\n min-height: 72px;\n padding: 6px 10px;\n resize: vertical;\n line-height: 20px;\n}\n\n\n._7b60337a_inputInvalid,\n._7b60337a_inputInvalid:focus {\n border-color: var(--dsw-alias-state-error-primary);\n}\n\n\nselect._f9d86f7b_input {\n max-width: 240px;\n cursor: pointer;\n}\n\n\n._16eb7153_selectInput {\n appearance: none;\n padding-right: 32px;\n \n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n background-size: 12px 12px;\n}\n\n\n._3e95886d_numberFields {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n}\n\n._0cfb5881_list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n}\n\n\n._f5c620d6_editorCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 14px 16px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-module-platform);\n}\n\n._224b1387_cardFoot {\n display: flex;\n justify-content: flex-end;\n}\n\n._8e220e07_ruleGrid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n._24a12a2f_ruleCell {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 120px;\n flex: 1;\n}\n\n\n._86bc52d2_collapseRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n._a73efcc6_collapseToggle {\n appearance: none;\n border: 0;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n font: inherit;\n text-align: left;\n}\n\n._a73efcc6_collapseToggle:disabled {\n cursor: default;\n}\n\n._af017718_collapseTitle {\n \n flex: none;\n max-width: 55%;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n._2cabe603_collapseMeta {\n \n flex: none;\n margin-left: auto;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n._7b04f727_slotTag {\n flex: none;\n display: inline-flex;\n align-items: center;\n border-radius: 999px;\n padding: 0 6px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n border: 1px solid;\n}\n\n._c381e792_slotTagHighCost {\n color: var(--dsw-alias-state-error-primary);\n border-color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n._23bdb6fc_slotTagMultiplier {\n color: var(--dsw-alias-state-warn-primary);\n border-color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent);\n}\n\n._f000eed9_slotTagActive {\n color: var(--dsw-alias-state-success-primary);\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);\n}\n\n\n._cd905ce5_dragHandle {\n appearance: none;\n border: 0;\n background: none;\n padding: 0;\n cursor: grab;\n color: var(--dsw-alias-label-tertiary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 6px;\n flex: none;\n}\n\n._cd905ce5_dragHandle:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n._cd905ce5_dragHandle:active {\n cursor: grabbing;\n}\n\n._cd905ce5_dragHandle:disabled {\n cursor: default;\n opacity: 0.4;\n}\n\n._cd905ce5_dragHandle:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._49f0c4f0_dragHandleIcon {\n transform: rotate(90deg);\n}\n\n\n._fee63b7e_slotCardDragging {\n opacity: 0.45;\n}\n\n._6f108491_slotCardOver {\n outline: 1px dashed var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n._1014c097_ruleCellLabel {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._15a12620_chainSelectors {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n._240cbbf8_selectorRow {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n\n._70effa64_iconButton {\n position: relative;\n box-sizing: border-box;\n appearance: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: none;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n\n._70effa64_iconButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._70effa64_iconButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n._70effa64_iconButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n._70effa64_iconButton::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n padding: 3px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n font-size: 11px;\n line-height: 17px;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity .12s;\n}\n\n._70effa64_iconButton:hover::after,\n._70effa64_iconButton:focus-visible::after {\n opacity: 1;\n}\n\n\n._a0968257_iconButtonDanger:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n\n._28202c90_addButton {\n align-self: flex-start;\n}\n\n\n._6a2bd54c_slotAddRow {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 4px;\n flex-wrap: wrap;\n}\n\n\n._4e77523e_rowActions {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n\n._747fd56d_primaryButton,\n._7e855445_secondaryButton {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 36px;\n padding: 0 14px;\n border: none;\n border-radius: 18px;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n cursor: pointer;\n}\n\n._747fd56d_primaryButton {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n._747fd56d_primaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n._7e855445_secondaryButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n\n._7e855445_secondaryButton:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n\n._747fd56d_primaryButton:disabled,\n._7e855445_secondaryButton:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n._747fd56d_primaryButton:focus-visible,\n._7e855445_secondaryButton:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n\n\n._238d19be_sectionAction {\n height: 28px;\n padding: 0 12px;\n border-radius: 14px;\n font-size: 12px;\n line-height: 18px;\n}\n\n\n._fbd045e0_statusBlock {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px 14px;\n margin-top: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n}\n\n._d4f70367_statusTitle {\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._8ab300ed_statusLine {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n._e8cedbf5_statusLineLabel {\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n\n._b48dfcba_offNotice {\n margin: 0;\n padding: 12px;\n border: 1px dashed var(--dsw-alias-border-l3);\n border-radius: 8px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n text-align: center;\n}\n\n@media (prefers-reduced-motion: reduce) {\n ._70effa64_iconButton::after {\n transition: none;\n }\n}\n";
1118
1496
  const tagId$2 = "dsh-llm-fallbacks/FallbacksCard.module.css";
1119
1497
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
1120
1498
  const tag = document.createElement("style");
@@ -1135,7 +1513,6 @@ window.__ModuleLoader__.load({
1135
1513
  "body": "_dbaa7975_body",
1136
1514
  "readOnly": "_4e39cf17_readOnly",
1137
1515
  "pending": "_99bf7f3c_pending",
1138
- "footer": "_114985b2_footer",
1139
1516
  "notice": "_9860a5b1_notice",
1140
1517
  "legacyNotice": "_5006d43e_legacyNotice",
1141
1518
  "error": "_21918751_error",
@@ -1149,13 +1526,19 @@ window.__ModuleLoader__.load({
1149
1526
  "fieldset": "_94c91bfd_fieldset",
1150
1527
  "field": "_67826267_field",
1151
1528
  "fieldLabel": "_13e68c3f_fieldLabel",
1529
+ "sectionHeading": "_ba240d82_sectionHeading",
1530
+ "sectionHeadingText": "_ddbc1b49_sectionHeadingText",
1531
+ "sectionActions": "_c41f9db7_sectionActions",
1152
1532
  "sectionToggle": "_b116c758_sectionToggle",
1153
1533
  "sectionToggleText": "_a5313fdf_sectionToggleText",
1154
1534
  "hint": "_4bc809b8_hint",
1155
1535
  "defaultNote": "_579f813a_defaultNote",
1536
+ "slotPresetName": "_b1962759_slotPresetName",
1156
1537
  "infoHint": "_a6244318_infoHint",
1157
1538
  "infoHintDisabled": "_e655c840_infoHintDisabled",
1158
1539
  "optionRow": "_a2521bb0_optionRow",
1540
+ "dayRow": "_c4ecbdf3_dayRow",
1541
+ "dayCell": "_0330c1ed_dayCell",
1159
1542
  "input": "_f9d86f7b_input",
1160
1543
  "inputTextarea": "_3a9b42fd_inputTextarea",
1161
1544
  "inputInvalid": "_7b60337a_inputInvalid",
@@ -1166,29 +1549,43 @@ window.__ModuleLoader__.load({
1166
1549
  "cardFoot": "_224b1387_cardFoot",
1167
1550
  "ruleGrid": "_8e220e07_ruleGrid",
1168
1551
  "ruleCell": "_24a12a2f_ruleCell",
1552
+ "collapseRow": "_86bc52d2_collapseRow",
1553
+ "collapseToggle": "_a73efcc6_collapseToggle",
1554
+ "collapseTitle": "_af017718_collapseTitle",
1555
+ "collapseMeta": "_2cabe603_collapseMeta",
1556
+ "slotTag": "_7b04f727_slotTag",
1557
+ "slotTagHighCost": "_c381e792_slotTagHighCost",
1558
+ "slotTagMultiplier": "_23bdb6fc_slotTagMultiplier",
1559
+ "slotTagActive": "_f000eed9_slotTagActive",
1560
+ "dragHandle": "_cd905ce5_dragHandle",
1561
+ "dragHandleIcon": "_49f0c4f0_dragHandleIcon",
1562
+ "slotCardDragging": "_fee63b7e_slotCardDragging",
1563
+ "slotCardOver": "_6f108491_slotCardOver",
1169
1564
  "ruleCellLabel": "_1014c097_ruleCellLabel",
1170
1565
  "chainSelectors": "_15a12620_chainSelectors",
1171
1566
  "selectorRow": "_240cbbf8_selectorRow",
1172
1567
  "iconButton": "_70effa64_iconButton",
1173
1568
  "iconButtonDanger": "_a0968257_iconButtonDanger",
1174
1569
  "addButton": "_28202c90_addButton",
1570
+ "slotAddRow": "_6a2bd54c_slotAddRow",
1571
+ "rowActions": "_4e77523e_rowActions",
1175
1572
  "primaryButton": "_747fd56d_primaryButton",
1176
1573
  "secondaryButton": "_7e855445_secondaryButton",
1574
+ "sectionAction": "_238d19be_sectionAction",
1177
1575
  "statusBlock": "_fbd045e0_statusBlock",
1178
1576
  "statusTitle": "_d4f70367_statusTitle",
1179
1577
  "statusLine": "_8ab300ed_statusLine",
1180
1578
  "statusLineLabel": "_e8cedbf5_statusLineLabel",
1181
- "offNotice": "_b48dfcba_offNotice",
1182
- "resetDialog": "_ec8bd970_resetDialog",
1183
- "confirmDanger": "_8677165c_confirmDanger"
1579
+ "offNotice": "_b48dfcba_offNotice"
1184
1580
  };
1185
1581
  //#endregion
1186
1582
  //#region src/client/FallbacksCard.tsx
1187
1583
  /**
1188
1584
  * Fallbacks settings card — the `fallbacks` plugin card on the web settings
1189
- * "插件配置" page (spec §4). Registered into the `settings.plugin.item` slot
1190
- * (id `fallbacks`, order 30, alongside the upstream bash/agent-loop/web-search
1191
- * cards and the advisor card); owner props are empty and all data flows
1585
+ * "插件配置" page (spec §4). Registered into the `settings.plugin.item` keyed
1586
+ * slot (key `fallbacks`, the settings namespace the card edits, alongside
1587
+ * the upstream bash/agent-loop/web-search cards and the advisor card, in
1588
+ * registration order); owner props are empty and all data flows
1192
1589
  * through {@link FallbacksSettingsController}.
1193
1590
  *
1194
1591
  * The card chrome replicates the upstream `PluginCard` contract (self-drawn:
@@ -1197,12 +1594,22 @@ window.__ModuleLoader__.load({
1197
1594
  * description, with a dirty "unsaved" pill and a rotating chevron
1198
1595
  * (`IconChevronDownOutline14` from ui-primitives — a CLIENT_EXTERNALS value
1199
1596
  * import), `aria-expanded`/`aria-label` like the upstream header; a divider
1200
- * under the header; then the form content; then a footer with
1201
- * Discard / Reset / Save carrying the upstream disabled semantics — save =
1202
- * `!dirty || saving || !writable`, discard = `!dirty || saving` (KD-U1).
1203
- * Disclosure is card-local state: which card a user has open is a reading
1204
- * gesture, and staged edits outlive collapsing — the pill rides the header
1205
- * (upstream rationale).
1597
+ * under the header; then the form content. PR #62 UX round 2: the card
1598
+ * footer is gone — each big section (主代理 / 子代理 / 高级选项) carries its
1599
+ * own Save/Discard actions beside its heading (高级选项: inside the expanded
1600
+ * body) and its own validation / save-error surface. PR #62 UX round 3:
1601
+ * each section's Save writes ONLY that section's fields — 主代理 owns
1602
+ * rootChain / timeSlots / tz (+ the card-level `enabled`), 子代理 owns
1603
+ * roles, 高级选项 owns the advanced scalars; the patch spreads the last
1604
+ * ACCEPTED config for every other section, so a 主代理 Save can never
1605
+ * ride along an unsaved 子代理 edit (and vice versa) — and validation /
1606
+ * the dirty gate apply per section too (a bad role id never blocks 主代理,
1607
+ * and only the saved section's Discard reverts that section's edits).
1608
+ * Save/discard disabled terms: save = `!sectionDirty || saving ||
1609
+ * !writable`, discard = `!sectionDirty || saving` (KD-U1). Disclosure is
1610
+ * card-local state:
1611
+ * which card a user has open is a reading gesture, and staged edits outlive
1612
+ * collapsing — the pill rides the header (upstream rationale).
1206
1613
  *
1207
1614
  * The form body is the two-block editing surface (spec §8): the `enabled`
1208
1615
  * checkbox row, the 6 top-level scalar fields (trigger codes / revert
@@ -1218,13 +1625,14 @@ window.__ModuleLoader__.load({
1218
1625
  * non-empty `state.legacyKeys` renders the migration banner at the top of
1219
1626
  * the card body. The row editors keep their filled editorCard surface
1220
1627
  * inside the card, with `--dsw-alias-*` tokens throughout. The reset-
1221
- * to-defaults confirmation stays a `Modal` (the delete-confirm pattern of
1222
- * the Models page) — no `window.confirm`.
1628
+ * to-defaults affordance is GONE from the card (PR #62 UX round 3) — the
1629
+ * gateway RPC `fallbacks/reset` and the store `resetToDefaults()` stay as
1630
+ * host APIs (store/gateway tests unchanged), only the card UI was removed.
1223
1631
  *
1224
1632
  * The page-only chrome is gone (720px column wrapper, title/intro banners,
1225
1633
  * page-bottom status block): the AC-7 read-only status (derived effective
1226
- * model + recent-switch summary) is folded into the card body above the
1227
- * footer, and the plugin-config section owns the column width.
1634
+ * model + recent-switch summary) is folded into the card body, and the
1635
+ * plugin-config section owns the column width.
1228
1636
  *
1229
1637
  * Degraded/error/loading states keep the same card chrome (KD-U3): the
1230
1638
  * header always renders title+description+chevron, and the body carries the
@@ -1238,8 +1646,11 @@ window.__ModuleLoader__.load({
1238
1646
  * 'error'`) also forces the body open with an error notice and — when the
1239
1647
  * form is inert (`!writable`, i.e. the load never landed) — a Retry button;
1240
1648
  * a save failure keeps the editable form so the Save action itself is the
1241
- * retry (the single `state.error` surface covers both, unlike the advisor's
1242
- * separate apply-failure hints).
1649
+ * retry. PR #62 UX round 2: the single `state.error` surface is split by
1650
+ * origin — a LOAD failure keeps the card-top notice (with Retry when
1651
+ * inert), while a WRITE failure renders under the section whose Save was
1652
+ * last clicked (`lastSaveSection`), unlike the advisor's separate
1653
+ * apply-failure hints.
1243
1654
  *
1244
1655
  * The degraded derivation is latched in the card (the store stays untouched):
1245
1656
  * `present` only ever changes inside the store's `accept()`, so the settled
@@ -1249,6 +1660,77 @@ window.__ModuleLoader__.load({
1249
1660
  * implemented without a store change); on a first mount the latch is false,
1250
1661
  * so the healthy card starts (and stays) collapsed through its first load.
1251
1662
  */
1663
+ const ALL_DAY_FLASH = "deepseek-official/deepseek-v4-flash";
1664
+ const ALL_DAY_PRO = "deepseek-official/deepseek-v4-pro";
1665
+ const SLOT_PRESET_IDS = [
1666
+ "liang-peak",
1667
+ "liang-valley",
1668
+ "glm-peak",
1669
+ "glm-valley"
1670
+ ];
1671
+ /** Custom-row day toggle order (index = weekday, 0=Sunday); display copy lives in the dictionaries. */
1672
+ const SLOT_WEEKDAYS = [
1673
+ "sun",
1674
+ "mon",
1675
+ "tue",
1676
+ "wed",
1677
+ "thu",
1678
+ "fri",
1679
+ "sat"
1680
+ ];
1681
+ /** IANA timezone of this renderer (browser / host). */
1682
+ function hostTimeZone() {
1683
+ try {
1684
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
1685
+ return typeof tz === "string" && tz !== "" ? tz : "UTC";
1686
+ } catch {
1687
+ return "UTC";
1688
+ }
1689
+ }
1690
+ /** `UTC+8` / `UTC-4` for an IANA id (current offset, DST-honest). */
1691
+ function tzUtcOffset(tz) {
1692
+ try {
1693
+ const name = new Intl.DateTimeFormat("en-US", {
1694
+ timeZone: tz,
1695
+ timeZoneName: "shortOffset"
1696
+ }).formatToParts(/* @__PURE__ */ new Date()).find((part) => part.type === "timeZoneName")?.value;
1697
+ if (name === void 0 || name === "") return "";
1698
+ return name.replace(/^GMT/, "UTC");
1699
+ } catch {
1700
+ return "";
1701
+ }
1702
+ }
1703
+ /** Read-only custom-row copy: `Asia/Shanghai (UTC+8)`. */
1704
+ function tzDisplayLabel(tz) {
1705
+ const offset = tzUtcOffset(tz);
1706
+ return offset === "" ? tz : `${tz} (${offset})`;
1707
+ }
1708
+ /** Persist tz: presets lock UTC+8; custom-only uses the host zone; else keep the accepted value. */
1709
+ function resolvedSlotTz(rows, fallback) {
1710
+ if (rows.some((row) => row.kind === "preset")) return "Asia/Shanghai";
1711
+ if (rows.some((row) => row.kind === "custom")) return hostTimeZone();
1712
+ return fallback === "" ? "Asia/Shanghai" : fallback;
1713
+ }
1714
+ /** Strict 24h `HH:mm` — the resolver's HHMM_RE twin (drift-guarded by the gateway reject-on-save). */
1715
+ const HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
1716
+ /**
1717
+ * The 默认模型 value for a chain: the official V4 id when the chain TAIL
1718
+ * is that model (Flash XOR Pro — leading 默认降级链 entries allowed);
1719
+ * `''` for an empty chain or a chain whose last entry is not official
1720
+ * (the panel reads back unselected and save validation blocks the value).
1721
+ */
1722
+ function allDayModelOf(chain) {
1723
+ const tail = chain.length >= 1 ? chain[chain.length - 1] : void 0;
1724
+ return tail === ALL_DAY_FLASH || tail === ALL_DAY_PRO ? tail : "";
1725
+ }
1726
+ /**
1727
+ * The 默认降级链 editor row: the leading entries BEFORE the official-V4
1728
+ * tail, or the whole chain while the tail is not official (the draft
1729
+ * rides the accepted value until a 默认模型 pick).
1730
+ */
1731
+ function allDayChainRowOf(chain, catalog) {
1732
+ return rootChainToRows(allDayModelOf(chain) === "" ? chain : chain.slice(0, -1), catalog)[0];
1733
+ }
1252
1734
  /** Split scalars from the row editors (rootChain / role entities / role rules). */
1253
1735
  function scalarsOf(config) {
1254
1736
  return {
@@ -1257,7 +1739,9 @@ window.__ModuleLoader__.load({
1257
1739
  cooldownMs: config.cooldownMs,
1258
1740
  revertPolicy: config.revertPolicy,
1259
1741
  maxSwitchesPerStep: config.maxSwitchesPerStep,
1260
- alwaysModeRetryCap: config.alwaysModeRetryCap
1742
+ alwaysModeRetryCap: config.alwaysModeRetryCap,
1743
+ roleAutoMatch: config.roleAutoMatch,
1744
+ tz: config.tz ?? "Asia/Shanghai"
1261
1745
  };
1262
1746
  }
1263
1747
  /**
@@ -1269,13 +1753,28 @@ window.__ModuleLoader__.load({
1269
1753
  * the top level: no presets UI this iteration (R-001 re-defer), so the
1270
1754
  * draft carries the accepted value through untouched — a clean draft stays
1271
1755
  * equal to the accepted config and a save never drops the key.
1756
+ * `roleAutoMatch` follows the same rule (config-model mirror of `presets`):
1757
+ * the draft carries the scalar's value through untouched. The scalar is
1758
+ * ALWAYS defined — the gateway composition resolves the schema default
1759
+ * `true` even for a legacy config that never declared the key — so the
1760
+ * toggle always renders (default on) and a save persists the resolved value
1761
+ * (AC-7 re-scope, PM decision 2026-08-17 Option A).
1762
+ *
1763
+ * All-day: rootChain is composed from the 默认降级链 editor's leading
1764
+ * selectors plus the 默认模型 tail (exactly one official V4 — Flash XOR
1765
+ * Pro). While no tail is selected the ACCEPTED chain rides through
1766
+ * untouched. `timeSlots` is rebuilt from the slot rows every render. `tz`
1767
+ * is a card scalar: preset rows lock it to Asia/Shanghai; custom rows
1768
+ * follow the selected timezone.
1272
1769
  */
1273
- function assembleConfig(scalars, rootChainRows, roleRows, ruleRows, originalRoles, presets) {
1770
+ function assembleConfig(scalars, allDayModel, acceptedRootChain, allDayChainRow, roleRows, ruleRows, originalRoles, presets, roleAutoMatch, timeSlotRows) {
1274
1771
  const list = mergeRoleExtras(roleRows, originalRoles);
1772
+ const trailingChain = rowsToRootChain([allDayChainRow]);
1773
+ const tz = resolvedSlotTz(timeSlotRows, scalars.tz);
1275
1774
  return {
1276
1775
  enabled: scalars.enabled,
1277
1776
  triggerCodes: [...scalars.triggerCodes],
1278
- rootChain: rowsToRootChain(rootChainRows),
1777
+ rootChain: allDayModel === "" ? [...acceptedRootChain] : [...trailingChain, allDayModel],
1279
1778
  roles: {
1280
1779
  list,
1281
1780
  rules: rowsToRules(ruleRows)
@@ -1284,7 +1783,18 @@ window.__ModuleLoader__.load({
1284
1783
  revertPolicy: scalars.revertPolicy,
1285
1784
  maxSwitchesPerStep: scalars.maxSwitchesPerStep,
1286
1785
  alwaysModeRetryCap: scalars.alwaysModeRetryCap,
1287
- ...presets === void 0 ? {} : { presets }
1786
+ ...presets === void 0 ? {} : { presets },
1787
+ roleAutoMatch,
1788
+ timeSlots: rowsToTimeSlots(timeSlotRows),
1789
+ tz
1790
+ };
1791
+ }
1792
+ /** An empty per-section validation-error record (the clean-draft shape). */
1793
+ function emptyValidationErrors() {
1794
+ return {
1795
+ main: [],
1796
+ sub: [],
1797
+ advanced: []
1288
1798
  };
1289
1799
  }
1290
1800
  /**
@@ -1292,9 +1802,14 @@ window.__ModuleLoader__.load({
1292
1802
  * role id format/reserved word/duplicates, undeclared rule role references
1293
1803
  * (only reachable through the synthetic outside option — the dropdown
1294
1804
  * itself constrains normal edits), and illegal selector entries in
1295
- * rootChain and role chains. Returns one localized message per violation;
1296
- * a non-empty result blocks {@link save} — the draft is never written.
1297
- * `persona` is free text and never validated.
1805
+ * rootChain and role chains. Returns one localized message per violation,
1806
+ * bucketed by the section that owns the offending field (PR #62 UX round
1807
+ * 2 — 主代理: allDay / timeSlots / slot* / tz / default model / default
1808
+ * chain; 子代理: role* / rule*; 高级选项: trigger / cooldown / revert /
1809
+ * always / roleAutoMatch — the scalars are never validated, so the
1810
+ * advanced bucket stays empty today). A non-empty result blocks
1811
+ * {@link save} — the draft is never written. `persona` is free text and
1812
+ * never validated.
1298
1813
  *
1299
1814
  * `seededIds` is the live trimmed-id → overridden map derived from
1300
1815
  * `state.seeds` (spec §9.4): the empty-chain block relaxes for seeded ids
@@ -1303,33 +1818,57 @@ window.__ModuleLoader__.load({
1303
1818
  * behavior is byte-identical.
1304
1819
  */
1305
1820
  function validateDraft(draft, t, seededIds) {
1306
- const errors = [];
1821
+ const errors = emptyValidationErrors();
1307
1822
  const declaredIds = /* @__PURE__ */ new Set();
1308
1823
  for (const role of draft.roles.list) {
1309
- if (!ROLE_ID_PATTERN.test(role.id)) errors.push(t("validation.roleIdFormat", { id: role.id }));
1310
- if (role.id === "inherit") errors.push(t("validation.roleIdReserved"));
1311
- if (declaredIds.has(role.id)) errors.push(t("validation.roleIdDuplicate", { id: role.id }));
1824
+ if (!ROLE_ID_PATTERN.test(role.id)) errors.sub.push(t("validation.roleIdFormat", { id: role.id }));
1825
+ if (role.id === "inherit") errors.sub.push(t("validation.roleIdReserved"));
1826
+ if (declaredIds.has(role.id)) errors.sub.push(t("validation.roleIdDuplicate", { id: role.id }));
1312
1827
  declaredIds.add(role.id);
1313
1828
  for (const entry of role.chain ?? []) try {
1314
1829
  parseSelector(entry);
1315
1830
  } catch (error) {
1316
- errors.push(t("validation.selector", {
1831
+ errors.sub.push(t("validation.selector", {
1317
1832
  entry,
1318
1833
  message: error.message
1319
1834
  }));
1320
1835
  }
1321
- if ((role.chain ?? []).length === 0 && !seededIds.has(role.id.trim())) errors.push(t("validation.roleChainRequired", { id: role.id }));
1836
+ if ((role.chain ?? []).length === 0 && !seededIds.has(role.id.trim())) errors.sub.push(t("validation.roleChainRequired", { id: role.id }));
1322
1837
  }
1838
+ const allDayTail = draft.rootChain.length >= 1 ? draft.rootChain[draft.rootChain.length - 1] : void 0;
1839
+ if (allDayTail !== ALL_DAY_FLASH && allDayTail !== ALL_DAY_PRO) errors.main.push(t("validation.allDayRequired"));
1323
1840
  for (const entry of draft.rootChain) try {
1324
1841
  parseSelector(entry);
1325
1842
  } catch (error) {
1326
- errors.push(t("validation.selector", {
1843
+ errors.main.push(t("validation.selector", {
1327
1844
  entry,
1328
1845
  message: error.message
1329
1846
  }));
1330
1847
  }
1848
+ const seenSlotPresets = /* @__PURE__ */ new Set();
1849
+ for (const row of draft.timeSlots ?? []) {
1850
+ if (row.kind !== "preset" && row.kind !== "custom") errors.main.push(t("validation.slotKind"));
1851
+ if (row.kind === "preset") {
1852
+ if (typeof row.preset !== "string" || !SLOT_PRESET_IDS.includes(row.preset)) errors.main.push(t("validation.slotPresetUnknown", { preset: row.preset }));
1853
+ else if (seenSlotPresets.has(row.preset)) errors.main.push(t("validation.slotPresetDuplicate", { preset: row.preset }));
1854
+ else seenSlotPresets.add(row.preset);
1855
+ if (row.start !== void 0 || row.end !== void 0 || row.days !== void 0 && row.days.length > 0) errors.main.push(t("validation.slotPresetFrozen"));
1856
+ } else if (row.kind === "custom") {
1857
+ if (typeof row.start !== "string" || typeof row.end !== "string" || !HHMM_RE.test(row.start) || !HHMM_RE.test(row.end)) errors.main.push(t("validation.slotWindow"));
1858
+ if (row.days !== void 0 && row.days.some((day) => !Number.isInteger(day) || day < 0 || day > 6)) errors.main.push(t("validation.slotDays"));
1859
+ }
1860
+ for (const entry of row.chain) try {
1861
+ parseSelector(entry);
1862
+ } catch (error) {
1863
+ errors.main.push(t("validation.selector", {
1864
+ entry,
1865
+ message: error.message
1866
+ }));
1867
+ }
1868
+ if (row.chain.length === 0) errors.main.push(t("validation.slotChainRequired"));
1869
+ }
1331
1870
  const validTargets = /* @__PURE__ */ new Set([...declaredIds, INHERIT_ROLE_ID]);
1332
- for (const rule of draft.roles.rules) if (!validTargets.has(rule.role)) errors.push(t("validation.ruleRoleUndeclared", { role: rule.role }));
1871
+ for (const rule of draft.roles.rules) if (!validTargets.has(rule.role)) errors.sub.push(t("validation.ruleRoleUndeclared", { role: rule.role }));
1333
1872
  return errors;
1334
1873
  }
1335
1874
  /**
@@ -1358,6 +1897,20 @@ window.__ModuleLoader__.load({
1358
1897
  const parsed = Number.parseInt(raw, 10);
1359
1898
  return Number.isNaN(parsed) ? 0 : Math.max(0, parsed);
1360
1899
  }
1900
+ /**
1901
+ * Custom time-slot rows whose window is not valid `HH:mm` — drives the
1902
+ * inline red border after a blocked save attempt (same derivation pattern
1903
+ * as {@link collectInvalidRoleIds}: one pass per render, index lookup per
1904
+ * row).
1905
+ */
1906
+ function collectInvalidSlotRows(rows) {
1907
+ const invalid = /* @__PURE__ */ new Set();
1908
+ rows.forEach((row, index) => {
1909
+ if (row.kind === "preset") return;
1910
+ if (!HHMM_RE.test(row.start) || !HHMM_RE.test(row.end)) invalid.add(index);
1911
+ });
1912
+ return invalid;
1913
+ }
1361
1914
  /** The catalog faces the dropdowns classify against; undefined while unready. */
1362
1915
  function catalogOf(state) {
1363
1916
  return state.catalogStatus === "ready" ? {
@@ -1546,24 +2099,79 @@ window.__ModuleLoader__.load({
1546
2099
  if (snapshot.switchesStatus === "idle") controller.loadSwitches();
1547
2100
  }, [controller]);
1548
2101
  const [scalars, setScalars] = (0, react.useState)(() => scalarsOf(defaultFallbacksConfig));
1549
- const [rootChainRows, setRootChainRows] = (0, react.useState)(() => rootChainToRows(defaultFallbacksConfig.rootChain));
2102
+ const [allDayModel, setAllDayModel] = (0, react.useState)(() => allDayModelOf(defaultFallbacksConfig.rootChain));
2103
+ const [allDayChainRow, setAllDayChainRow] = (0, react.useState)(() => allDayChainRowOf(defaultFallbacksConfig.rootChain, void 0));
2104
+ const [timeSlotRows, setTimeSlotRows] = (0, react.useState)(() => timeSlotsToRows(defaultFallbacksConfig.timeSlots ?? []));
1550
2105
  const [roleRows, setRoleRows] = (0, react.useState)(() => rolesToRows(defaultFallbacksConfig.roles.list));
1551
2106
  const [ruleRows, setRuleRows] = (0, react.useState)(() => rulesToRows(defaultFallbacksConfig.roles.rules));
1552
- const [validationErrors, setValidationErrors] = (0, react.useState)([]);
2107
+ const [presetToAdd, setPresetToAdd] = (0, react.useState)("");
2108
+ const [validationErrors, setValidationErrors] = (0, react.useState)(emptyValidationErrors);
1553
2109
  const [validationAttempted, setValidationAttempted] = (0, react.useState)(false);
2110
+ const [lastSaveSection, setLastSaveSection] = (0, react.useState)(null);
1554
2111
  const seededConfigKey = (0, react.useRef)(null);
2112
+ const draft = assembleConfig(scalars, allDayModel, state.config.rootChain, allDayChainRow, roleRows, ruleRows, state.config.roles.list, state.config.presets, scalars.roleAutoMatch, timeSlotRows);
2113
+ const hasEmptyRuleRows = ruleRows.some((row) => row.role === "");
2114
+ const enabledDirty = scalars.enabled !== state.config.enabled;
2115
+ const mainDirty = enabledDirty || JSON.stringify([...draft.rootChain, draft.timeSlots]) !== JSON.stringify([...state.config.rootChain, state.config.timeSlots ?? []]);
2116
+ const subDirty = hasEmptyRuleRows || JSON.stringify(draft.roles) !== JSON.stringify(state.config.roles);
2117
+ const advancedDirty = JSON.stringify([
2118
+ draft.triggerCodes,
2119
+ draft.cooldownMs,
2120
+ draft.revertPolicy,
2121
+ draft.maxSwitchesPerStep,
2122
+ draft.alwaysModeRetryCap,
2123
+ draft.roleAutoMatch
2124
+ ]) !== JSON.stringify([
2125
+ state.config.triggerCodes,
2126
+ state.config.cooldownMs,
2127
+ state.config.revertPolicy,
2128
+ state.config.maxSwitchesPerStep,
2129
+ state.config.alwaysModeRetryCap,
2130
+ state.config.roleAutoMatch
2131
+ ]);
2132
+ const dirty = mainDirty || subDirty || advancedDirty;
2133
+ const firstSeedDone = (0, react.useRef)(false);
2134
+ const forceReseed = (0, react.useRef)(false);
1555
2135
  (0, react.useEffect)(() => {
1556
2136
  if (state.status !== "ready") return;
1557
2137
  const key = JSON.stringify(state.config);
1558
2138
  if (seededConfigKey.current === key) return;
1559
2139
  seededConfigKey.current = key;
1560
- setScalars(scalarsOf(state.config));
1561
- setRootChainRows(rootChainToRows(state.config.rootChain, catalogOf(state)));
1562
- setRoleRows(rolesToRows(state.config.roles.list, catalogOf(state)));
1563
- setRuleRows(rulesToRows(state.config.roles.rules, catalogOf(state)));
1564
- }, [state.status, state.config]);
1565
- const [confirmingReset, setConfirmingReset] = (0, react.useState)(false);
1566
- const [resetting, setResetting] = (0, react.useState)(false);
2140
+ const firstSeed = !firstSeedDone.current;
2141
+ firstSeedDone.current = true;
2142
+ const force = forceReseed.current;
2143
+ forceReseed.current = false;
2144
+ const catalog = catalogOf(state);
2145
+ if (firstSeed || force || !mainDirty) {
2146
+ setAllDayModel(allDayModelOf(state.config.rootChain));
2147
+ setAllDayChainRow(allDayChainRowOf(state.config.rootChain, catalog));
2148
+ setTimeSlotRows(timeSlotsToRows(state.config.timeSlots ?? [], catalog));
2149
+ setScalars((prev) => ({
2150
+ ...prev,
2151
+ enabled: state.config.enabled,
2152
+ tz: state.config.tz ?? "Asia/Shanghai"
2153
+ }));
2154
+ }
2155
+ if (firstSeed || force || !subDirty) {
2156
+ setRoleRows(rolesToRows(state.config.roles.list, catalog));
2157
+ setRuleRows(rulesToRows(state.config.roles.rules, catalog));
2158
+ }
2159
+ if (firstSeed || force || !advancedDirty) setScalars((prev) => ({
2160
+ ...prev,
2161
+ triggerCodes: [...state.config.triggerCodes],
2162
+ cooldownMs: state.config.cooldownMs,
2163
+ revertPolicy: state.config.revertPolicy,
2164
+ maxSwitchesPerStep: state.config.maxSwitchesPerStep,
2165
+ alwaysModeRetryCap: state.config.alwaysModeRetryCap,
2166
+ roleAutoMatch: state.config.roleAutoMatch
2167
+ }));
2168
+ }, [
2169
+ state.status,
2170
+ state.config,
2171
+ mainDirty,
2172
+ subDirty,
2173
+ advancedDirty
2174
+ ]);
1567
2175
  const updateScalars = (mutator) => {
1568
2176
  setScalars((prev) => {
1569
2177
  const next = {
@@ -1574,17 +2182,32 @@ window.__ModuleLoader__.load({
1574
2182
  return next;
1575
2183
  });
1576
2184
  };
1577
- const updateRootChainSelector = (selectorIndex, patch) => {
1578
- setRootChainRows((rows) => rows.map((row, index) => index === 0 ? {
1579
- ...row,
1580
- selectors: row.selectors.map((selector, sIndex) => sIndex === selectorIndex ? {
1581
- ...selector,
2185
+ const updateTimeSlotRow = (index, patch) => {
2186
+ setTimeSlotRows((rows) => {
2187
+ const next = rows.map((row) => ({ ...row }));
2188
+ next[index] = {
2189
+ ...next[index],
1582
2190
  ...patch
1583
- } : selector)
1584
- } : row));
2191
+ };
2192
+ return next;
2193
+ });
2194
+ };
2195
+ const updateTimeSlotSelector = (rowIndex, selectorIndex, patch) => {
2196
+ setTimeSlotRows((rows) => {
2197
+ const next = rows.map((row) => ({
2198
+ ...row,
2199
+ selectors: row.selectors.map((selector) => ({ ...selector }))
2200
+ }));
2201
+ const selectors = next[rowIndex].selectors;
2202
+ selectors[selectorIndex] = {
2203
+ ...selectors[selectorIndex],
2204
+ ...patch
2205
+ };
2206
+ return next;
2207
+ });
1585
2208
  };
1586
- const addRootChainSelector = () => {
1587
- setRootChainRows((rows) => rows.map((row, index) => index === 0 ? {
2209
+ const addTimeSlotSelector = (rowIndex) => {
2210
+ setTimeSlotRows((rows) => rows.map((row, index) => index === rowIndex ? {
1588
2211
  ...row,
1589
2212
  selectors: [...row.selectors, {
1590
2213
  wildcard: false,
@@ -1593,12 +2216,88 @@ window.__ModuleLoader__.load({
1593
2216
  }]
1594
2217
  } : row));
1595
2218
  };
1596
- const removeRootChainSelector = (selectorIndex) => {
1597
- setRootChainRows((rows) => rows.map((row, index) => index === 0 ? {
2219
+ const removeTimeSlotSelector = (rowIndex, selectorIndex) => {
2220
+ setTimeSlotRows((rows) => rows.map((row, index) => index === rowIndex ? {
1598
2221
  ...row,
1599
2222
  selectors: row.selectors.filter((_, sIndex) => sIndex !== selectorIndex)
1600
2223
  } : row));
1601
2224
  };
2225
+ const addPresetSlotRow = () => {
2226
+ if (presetToAdd === "") return;
2227
+ if ((presetToAdd === "glm-peak" || presetToAdd === "glm-valley") && !glmConfigured) return;
2228
+ setTimeSlotRows((rows) => [...rows, {
2229
+ kind: "preset",
2230
+ preset: presetToAdd,
2231
+ start: "",
2232
+ end: "",
2233
+ days: [],
2234
+ name: "",
2235
+ collapsed: false,
2236
+ selectors: []
2237
+ }]);
2238
+ setPresetToAdd("");
2239
+ };
2240
+ const addCustomSlotRow = () => {
2241
+ setTimeSlotRows((rows) => [...rows, {
2242
+ kind: "custom",
2243
+ start: "",
2244
+ end: "",
2245
+ days: [],
2246
+ name: "",
2247
+ collapsed: false,
2248
+ selectors: []
2249
+ }]);
2250
+ };
2251
+ const removeTimeSlotRow = (index) => {
2252
+ setTimeSlotRows((rows) => rows.filter((_, rowIndex) => rowIndex !== index));
2253
+ };
2254
+ const moveTimeSlotRow = (index, delta) => {
2255
+ setTimeSlotRows((rows) => {
2256
+ const target = index + delta;
2257
+ if (target < 0 || target >= rows.length) return rows;
2258
+ const next = rows.map((row) => ({ ...row }));
2259
+ const moved = next[index];
2260
+ next[index] = next[target];
2261
+ next[target] = moved;
2262
+ return next;
2263
+ });
2264
+ };
2265
+ const [draggedSlotIndex, setDraggedSlotIndex] = (0, react.useState)(null);
2266
+ const [overSlotIndex, setOverSlotIndex] = (0, react.useState)(null);
2267
+ const reorderTimeSlotRow = (from, to) => {
2268
+ setTimeSlotRows((rows) => {
2269
+ if (from === to || from < 0 || to < 0 || from >= rows.length || to >= rows.length) return rows;
2270
+ const next = rows.map((row) => ({ ...row }));
2271
+ const [moved] = next.splice(from, 1);
2272
+ next.splice(to, 0, moved);
2273
+ return next;
2274
+ });
2275
+ };
2276
+ const updateAllDayChainSelector = (selectorIndex, patch) => {
2277
+ setAllDayChainRow((row) => ({
2278
+ ...row,
2279
+ selectors: row.selectors.map((selector, index) => index === selectorIndex ? {
2280
+ ...selector,
2281
+ ...patch
2282
+ } : selector)
2283
+ }));
2284
+ };
2285
+ const addAllDayChainSelector = () => {
2286
+ setAllDayChainRow((row) => ({
2287
+ ...row,
2288
+ selectors: [...row.selectors, {
2289
+ wildcard: false,
2290
+ provider: null,
2291
+ model: null
2292
+ }]
2293
+ }));
2294
+ };
2295
+ const removeAllDayChainSelector = (selectorIndex) => {
2296
+ setAllDayChainRow((row) => ({
2297
+ ...row,
2298
+ selectors: row.selectors.filter((_, index) => index !== selectorIndex)
2299
+ }));
2300
+ };
1602
2301
  const updateRoleRow = (index, patch) => {
1603
2302
  setRoleRows((rows) => {
1604
2303
  const next = rows.map((row) => ({ ...row }));
@@ -1644,7 +2343,8 @@ window.__ModuleLoader__.load({
1644
2343
  id: "",
1645
2344
  persona: "",
1646
2345
  selectors: [],
1647
- fallback: "inherit-root"
2346
+ fallback: "inherit-root",
2347
+ collapsed: true
1648
2348
  }]);
1649
2349
  };
1650
2350
  const removeRole = (index) => {
@@ -1660,18 +2360,23 @@ window.__ModuleLoader__.load({
1660
2360
  return next;
1661
2361
  });
1662
2362
  };
1663
- const draft = assembleConfig(scalars, rootChainRows, roleRows, ruleRows, state.config.roles.list, state.config.presets);
1664
- const hasEmptyRuleRows = ruleRows.some((row) => row.role === "");
1665
- const dirty = JSON.stringify(draft) !== JSON.stringify(state.config) || hasEmptyRuleRows;
2363
+ const allValidationErrors = [
2364
+ ...validationErrors.main,
2365
+ ...validationErrors.sub,
2366
+ ...validationErrors.advanced
2367
+ ];
1666
2368
  const saving = state.status === "saving";
1667
2369
  const writable = state.writable;
1668
2370
  const unknownCodes = scalars.triggerCodes.filter((code) => !KNOWN_TRIGGER_CODES.includes(code));
2371
+ const presetsPresent = timeSlotRows.some((row) => row.kind === "preset");
2372
+ const glmConfigured = state.configuredProviders.some((entry) => entry.provider === "zai-coding-cn");
2373
+ const slotState = resolveSlotState(state.config, /* @__PURE__ */ new Date(), scalars.tz);
2374
+ const activeSlotIndex = slotState.winner === "all-day" ? -1 : state.config.timeSlots?.indexOf(slotState.winner) ?? -1;
1669
2375
  const roleOptions = ruleRoleOptions({ list: roleRows });
1670
2376
  const invalidRoleIds = validationAttempted ? collectInvalidRoleIds(roleRows) : null;
2377
+ const invalidSlotRows = validationAttempted ? collectInvalidSlotRows(timeSlotRows) : null;
1671
2378
  const seededIds = /* @__PURE__ */ new Map();
1672
2379
  for (const seed of state.seeds) seededIds.set(seed.id.trim(), seed.overridden);
1673
- const effectiveModel = deriveEffectiveModel(state.config, state.switches);
1674
- const effectiveModelLine = effectiveModel.kind === "unavailable" ? t("status.effectiveModel.unavailable") : `${effectiveModel.provider}/${effectiveModel.model} · ${t("status.effectiveModel.note")}`;
1675
2380
  const latestSwitch = state.switches[0];
1676
2381
  let switchesLine;
1677
2382
  if (state.switchesStatus === "error") switchesLine = t("status.switches.error", { message: state.switchesError });
@@ -1679,53 +2384,138 @@ window.__ModuleLoader__.load({
1679
2384
  else if (latestSwitch === void 0) switchesLine = t("status.switches.empty");
1680
2385
  else {
1681
2386
  const reasonKey = SWITCH_REASON_KEYS[latestSwitch.reason];
1682
- switchesLine = t("status.switches.compact", {
2387
+ const params = {
1683
2388
  count: String(state.switches.length),
1684
2389
  from: `${latestSwitch.from.provider}/${latestSwitch.from.model}`,
1685
2390
  to: `${latestSwitch.to.provider}/${latestSwitch.to.model}`,
1686
2391
  role: latestSwitch.role,
1687
2392
  reason: reasonKey === void 0 ? latestSwitch.reason : t(reasonKey)
1688
- });
2393
+ };
2394
+ switchesLine = latestSwitch.reason === "role-inject" ? t("status.switches.compact.roleInject", params) : t("status.switches.compact", params);
1689
2395
  }
1690
- const catalogSeededEpoch = (0, react.useRef)(null);
2396
+ const catalogSeededSections = (0, react.useRef)({
2397
+ epoch: null,
2398
+ main: false,
2399
+ sub: false
2400
+ });
1691
2401
  (0, react.useEffect)(() => {
1692
2402
  if (state.catalogStatus !== "ready") return;
1693
- if (catalogSeededEpoch.current === state.catalogEpoch) return;
1694
- if (dirty) return;
1695
- catalogSeededEpoch.current = state.catalogEpoch;
1696
- setRootChainRows(rootChainToRows(state.config.rootChain, catalogOf(state)));
1697
- setRoleRows(rolesToRows(state.config.roles.list, catalogOf(state)));
1698
- setRuleRows(rulesToRows(state.config.roles.rules, catalogOf(state)));
2403
+ const seed = catalogSeededSections.current;
2404
+ if (seed.epoch !== state.catalogEpoch) {
2405
+ seed.epoch = state.catalogEpoch;
2406
+ seed.main = false;
2407
+ seed.sub = false;
2408
+ }
2409
+ const catalog = catalogOf(state);
2410
+ if (!mainDirty && !seed.main) {
2411
+ seed.main = true;
2412
+ setAllDayModel(allDayModelOf(state.config.rootChain));
2413
+ setAllDayChainRow(allDayChainRowOf(state.config.rootChain, catalog));
2414
+ setTimeSlotRows(timeSlotsToRows(state.config.timeSlots ?? [], catalog));
2415
+ }
2416
+ if (!subDirty && !seed.sub) {
2417
+ seed.sub = true;
2418
+ setRoleRows(rolesToRows(state.config.roles.list, catalog));
2419
+ setRuleRows(rulesToRows(state.config.roles.rules, catalog));
2420
+ }
1699
2421
  }, [
1700
2422
  state.catalogStatus,
1701
2423
  state.catalogEpoch,
1702
2424
  state.config,
1703
- dirty
2425
+ mainDirty,
2426
+ subDirty
1704
2427
  ]);
1705
- const save = () => {
2428
+ const sectionPatch = (section) => {
2429
+ const base = {
2430
+ ...state.config,
2431
+ enabled: scalars.enabled
2432
+ };
2433
+ switch (section) {
2434
+ case "main": return {
2435
+ ...base,
2436
+ rootChain: draft.rootChain,
2437
+ timeSlots: draft.timeSlots,
2438
+ tz: draft.tz
2439
+ };
2440
+ case "sub": return {
2441
+ ...base,
2442
+ roles: draft.roles
2443
+ };
2444
+ case "advanced": return {
2445
+ ...base,
2446
+ triggerCodes: draft.triggerCodes,
2447
+ cooldownMs: draft.cooldownMs,
2448
+ revertPolicy: draft.revertPolicy,
2449
+ maxSwitchesPerStep: draft.maxSwitchesPerStep,
2450
+ alwaysModeRetryCap: draft.alwaysModeRetryCap,
2451
+ roleAutoMatch: draft.roleAutoMatch
2452
+ };
2453
+ }
2454
+ };
2455
+ const save = (section) => {
2456
+ setLastSaveSection(section);
1706
2457
  const errors = validateDraft(draft, t, seededIds);
1707
- if (hasEmptyRuleRows) errors.push(t("validation.ruleRoleRequired"));
1708
- if (errors.length > 0) {
2458
+ if (section === "sub" && hasEmptyRuleRows) errors.sub.push(t("validation.ruleRoleRequired"));
2459
+ if (errors[section].length > 0) {
1709
2460
  setValidationErrors(errors);
1710
2461
  setValidationAttempted(true);
1711
2462
  return;
1712
2463
  }
1713
- setValidationErrors([]);
2464
+ setValidationErrors(emptyValidationErrors());
2465
+ setValidationAttempted(false);
2466
+ controller.save(sectionPatch(section));
2467
+ };
2468
+ const saveEnabled = () => {
2469
+ setLastSaveSection("main");
2470
+ setValidationErrors(emptyValidationErrors());
2471
+ setValidationAttempted(false);
2472
+ controller.save({
2473
+ ...state.config,
2474
+ enabled: scalars.enabled
2475
+ });
2476
+ };
2477
+ const discardSection = (section) => {
2478
+ switch (section) {
2479
+ case "main":
2480
+ setAllDayModel(allDayModelOf(state.config.rootChain));
2481
+ setAllDayChainRow(allDayChainRowOf(state.config.rootChain, catalogOf(state)));
2482
+ setTimeSlotRows(timeSlotsToRows(state.config.timeSlots ?? [], catalogOf(state)));
2483
+ setScalars((prev) => ({
2484
+ ...prev,
2485
+ enabled: state.config.enabled,
2486
+ tz: state.config.tz ?? "Asia/Shanghai"
2487
+ }));
2488
+ break;
2489
+ case "sub":
2490
+ setRoleRows(rolesToRows(state.config.roles.list, catalogOf(state)));
2491
+ setRuleRows(rulesToRows(state.config.roles.rules, catalogOf(state)));
2492
+ break;
2493
+ case "advanced": setScalars((prev) => ({
2494
+ ...prev,
2495
+ triggerCodes: [...state.config.triggerCodes],
2496
+ cooldownMs: state.config.cooldownMs,
2497
+ revertPolicy: state.config.revertPolicy,
2498
+ maxSwitchesPerStep: state.config.maxSwitchesPerStep,
2499
+ alwaysModeRetryCap: state.config.alwaysModeRetryCap,
2500
+ roleAutoMatch: state.config.roleAutoMatch
2501
+ }));
2502
+ }
2503
+ setValidationErrors(emptyValidationErrors());
1714
2504
  setValidationAttempted(false);
1715
- controller.save(draft);
1716
2505
  };
1717
- const discard = () => {
1718
- setScalars(scalarsOf(state.config));
1719
- setRootChainRows(rootChainToRows(state.config.rootChain, catalogOf(state)));
1720
- setRoleRows(rolesToRows(state.config.roles.list, catalogOf(state)));
1721
- setRuleRows(rulesToRows(state.config.roles.rules, catalogOf(state)));
1722
- setValidationErrors([]);
2506
+ const discardEnabled = () => {
2507
+ setScalars((prev) => ({
2508
+ ...prev,
2509
+ enabled: state.config.enabled
2510
+ }));
2511
+ setValidationErrors(emptyValidationErrors());
1723
2512
  setValidationAttempted(false);
1724
2513
  };
1725
2514
  (0, react.useEffect)(() => {
1726
2515
  if (!validationAttempted) return;
1727
- if (validateDraft(draft, t, seededIds).length === 0 && !ruleRows.some((row) => row.role === "")) {
1728
- setValidationErrors([]);
2516
+ const errors = validateDraft(draft, t, seededIds);
2517
+ if (errors.main.length === 0 && errors.sub.length === 0 && errors.advanced.length === 0 && !ruleRows.some((row) => row.role === "")) {
2518
+ setValidationErrors(emptyValidationErrors());
1729
2519
  setValidationAttempted(false);
1730
2520
  }
1731
2521
  }, [
@@ -1734,13 +2524,10 @@ window.__ModuleLoader__.load({
1734
2524
  ruleRows,
1735
2525
  t
1736
2526
  ]);
1737
- const confirmReset = () => {
1738
- setResetting(true);
1739
- controller.resetToDefaults().finally(() => {
1740
- setResetting(false);
1741
- setConfirmingReset(false);
1742
- });
1743
- };
2527
+ (0, react.useEffect)(() => {
2528
+ if (state.status === "ready") setLastSaveSection(null);
2529
+ if (state.status === "error") forceReseed.current = false;
2530
+ }, [state.status]);
1744
2531
  const [userOpen, setUserOpen] = (0, react.useState)(false);
1745
2532
  const [advancedOpen, setAdvancedOpen] = (0, react.useState)(false);
1746
2533
  const degradedLatch = (0, react.useRef)(false);
@@ -1781,851 +2568,1323 @@ window.__ModuleLoader__.load({
1781
2568
  });
1782
2569
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1783
2570
  className: open ? `${FallbacksCard_module_css_default.card} ${FallbacksCard_module_css_default.cardOpen}` : FallbacksCard_module_css_default.card,
1784
- children: [
1785
- header,
1786
- open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1787
- className: FallbacksCard_module_css_default.body,
1788
- children: [
1789
- state.legacyKeys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1790
- className: FallbacksCard_module_css_default.legacyNotice,
1791
- role: "status",
1792
- children: t("legacy.banner", { keys: state.legacyKeys.join(", ") })
1793
- }),
1794
- state.status === "error" && state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1795
- className: FallbacksCard_module_css_default.noticeRow,
1796
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1797
- className: FallbacksCard_module_css_default.error,
1798
- role: "alert",
1799
- children: t("error.generic", { message: state.error })
1800
- }), !state.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1801
- variant: "outline",
1802
- size: "sm",
1803
- onClick: () => {
1804
- controller.load();
1805
- },
1806
- children: t("retry")
1807
- })]
1808
- }),
1809
- validationErrors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2571
+ children: [header, open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2572
+ className: FallbacksCard_module_css_default.body,
2573
+ children: [
2574
+ state.legacyKeys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2575
+ className: FallbacksCard_module_css_default.legacyNotice,
2576
+ role: "status",
2577
+ children: t("legacy.banner", { keys: state.legacyKeys.join(", ") })
2578
+ }),
2579
+ state.status === "error" && state.error !== null && lastSaveSection === null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2580
+ className: FallbacksCard_module_css_default.noticeRow,
2581
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1810
2582
  className: FallbacksCard_module_css_default.error,
1811
2583
  role: "alert",
1812
- children: `${t("validation.blocked")}${validationErrors.join("; ")}`
1813
- }),
1814
- degraded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1815
- className: FallbacksCard_module_css_default.notice,
1816
- role: "status",
1817
- children: t("unavailable")
1818
- }),
1819
- state.status === "ready" && !state.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1820
- className: FallbacksCard_module_css_default.readOnly,
1821
- role: "status",
1822
- children: t("readOnly")
1823
- }),
1824
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1825
- className: FallbacksCard_module_css_default.form,
1826
- children: [
1827
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1828
- className: FallbacksCard_module_css_default.checkboxRow,
1829
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1830
- className: FallbacksCard_module_css_default.checkLabel,
1831
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1832
- className: FallbacksCard_module_css_default.checkLabelTitle,
1833
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1834
- htmlFor: "fallbacks-enabled",
1835
- children: t("enabled.label")
1836
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
1837
- label: t("enabled.tooltip"),
1838
- disabled: !writable
1839
- })]
1840
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1841
- className: FallbacksCard_module_css_default.checkLabelDesc,
1842
- children: t("enabled.hint")
2584
+ children: t("error.generic", { message: state.error })
2585
+ }), !state.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2586
+ variant: "outline",
2587
+ size: "sm",
2588
+ onClick: () => {
2589
+ controller.load();
2590
+ },
2591
+ children: t("retry")
2592
+ })]
2593
+ }),
2594
+ degraded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2595
+ className: FallbacksCard_module_css_default.notice,
2596
+ role: "status",
2597
+ children: t("unavailable")
2598
+ }),
2599
+ state.status === "ready" && !state.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2600
+ className: FallbacksCard_module_css_default.readOnly,
2601
+ role: "status",
2602
+ children: t("readOnly")
2603
+ }),
2604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2605
+ className: FallbacksCard_module_css_default.form,
2606
+ children: [
2607
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2608
+ className: FallbacksCard_module_css_default.checkboxRow,
2609
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2610
+ className: FallbacksCard_module_css_default.checkLabel,
2611
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2612
+ className: FallbacksCard_module_css_default.checkLabelTitle,
2613
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2614
+ htmlFor: "fallbacks-enabled",
2615
+ children: t("enabled.label")
2616
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2617
+ label: t("enabled.tooltip"),
2618
+ disabled: !writable
1843
2619
  })]
1844
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1845
- id: "fallbacks-enabled",
1846
- type: "checkbox",
1847
- className: FallbacksCard_module_css_default.checkbox,
1848
- checked: scalars.enabled,
1849
- disabled: !writable,
1850
- onChange: (event) => {
1851
- updateScalars((draft) => {
1852
- draft.enabled = event.target.checked;
1853
- });
1854
- }
2620
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2621
+ className: FallbacksCard_module_css_default.checkLabelDesc,
2622
+ children: t("enabled.hint")
1855
2623
  })]
1856
- }),
1857
- !scalars.enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2624
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2625
+ id: "fallbacks-enabled",
2626
+ type: "checkbox",
2627
+ className: FallbacksCard_module_css_default.checkbox,
2628
+ checked: scalars.enabled,
2629
+ disabled: !writable,
2630
+ onChange: (event) => {
2631
+ updateScalars((draft) => {
2632
+ draft.enabled = event.target.checked;
2633
+ });
2634
+ }
2635
+ })]
2636
+ }),
2637
+ !scalars.enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2638
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1858
2639
  className: FallbacksCard_module_css_default.offNotice,
1859
2640
  children: t("enabled.off")
1860
2641
  }),
1861
- scalars.enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
1862
- className: FallbacksCard_module_css_default.fieldset,
1863
- disabled: !writable,
1864
- children: [
1865
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1866
- className: FallbacksCard_module_css_default.field,
1867
- role: "group",
1868
- "aria-labelledby": "fallbacks-root-chain",
1869
- children: [
1870
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1871
- className: FallbacksCard_module_css_default.fieldLabel,
1872
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1873
- id: "fallbacks-root-chain",
1874
- children: t("rootChain.label")
1875
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
1876
- label: t("rootChain.tooltip"),
1877
- disabled: !writable
1878
- })]
1879
- }),
1880
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1881
- className: FallbacksCard_module_css_default.hint,
1882
- children: t("rootChain.hint")
1883
- }),
1884
- state.catalogStatus === "error" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1885
- className: FallbacksCard_module_css_default.hint,
1886
- children: t("catalog.error", { message: state.catalogError })
1887
- }),
1888
- state.catalogStatus === "ready" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1889
- className: FallbacksCard_module_css_default.hint,
1890
- children: t("catalog.partial", { message: state.catalogError })
1891
- }),
1892
- state.catalogStatus === "ready" && (state.groups.length === 0 || state.configuredProviders.length === 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1893
- className: FallbacksCard_module_css_default.hint,
1894
- children: t("catalog.empty")
1895
- }),
1896
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1897
- className: FallbacksCard_module_css_default.list,
1898
- children: rootChainRows.map((row, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1899
- className: FallbacksCard_module_css_default.editorCard,
1900
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1901
- className: FallbacksCard_module_css_default.chainSelectors,
1902
- children: row.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
1903
- selector,
1904
- catalog: catalogOf(state),
1905
- configuredProviders: state.configuredProviders,
2642
+ allValidationErrors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2643
+ className: FallbacksCard_module_css_default.error,
2644
+ role: "alert",
2645
+ children: `${t("validation.blocked")}${allValidationErrors.join("; ")}`
2646
+ }),
2647
+ lastSaveSection === "main" && state.status === "error" && state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2648
+ className: FallbacksCard_module_css_default.error,
2649
+ role: "alert",
2650
+ children: t("error.generic", { message: state.error })
2651
+ }),
2652
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2653
+ className: FallbacksCard_module_css_default.sectionActions,
2654
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2655
+ type: "button",
2656
+ className: `${FallbacksCard_module_css_default.secondaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
2657
+ disabled: !enabledDirty || saving,
2658
+ onClick: discardEnabled,
2659
+ children: t("discard")
2660
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2661
+ type: "button",
2662
+ className: `${FallbacksCard_module_css_default.primaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
2663
+ disabled: !writable || saving || !enabledDirty,
2664
+ onClick: saveEnabled,
2665
+ children: saving ? t("save.saving") : t("save")
2666
+ })]
2667
+ })
2668
+ ] }),
2669
+ scalars.enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
2670
+ className: FallbacksCard_module_css_default.fieldset,
2671
+ disabled: !writable,
2672
+ children: [
2673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2674
+ className: FallbacksCard_module_css_default.sectionHeading,
2675
+ id: "fallbacks-main-agent",
2676
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2677
+ className: FallbacksCard_module_css_default.sectionHeadingText,
2678
+ children: t("mainAgent.label")
2679
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2680
+ className: FallbacksCard_module_css_default.sectionActions,
2681
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2682
+ type: "button",
2683
+ className: `${FallbacksCard_module_css_default.secondaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
2684
+ disabled: !mainDirty || saving,
2685
+ onClick: () => {
2686
+ discardSection("main");
2687
+ },
2688
+ children: t("discard")
2689
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2690
+ type: "button",
2691
+ className: `${FallbacksCard_module_css_default.primaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
2692
+ disabled: !writable || saving || !mainDirty,
2693
+ onClick: () => {
2694
+ save("main");
2695
+ },
2696
+ children: saving ? t("save.saving") : t("save")
2697
+ })]
2698
+ })]
2699
+ }),
2700
+ validationErrors.main.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2701
+ className: FallbacksCard_module_css_default.error,
2702
+ role: "alert",
2703
+ children: `${t("validation.blocked")}${validationErrors.main.join("; ")}`
2704
+ }),
2705
+ lastSaveSection === "main" && state.status === "error" && state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2706
+ className: FallbacksCard_module_css_default.error,
2707
+ role: "alert",
2708
+ children: t("error.generic", { message: state.error })
2709
+ }),
2710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2711
+ className: FallbacksCard_module_css_default.field,
2712
+ role: "group",
2713
+ "aria-labelledby": "fallbacks-time-slots",
2714
+ children: [
2715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2716
+ className: FallbacksCard_module_css_default.fieldLabel,
2717
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2718
+ id: "fallbacks-time-slots",
2719
+ children: t("timeSlots.label")
2720
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2721
+ label: t("timeSlots.tooltip"),
2722
+ disabled: !writable
2723
+ })]
2724
+ }),
2725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2726
+ className: FallbacksCard_module_css_default.hint,
2727
+ children: t("timeSlots.hint")
2728
+ }),
2729
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2730
+ className: FallbacksCard_module_css_default.list,
2731
+ children: timeSlotRows.map((row, index) => {
2732
+ const invalidWindow = invalidSlotRows?.has(index) ?? false;
2733
+ const chainEmpty = row.selectors.every((selector) => selectorRowToRaw(selector) === "");
2734
+ const firstModel = row.selectors.map(selectorRowToRaw).find((entry) => entry !== "");
2735
+ const slotExpanded = !row.collapsed || !writable;
2736
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2737
+ className: `${FallbacksCard_module_css_default.editorCard} ${draggedSlotIndex === index ? FallbacksCard_module_css_default.slotCardDragging : ""} ${overSlotIndex === index && draggedSlotIndex !== null && draggedSlotIndex !== index ? FallbacksCard_module_css_default.slotCardOver : ""}`,
2738
+ onDragOver: (event) => {
2739
+ if (draggedSlotIndex === null) return;
2740
+ event.preventDefault();
2741
+ if (overSlotIndex !== index) setOverSlotIndex(index);
2742
+ },
2743
+ onDrop: (event) => {
2744
+ event.preventDefault();
2745
+ const from = draggedSlotIndex;
2746
+ setDraggedSlotIndex(null);
2747
+ setOverSlotIndex(null);
2748
+ if (from !== null && from !== index) reorderTimeSlotRow(from, index);
2749
+ },
2750
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2751
+ className: FallbacksCard_module_css_default.collapseRow,
2752
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2753
+ type: "button",
2754
+ className: FallbacksCard_module_css_default.collapseToggle,
2755
+ "aria-expanded": slotExpanded,
2756
+ "aria-label": t(slotExpanded ? "timeSlots.collapse" : "timeSlots.expand"),
1906
2757
  disabled: !writable,
1907
- t,
1908
- onChange: (patch) => {
1909
- updateRootChainSelector(selectorIndex, patch);
2758
+ onClick: () => {
2759
+ updateTimeSlotRow(index, { collapsed: !row.collapsed });
1910
2760
  },
1911
- onRemove: () => {
1912
- removeRootChainSelector(selectorIndex);
1913
- }
1914
- }, selectorIndex))
1915
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1916
- variant: "outline",
1917
- size: "sm",
1918
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
1919
- className: FallbacksCard_module_css_default.addButton,
1920
- onClick: addRootChainSelector,
1921
- children: t("rootChain.selector.add")
1922
- })]
1923
- }, rowIndex))
1924
- })
1925
- ]
1926
- }),
1927
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1928
- className: FallbacksCard_module_css_default.field,
1929
- role: "group",
1930
- "aria-labelledby": "fallbacks-roles-list",
1931
- children: [
1932
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1933
- className: FallbacksCard_module_css_default.fieldLabel,
1934
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1935
- id: "fallbacks-roles-list",
1936
- children: t("roles.list.label")
1937
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
1938
- label: t("roles.list.tooltip"),
1939
- disabled: !writable
1940
- })]
1941
- }),
1942
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1943
- className: FallbacksCard_module_css_default.hint,
1944
- children: t("roles.list.hint")
1945
- }),
1946
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1947
- className: FallbacksCard_module_css_default.list,
1948
- children: roleRows.map((row, index) => {
1949
- const invalid = invalidRoleIds?.has(row.id.trim()) ?? false;
1950
- const seed = seededIds.get(row.id.trim());
1951
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1952
- className: FallbacksCard_module_css_default.editorCard,
1953
- children: [
1954
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1955
- className: FallbacksCard_module_css_default.ruleGrid,
1956
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1957
- className: FallbacksCard_module_css_default.ruleCell,
1958
- children: [
1959
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1960
- className: FallbacksCard_module_css_default.ruleCellLabel,
1961
- children: t("roles.id")
1962
- }),
1963
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1964
- className: `${FallbacksCard_module_css_default.input} ${invalid ? FallbacksCard_module_css_default.inputInvalid : ""}`,
1965
- value: row.id,
1966
- placeholder: t("roles.idPlaceholder"),
1967
- "aria-label": t("roles.id"),
1968
- "aria-invalid": invalid ? true : void 0,
1969
- disabled: !writable || seed !== void 0,
1970
- onChange: (event) => {
1971
- updateRoleRow(index, { id: event.target.value });
1972
- }
1973
- }),
1974
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1975
- className: FallbacksCard_module_css_default.hint,
1976
- children: t("roles.id.hint")
1977
- })
1978
- ]
2761
+ children: [
2762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: slotExpanded ? `${FallbacksCard_module_css_default.chevron} ${FallbacksCard_module_css_default.chevronOpen}` : FallbacksCard_module_css_default.chevron }),
2763
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2764
+ className: FallbacksCard_module_css_default.collapseTitle,
2765
+ children: row.kind === "preset" ? t(`timeSlots.preset.${row.preset}.label`) : row.name !== "" ? row.name : `custom ${row.start}-${row.end}`
2766
+ }),
2767
+ row.kind === "preset" && (row.preset === "liang-peak" || row.preset === "glm-peak") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2768
+ className: `${FallbacksCard_module_css_default.slotTag} ${FallbacksCard_module_css_default.slotTagHighCost}`,
2769
+ children: t("timeSlots.preset.highCost")
2770
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2771
+ className: `${FallbacksCard_module_css_default.slotTag} ${FallbacksCard_module_css_default.slotTagMultiplier}`,
2772
+ children: t("timeSlots.preset.multiplier", { n: row.preset === "liang-peak" ? "2" : "3" })
2773
+ })] }),
2774
+ activeSlotIndex === index && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2775
+ className: `${FallbacksCard_module_css_default.slotTag} ${FallbacksCard_module_css_default.slotTagActive}`,
2776
+ children: t("timeSlots.active")
2777
+ }),
2778
+ firstModel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2779
+ className: FallbacksCard_module_css_default.collapseMeta,
2780
+ children: firstModel
1979
2781
  })
1980
- }),
1981
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2782
+ ]
2783
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2784
+ type: "button",
2785
+ className: FallbacksCard_module_css_default.dragHandle,
2786
+ draggable: writable,
2787
+ "data-tip": t("timeSlots.drag"),
2788
+ "aria-label": t("timeSlots.drag"),
2789
+ disabled: !writable,
2790
+ onDragStart: () => {
2791
+ if (!writable) return;
2792
+ setDraggedSlotIndex(index);
2793
+ setOverSlotIndex(index);
2794
+ },
2795
+ onDragEnd: () => {
2796
+ setDraggedSlotIndex(null);
2797
+ setOverSlotIndex(null);
2798
+ },
2799
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { className: FallbacksCard_module_css_default.dragHandleIcon })
2800
+ })]
2801
+ }), slotExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2802
+ row.kind === "preset" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1982
2804
  className: FallbacksCard_module_css_default.ruleGrid,
1983
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2805
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1984
2806
  className: FallbacksCard_module_css_default.ruleCell,
1985
- children: [
1986
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1987
- className: FallbacksCard_module_css_default.ruleCellLabel,
1988
- children: t("roles.persona")
1989
- }),
1990
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1991
- rows: 3,
1992
- className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.inputTextarea}`,
1993
- value: row.persona,
1994
- placeholder: t("roles.personaPlaceholder"),
1995
- "aria-label": t("roles.persona"),
1996
- disabled: !writable,
1997
- onChange: (event) => {
1998
- updateRoleRow(index, { persona: event.target.value });
1999
- }
2000
- }),
2001
- seed !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2002
- className: FallbacksCard_module_css_default.hint,
2003
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2004
- className: FallbacksCard_module_css_default.pending,
2005
- children: t(seed ? "roles.seedOverride" : "roles.seedDefault")
2006
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2007
- variant: "outline",
2008
- size: "sm",
2009
- disabled: !writable || saving,
2010
- onClick: () => {
2011
- controller.revertSeed(row.id.trim());
2012
- },
2013
- children: t("roles.revertPersona")
2014
- })]
2015
- })
2016
- ]
2017
- })
2807
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2808
+ className: FallbacksCard_module_css_default.ruleCellLabel,
2809
+ children: t("timeSlots.preset.name")
2810
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2811
+ className: FallbacksCard_module_css_default.slotPresetName,
2812
+ children: t(`timeSlots.preset.${row.preset}.label`)
2813
+ })]
2814
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2815
+ className: FallbacksCard_module_css_default.ruleCell,
2816
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2817
+ className: FallbacksCard_module_css_default.ruleCellLabel,
2818
+ children: t("timeSlots.preset.windowLabel")
2819
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2820
+ className: FallbacksCard_module_css_default.hint,
2821
+ children: t(`timeSlots.preset.${row.preset}.window`)
2822
+ })]
2823
+ })]
2018
2824
  }),
2825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2826
+ className: FallbacksCard_module_css_default.hint,
2827
+ children: t("timeSlots.preset.chainsOnly")
2828
+ }),
2829
+ (row.preset === "glm-peak" || row.preset === "glm-valley") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2830
+ className: FallbacksCard_module_css_default.hint,
2831
+ children: t("timeSlots.preset.glm.note")
2832
+ })
2833
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2019
2834
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2020
- className: FallbacksCard_module_css_default.chainSelectors,
2021
- children: [row.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
2022
- selector,
2023
- catalog: catalogOf(state),
2024
- configuredProviders: state.configuredProviders,
2835
+ className: FallbacksCard_module_css_default.field,
2836
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2837
+ className: FallbacksCard_module_css_default.ruleCellLabel,
2838
+ children: t("timeSlots.name")
2839
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2840
+ className: FallbacksCard_module_css_default.input,
2841
+ value: row.name,
2842
+ placeholder: t("timeSlots.name"),
2843
+ "aria-label": t("timeSlots.name"),
2025
2844
  disabled: !writable,
2026
- t,
2027
- onChange: (patch) => {
2028
- updateRoleSelector(index, selectorIndex, patch);
2029
- },
2030
- onRemove: () => {
2031
- removeRoleSelector(index, selectorIndex);
2845
+ onChange: (event) => {
2846
+ updateTimeSlotRow(index, { name: event.target.value });
2032
2847
  }
2033
- }, selectorIndex)), row.selectors.every((selector) => selectorRowToRaw(selector) === "") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2848
+ })]
2849
+ }),
2850
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2851
+ className: FallbacksCard_module_css_default.field,
2852
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2853
+ className: FallbacksCard_module_css_default.ruleCellLabel,
2854
+ children: t("timeSlots.tz.label")
2855
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2034
2856
  className: FallbacksCard_module_css_default.hint,
2035
- children: seed !== void 0 ? t("roles.seedChainOptional", { id: row.id }) : t("validation.roleChainRequired", { id: row.id })
2857
+ "aria-label": t("timeSlots.tz.label"),
2858
+ children: tzDisplayLabel(presetsPresent ? "Asia/Shanghai" : hostTimeZone())
2036
2859
  })]
2037
2860
  }),
2038
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2039
2862
  className: FallbacksCard_module_css_default.ruleGrid,
2040
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2041
2864
  className: FallbacksCard_module_css_default.ruleCell,
2042
2865
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2043
2866
  className: FallbacksCard_module_css_default.ruleCellLabel,
2044
- children: t("roles.fallback")
2045
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2046
- className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
2047
- value: row.fallback,
2048
- "aria-label": t("roles.fallback"),
2867
+ children: t("timeSlots.start")
2868
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2869
+ className: `${FallbacksCard_module_css_default.input} ${invalidWindow ? FallbacksCard_module_css_default.inputInvalid : ""}`,
2870
+ value: row.start,
2871
+ placeholder: "09:00",
2872
+ "aria-label": t("timeSlots.start"),
2049
2873
  disabled: !writable,
2050
2874
  onChange: (event) => {
2051
- updateRoleRow(index, { fallback: event.target.value });
2052
- },
2053
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2054
- value: "inherit-root",
2055
- children: t("roles.fallback.inherit-root")
2056
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2057
- value: "none",
2058
- children: t("roles.fallback.none")
2059
- })]
2875
+ updateTimeSlotRow(index, { start: event.target.value });
2876
+ }
2060
2877
  })]
2061
- })
2062
- }),
2063
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2064
- variant: "outline",
2065
- size: "sm",
2066
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
2067
- className: FallbacksCard_module_css_default.addButton,
2068
- onClick: () => {
2069
- addRoleSelector(index);
2070
- },
2071
- children: t("roles.selector.add")
2878
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2879
+ className: FallbacksCard_module_css_default.ruleCell,
2880
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2881
+ className: FallbacksCard_module_css_default.ruleCellLabel,
2882
+ children: t("timeSlots.end")
2883
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2884
+ className: `${FallbacksCard_module_css_default.input} ${invalidWindow ? FallbacksCard_module_css_default.inputInvalid : ""}`,
2885
+ value: row.end,
2886
+ placeholder: "18:00",
2887
+ "aria-label": t("timeSlots.end"),
2888
+ disabled: !writable,
2889
+ onChange: (event) => {
2890
+ updateTimeSlotRow(index, { end: event.target.value });
2891
+ }
2892
+ })]
2893
+ })]
2072
2894
  }),
2073
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2074
- className: FallbacksCard_module_css_default.cardFoot,
2075
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2076
- type: "button",
2077
- className: `${FallbacksCard_module_css_default.iconButton} ${FallbacksCard_module_css_default.iconButtonDanger}`,
2078
- "data-tip": t("roles.remove"),
2079
- "aria-label": t("roles.remove"),
2080
- onClick: () => {
2081
- removeRole(index);
2082
- },
2083
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {})
2084
- })
2085
- })
2086
- ]
2087
- }, index);
2088
- })
2089
- }),
2090
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2091
- variant: "outline",
2092
- size: "sm",
2093
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
2094
- className: FallbacksCard_module_css_default.addButton,
2095
- onClick: addRole,
2096
- children: t("roles.add")
2097
- })
2098
- ]
2099
- }),
2100
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2101
- className: FallbacksCard_module_css_default.field,
2102
- role: "group",
2103
- "aria-labelledby": "fallbacks-roles-rules",
2104
- children: [
2105
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2106
- className: FallbacksCard_module_css_default.fieldLabel,
2107
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2108
- id: "fallbacks-roles-rules",
2109
- children: t("roles.rules")
2110
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2111
- label: t("roles.rules.tooltip"),
2112
- disabled: !writable
2113
- })]
2114
- }),
2115
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2116
- className: FallbacksCard_module_css_default.hint,
2117
- children: t("roles.rules.hint")
2118
- }),
2119
- state.catalogStatus === "error" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2120
- className: FallbacksCard_module_css_default.hint,
2121
- children: t("catalog.error", { message: state.catalogError })
2122
- }),
2123
- state.catalogStatus === "ready" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2124
- className: FallbacksCard_module_css_default.hint,
2125
- children: t("catalog.partial", { message: state.catalogError })
2126
- }),
2127
- state.catalogStatus === "ready" && (state.groups.length === 0 || state.configuredProviders.length === 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2128
- className: FallbacksCard_module_css_default.hint,
2129
- children: t("catalog.empty")
2130
- }),
2131
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2132
- className: FallbacksCard_module_css_default.list,
2133
- children: ruleRows.map((row, index) => {
2134
- const catalog = catalogOf(state);
2135
- const providerRaw = selectionToRaw(row.provider);
2136
- const group = catalog?.groups.find((entry) => entry.id === providerRaw);
2137
- const providerOutside = row.provider?.kind === "outside";
2138
- const providerUnconfigured = !providerOutside && providerRaw !== "" && (catalog?.providers.some((entry) => entry.provider === providerRaw) ?? false) && !state.configuredProviders.some((entry) => entry.provider === providerRaw);
2139
- const modelOutside = row.model?.kind === "outside";
2140
- const roleOutside = row.role !== "" && !roleOptions.includes(row.role);
2141
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2142
- className: FallbacksCard_module_css_default.editorCard,
2143
- children: [
2144
2895
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2145
- className: FallbacksCard_module_css_default.ruleGrid,
2896
+ className: FallbacksCard_module_css_default.field,
2146
2897
  children: [
2147
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2148
- className: FallbacksCard_module_css_default.ruleCell,
2149
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2150
- className: FallbacksCard_module_css_default.ruleCellLabel,
2151
- children: t("roles.rule.origin")
2152
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2153
- className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
2154
- value: row.origin,
2155
- onChange: (event) => {
2156
- updateRuleRow(index, { origin: event.target.value });
2157
- },
2158
- children: [
2159
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2160
- value: "",
2161
- children: t("roles.rule.origin.any")
2162
- }),
2163
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2164
- value: "root",
2165
- children: t("roles.rule.origin.root")
2166
- }),
2167
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2168
- value: "subagent",
2169
- children: t("roles.rule.origin.subagent")
2170
- })
2171
- ]
2172
- })]
2898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2899
+ className: FallbacksCard_module_css_default.ruleCellLabel,
2900
+ children: t("timeSlots.days")
2173
2901
  }),
2174
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2175
- className: FallbacksCard_module_css_default.ruleCell,
2176
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2177
- className: FallbacksCard_module_css_default.ruleCellLabel,
2178
- children: t("roles.rule.provider")
2179
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2180
- className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
2181
- value: providerRaw,
2182
- onChange: (event) => {
2183
- if (event.target.value === providerRaw) return;
2184
- updateRuleRow(index, {
2185
- provider: classifyProvider(event.target.value, catalog),
2186
- model: null
2187
- });
2188
- },
2189
- children: [
2190
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2191
- value: "",
2192
- children: t("roles.rule.provider.any")
2193
- }),
2194
- state.configuredProviders.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2195
- value: entry.provider,
2196
- children: entry.displayName
2197
- }, entry.provider)),
2198
- providerUnconfigured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2199
- value: providerRaw,
2200
- children: `${providerRaw}${t("catalog.unconfigured.short")}`
2201
- }),
2202
- providerOutside && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2203
- value: providerRaw,
2204
- children: `${providerRaw}${t("catalog.outside.short")}`
2205
- })
2206
- ]
2207
- })]
2902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2903
+ className: FallbacksCard_module_css_default.dayRow,
2904
+ children: SLOT_WEEKDAYS.map((day, dayIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2905
+ className: FallbacksCard_module_css_default.dayCell,
2906
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2907
+ type: "checkbox",
2908
+ checked: row.days.includes(dayIndex),
2909
+ disabled: !writable,
2910
+ onChange: () => {
2911
+ updateTimeSlotRow(index, { days: row.days.includes(dayIndex) ? row.days.filter((existing) => existing !== dayIndex) : [...row.days, dayIndex] });
2912
+ }
2913
+ }), t(`timeSlots.day.${day}`)]
2914
+ }, day))
2208
2915
  }),
2209
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2210
- className: FallbacksCard_module_css_default.ruleCell,
2211
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2212
- className: FallbacksCard_module_css_default.ruleCellLabel,
2213
- children: t("roles.rule.model")
2214
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2215
- className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
2216
- value: selectionToRaw(row.model),
2217
- onChange: (event) => {
2218
- updateRuleRow(index, { model: classifyModel(providerRaw, event.target.value, catalog) });
2219
- },
2220
- children: [
2221
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2222
- value: "",
2223
- children: t("roles.rule.model.any")
2224
- }),
2225
- (group?.models ?? []).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2226
- value: model.id,
2227
- children: model.name
2228
- }, model.id)),
2229
- modelOutside && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2230
- value: selectionToRaw(row.model),
2231
- children: `${selectionToRaw(row.model)}${t("catalog.outside.short")}`
2232
- })
2233
- ]
2234
- })]
2235
- }),
2236
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2237
- className: FallbacksCard_module_css_default.ruleCell,
2238
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2239
- className: FallbacksCard_module_css_default.ruleCellLabel,
2240
- children: t("roles.rule.role")
2241
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2242
- className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
2243
- value: row.role,
2244
- disabled: !writable,
2245
- onChange: (event) => {
2246
- updateRuleRow(index, { role: event.target.value });
2247
- },
2248
- children: [
2249
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2250
- value: "",
2251
- children: t("roles.rule.roleSelectPlaceholder")
2252
- }),
2253
- roleOptions.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2254
- value: id,
2255
- children: id === "inherit" ? t("roles.rule.role.inherit") : id
2256
- }, id)),
2257
- roleOutside && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2258
- value: row.role,
2259
- children: `${row.role}${t("roles.rule.roleUndeclared.short")}`
2260
- })
2261
- ]
2262
- })]
2916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2917
+ className: FallbacksCard_module_css_default.hint,
2918
+ children: t("timeSlots.days.hint")
2263
2919
  })
2264
2920
  ]
2265
2921
  }),
2266
- (providerOutside || modelOutside) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2922
+ (row.start !== "" || row.end !== "") && !(HHMM_RE.test(row.start) && HHMM_RE.test(row.end)) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2267
2923
  className: FallbacksCard_module_css_default.hint,
2268
- children: [t("catalog.outside.hint"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2269
- label: t("catalog.outside.tooltip"),
2270
- disabled: !writable
2271
- })]
2272
- }),
2273
- row.role === "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2274
- className: FallbacksCard_module_css_default.hint,
2275
- children: t("validation.ruleRoleRequired")
2276
- }),
2277
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2278
- className: FallbacksCard_module_css_default.cardFoot,
2279
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2280
- type: "button",
2281
- className: `${FallbacksCard_module_css_default.iconButton} ${FallbacksCard_module_css_default.iconButtonDanger}`,
2282
- "data-tip": t("roles.removeRule"),
2283
- "aria-label": t("roles.removeRule"),
2284
- onClick: () => {
2285
- setRuleRows((rows) => rows.filter((_, rowIndex) => rowIndex !== index));
2286
- },
2287
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {})
2288
- })
2924
+ children: t("validation.slotWindow")
2289
2925
  })
2290
- ]
2291
- }, index);
2292
- })
2293
- }),
2294
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2295
- variant: "outline",
2296
- size: "sm",
2297
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
2298
- className: FallbacksCard_module_css_default.addButton,
2299
- onClick: () => {
2300
- setRuleRows((rows) => [...rows, {
2301
- origin: "",
2302
- provider: null,
2303
- model: null,
2304
- role: ""
2305
- }]);
2306
- },
2307
- children: t("roles.addRule")
2308
- })
2309
- ]
2310
- }),
2311
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2312
- className: FallbacksCard_module_css_default.field,
2313
- role: "group",
2314
- "aria-labelledby": "fallbacks-advanced",
2315
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2316
- type: "button",
2317
- className: FallbacksCard_module_css_default.sectionToggle,
2318
- disabled: !writable,
2319
- "aria-expanded": advancedVisible,
2320
- "aria-controls": advancedVisible ? "fallbacks-advanced-body" : void 0,
2321
- "aria-label": t(advancedVisible ? "advanced.collapse" : "advanced.expand"),
2322
- onClick: () => {
2323
- if (writable) setAdvancedOpen(!advancedOpen);
2324
- },
2325
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2326
- id: "fallbacks-advanced",
2327
- className: FallbacksCard_module_css_default.sectionToggleText,
2328
- children: t("advanced.label")
2329
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: advancedVisible ? `${FallbacksCard_module_css_default.chevron} ${FallbacksCard_module_css_default.chevronOpen}` : FallbacksCard_module_css_default.chevron })]
2330
- }), advancedVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2331
- id: "fallbacks-advanced-body",
2332
- children: [
2333
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2334
- className: FallbacksCard_module_css_default.field,
2335
- role: "group",
2336
- "aria-labelledby": "fallbacks-trigger-codes",
2337
- children: [
2338
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2339
- className: FallbacksCard_module_css_default.fieldLabel,
2340
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2341
- id: "fallbacks-trigger-codes",
2342
- children: t("triggerCodes.label")
2343
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2344
- label: t("triggerCodes.tooltip"),
2345
- disabled: !writable
2346
- })]
2347
- }),
2348
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2926
+ ] }),
2927
+ chainEmpty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2349
2928
  className: FallbacksCard_module_css_default.hint,
2350
- children: t("triggerCodes.hint")
2929
+ children: t("validation.slotChainRequired")
2351
2930
  }),
2352
- KNOWN_TRIGGER_CODES.map((code) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2353
- className: FallbacksCard_module_css_default.optionRow,
2354
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2355
- type: "checkbox",
2356
- checked: scalars.triggerCodes.includes(code),
2357
- onChange: (event) => {
2358
- updateScalars((draft) => {
2359
- draft.triggerCodes = withTriggerCode(draft.triggerCodes, code, event.target.checked);
2360
- });
2931
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2932
+ className: FallbacksCard_module_css_default.chainSelectors,
2933
+ children: row.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
2934
+ selector,
2935
+ catalog: catalogOf(state),
2936
+ configuredProviders: state.configuredProviders,
2937
+ disabled: !writable,
2938
+ t,
2939
+ onChange: (patch) => {
2940
+ updateTimeSlotSelector(index, selectorIndex, patch);
2941
+ },
2942
+ onRemove: () => {
2943
+ removeTimeSlotSelector(index, selectorIndex);
2361
2944
  }
2362
- }), t(TRIGGER_CODE_LABELS[code])]
2363
- }, code)),
2364
- unknownCodes.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2365
- className: FallbacksCard_module_css_default.hint,
2366
- children: t("triggerCodes.extra", { codes: unknownCodes.join(", ") })
2367
- })
2368
- ]
2369
- }),
2370
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2371
- className: FallbacksCard_module_css_default.field,
2372
- role: "group",
2373
- "aria-labelledby": "fallbacks-revert-policy",
2374
- children: [
2375
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2376
- className: FallbacksCard_module_css_default.fieldLabel,
2377
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2378
- id: "fallbacks-revert-policy",
2379
- children: t("revertPolicy.label")
2380
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2381
- label: t("revertPolicy.tooltip"),
2382
- disabled: !writable
2383
- })]
2945
+ }, selectorIndex))
2384
2946
  }),
2385
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2386
- className: FallbacksCard_module_css_default.hint,
2387
- children: t("revertPolicy.hint")
2947
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2948
+ variant: "outline",
2949
+ size: "sm",
2950
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
2951
+ className: FallbacksCard_module_css_default.addButton,
2952
+ onClick: () => {
2953
+ addTimeSlotSelector(index);
2954
+ },
2955
+ children: t("timeSlots.selector.add")
2388
2956
  }),
2389
- ["cooldown-expiry", "never"].map((policy) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2390
- className: FallbacksCard_module_css_default.optionRow,
2391
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2392
- type: "radio",
2393
- name: "fallbacks-revert-policy",
2394
- checked: scalars.revertPolicy === policy,
2395
- onChange: () => {
2396
- updateScalars((draft) => {
2397
- draft.revertPolicy = policy;
2398
- });
2399
- }
2400
- }), t(`revertPolicy.${policy}`)]
2401
- }, policy))
2402
- ]
2957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2958
+ className: FallbacksCard_module_css_default.cardFoot,
2959
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2960
+ className: FallbacksCard_module_css_default.rowActions,
2961
+ children: [
2962
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2963
+ type: "button",
2964
+ className: FallbacksCard_module_css_default.iconButton,
2965
+ "data-tip": t("timeSlots.moveUp"),
2966
+ "aria-label": t("timeSlots.moveUp"),
2967
+ disabled: !writable || index === 0,
2968
+ onClick: () => {
2969
+ moveTimeSlotRow(index, -1);
2970
+ },
2971
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
2972
+ }),
2973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2974
+ type: "button",
2975
+ className: FallbacksCard_module_css_default.iconButton,
2976
+ "data-tip": t("timeSlots.moveDown"),
2977
+ "aria-label": t("timeSlots.moveDown"),
2978
+ disabled: !writable || index === timeSlotRows.length - 1,
2979
+ onClick: () => {
2980
+ moveTimeSlotRow(index, 1);
2981
+ },
2982
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
2983
+ }),
2984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2985
+ type: "button",
2986
+ className: `${FallbacksCard_module_css_default.iconButton} ${FallbacksCard_module_css_default.iconButtonDanger}`,
2987
+ "data-tip": t("timeSlots.remove"),
2988
+ "aria-label": t("timeSlots.remove"),
2989
+ onClick: () => {
2990
+ removeTimeSlotRow(index);
2991
+ },
2992
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {})
2993
+ })
2994
+ ]
2995
+ })
2996
+ })
2997
+ ] })]
2998
+ }, index);
2999
+ })
3000
+ }),
3001
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3002
+ className: FallbacksCard_module_css_default.slotAddRow,
3003
+ children: [
3004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3005
+ className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
3006
+ value: presetToAdd,
3007
+ "aria-label": t("timeSlots.presetPlaceholder"),
3008
+ disabled: !writable,
3009
+ onChange: (event) => {
3010
+ setPresetToAdd(event.target.value);
3011
+ },
3012
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3013
+ value: "",
3014
+ children: t("timeSlots.presetPlaceholder")
3015
+ }), SLOT_PRESET_IDS.filter((id) => !timeSlotRows.some((row) => row.kind === "preset" && row.preset === id)).map((id) => {
3016
+ const glmUnconfigured = !glmConfigured && (id === "glm-peak" || id === "glm-valley");
3017
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3018
+ value: id,
3019
+ disabled: glmUnconfigured,
3020
+ children: [t(`timeSlots.preset.${id}.label`), glmUnconfigured ? t("timeSlots.preset.glm.unconfigured") : null]
3021
+ }, id);
3022
+ })]
2403
3023
  }),
2404
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2405
- className: FallbacksCard_module_css_default.numberFields,
2406
- children: [
2407
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2408
- className: FallbacksCard_module_css_default.field,
3024
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3025
+ variant: "outline",
3026
+ size: "sm",
3027
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
3028
+ disabled: !writable || presetToAdd === "",
3029
+ onClick: addPresetSlotRow,
3030
+ children: t("timeSlots.addPreset")
3031
+ }),
3032
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3033
+ variant: "outline",
3034
+ size: "sm",
3035
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
3036
+ disabled: !writable,
3037
+ onClick: addCustomSlotRow,
3038
+ children: t("timeSlots.addCustom")
3039
+ })
3040
+ ]
3041
+ })
3042
+ ]
3043
+ }),
3044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3045
+ className: FallbacksCard_module_css_default.field,
3046
+ role: "group",
3047
+ "aria-labelledby": "fallbacks-root-chain",
3048
+ children: [
3049
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3050
+ className: FallbacksCard_module_css_default.fieldLabel,
3051
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3052
+ id: "fallbacks-root-chain",
3053
+ children: t("rootChain.label")
3054
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3055
+ label: t("rootChain.tooltip"),
3056
+ disabled: !writable
3057
+ })]
3058
+ }),
3059
+ state.catalogStatus === "error" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3060
+ className: FallbacksCard_module_css_default.hint,
3061
+ children: t("catalog.error", { message: state.catalogError })
3062
+ }),
3063
+ state.catalogStatus === "ready" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3064
+ className: FallbacksCard_module_css_default.hint,
3065
+ children: t("catalog.partial", { message: state.catalogError })
3066
+ }),
3067
+ state.catalogStatus === "ready" && (state.groups.length === 0 || state.configuredProviders.length === 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3068
+ className: FallbacksCard_module_css_default.hint,
3069
+ children: t("catalog.empty")
3070
+ }),
3071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3072
+ className: FallbacksCard_module_css_default.list,
3073
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3074
+ className: FallbacksCard_module_css_default.editorCard,
3075
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3076
+ className: FallbacksCard_module_css_default.chainSelectors,
3077
+ children: allDayChainRow.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
3078
+ selector,
3079
+ catalog: catalogOf(state),
3080
+ configuredProviders: state.configuredProviders,
3081
+ disabled: !writable,
3082
+ t,
3083
+ onChange: (patch) => {
3084
+ updateAllDayChainSelector(selectorIndex, patch);
3085
+ },
3086
+ onRemove: () => {
3087
+ removeAllDayChainSelector(selectorIndex);
3088
+ }
3089
+ }, selectorIndex))
3090
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3091
+ variant: "outline",
3092
+ size: "sm",
3093
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
3094
+ className: FallbacksCard_module_css_default.addButton,
3095
+ onClick: addAllDayChainSelector,
3096
+ children: t("timeSlots.selector.add")
3097
+ })]
3098
+ })
3099
+ })
3100
+ ]
3101
+ }),
3102
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3103
+ className: FallbacksCard_module_css_default.field,
3104
+ role: "group",
3105
+ "aria-labelledby": "fallbacks-default-model",
3106
+ children: [
3107
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3108
+ className: FallbacksCard_module_css_default.fieldLabel,
3109
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3110
+ id: "fallbacks-default-model",
3111
+ children: t("defaultModel.label")
3112
+ })
3113
+ }),
3114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3115
+ className: FallbacksCard_module_css_default.hint,
3116
+ children: t("allDay.hint")
3117
+ }),
3118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3119
+ className: FallbacksCard_module_css_default.list,
3120
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3121
+ className: FallbacksCard_module_css_default.editorCard,
3122
+ children: [
3123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3124
+ className: FallbacksCard_module_css_default.optionRow,
3125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3126
+ type: "radio",
3127
+ name: "fallbacks-all-day",
3128
+ checked: allDayModel === ALL_DAY_FLASH,
3129
+ disabled: !writable,
3130
+ onChange: () => {
3131
+ setAllDayModel(ALL_DAY_FLASH);
3132
+ }
3133
+ }), t("allDay.flash")]
3134
+ }),
3135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3136
+ className: FallbacksCard_module_css_default.optionRow,
3137
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3138
+ type: "radio",
3139
+ name: "fallbacks-all-day",
3140
+ checked: allDayModel === ALL_DAY_PRO,
3141
+ disabled: !writable,
3142
+ onChange: () => {
3143
+ setAllDayModel(ALL_DAY_PRO);
3144
+ }
3145
+ }), t("allDay.pro")]
3146
+ }),
3147
+ allDayModel === "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3148
+ className: FallbacksCard_module_css_default.hint,
3149
+ children: t("allDay.nonconforming")
3150
+ })
3151
+ ]
3152
+ })
3153
+ })
3154
+ ]
3155
+ }),
3156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3157
+ className: FallbacksCard_module_css_default.sectionHeading,
3158
+ id: "fallbacks-subagents",
3159
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3160
+ className: FallbacksCard_module_css_default.sectionHeadingText,
3161
+ children: t("subagents.label")
3162
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3163
+ className: FallbacksCard_module_css_default.sectionActions,
3164
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3165
+ type: "button",
3166
+ className: `${FallbacksCard_module_css_default.secondaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
3167
+ disabled: !subDirty || saving,
3168
+ onClick: () => {
3169
+ discardSection("sub");
3170
+ },
3171
+ children: t("discard")
3172
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3173
+ type: "button",
3174
+ className: `${FallbacksCard_module_css_default.primaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
3175
+ disabled: !writable || saving || !subDirty,
3176
+ onClick: () => {
3177
+ save("sub");
3178
+ },
3179
+ children: saving ? t("save.saving") : t("save")
3180
+ })]
3181
+ })]
3182
+ }),
3183
+ validationErrors.sub.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3184
+ className: FallbacksCard_module_css_default.error,
3185
+ role: "alert",
3186
+ children: `${t("validation.blocked")}${validationErrors.sub.join("; ")}`
3187
+ }),
3188
+ lastSaveSection === "sub" && state.status === "error" && state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3189
+ className: FallbacksCard_module_css_default.error,
3190
+ role: "alert",
3191
+ children: t("error.generic", { message: state.error })
3192
+ }),
3193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3194
+ className: FallbacksCard_module_css_default.field,
3195
+ role: "group",
3196
+ "aria-labelledby": "fallbacks-roles-list",
3197
+ children: [
3198
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3199
+ className: FallbacksCard_module_css_default.fieldLabel,
3200
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3201
+ id: "fallbacks-roles-list",
3202
+ children: t("roles.list.label")
3203
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3204
+ label: t("roles.list.tooltip"),
3205
+ disabled: !writable
3206
+ })]
3207
+ }),
3208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3209
+ className: FallbacksCard_module_css_default.hint,
3210
+ children: t("roles.list.hint")
3211
+ }),
3212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3213
+ className: FallbacksCard_module_css_default.list,
3214
+ children: roleRows.map((row, index) => {
3215
+ const invalid = invalidRoleIds?.has(row.id.trim()) ?? false;
3216
+ const seed = seededIds.get(row.id.trim());
3217
+ const roleSummary = row.selectors.map(selectorRowToRaw).find((entry) => entry !== "") ?? row.fallback;
3218
+ const roleExpanded = !row.collapsed || !writable;
3219
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3220
+ className: FallbacksCard_module_css_default.editorCard,
3221
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3222
+ className: FallbacksCard_module_css_default.collapseRow,
3223
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3224
+ type: "button",
3225
+ className: FallbacksCard_module_css_default.collapseToggle,
3226
+ "aria-expanded": roleExpanded,
3227
+ "aria-label": t(roleExpanded ? "roles.collapse" : "roles.expand"),
3228
+ disabled: !writable,
3229
+ onClick: () => {
3230
+ updateRoleRow(index, { collapsed: !row.collapsed });
3231
+ },
2409
3232
  children: [
2410
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2411
- className: FallbacksCard_module_css_default.fieldLabel,
2412
- children: [
2413
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2414
- htmlFor: "fallbacks-cooldown-ms",
2415
- children: t("cooldownMs.label")
2416
- }),
2417
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2418
- label: t("cooldownMs.tooltip"),
2419
- disabled: !writable
2420
- }),
2421
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2422
- className: FallbacksCard_module_css_default.defaultNote,
2423
- children: [
2424
- t("defaults.prefix"),
2425
- ": ",
2426
- state.config.cooldownMs
2427
- ]
2428
- })
2429
- ]
2430
- }),
2431
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2432
- id: "fallbacks-cooldown-ms",
2433
- className: FallbacksCard_module_css_default.input,
2434
- type: "number",
2435
- min: 0,
2436
- value: String(scalars.cooldownMs),
2437
- disabled: !writable,
2438
- onChange: (event) => {
2439
- updateScalars((draft) => {
2440
- draft.cooldownMs = parseCount(event.target.value);
2441
- });
2442
- }
3233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: roleExpanded ? `${FallbacksCard_module_css_default.chevron} ${FallbacksCard_module_css_default.chevronOpen}` : FallbacksCard_module_css_default.chevron }),
3234
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3235
+ className: FallbacksCard_module_css_default.collapseTitle,
3236
+ children: row.id
2443
3237
  }),
2444
3238
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2445
- className: FallbacksCard_module_css_default.hint,
2446
- children: t("cooldownMs.hint")
3239
+ className: FallbacksCard_module_css_default.collapseMeta,
3240
+ children: roleSummary
2447
3241
  })
2448
3242
  ]
3243
+ })
3244
+ }), roleExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3246
+ className: FallbacksCard_module_css_default.ruleGrid,
3247
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3248
+ className: FallbacksCard_module_css_default.ruleCell,
3249
+ children: [
3250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3251
+ className: FallbacksCard_module_css_default.ruleCellLabel,
3252
+ children: t("roles.id")
3253
+ }),
3254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3255
+ className: `${FallbacksCard_module_css_default.input} ${invalid ? FallbacksCard_module_css_default.inputInvalid : ""}`,
3256
+ value: row.id,
3257
+ placeholder: t("roles.idPlaceholder"),
3258
+ "aria-label": t("roles.id"),
3259
+ "aria-invalid": invalid ? true : void 0,
3260
+ disabled: !writable || seed !== void 0,
3261
+ onChange: (event) => {
3262
+ updateRoleRow(index, { id: event.target.value });
3263
+ }
3264
+ }),
3265
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3266
+ className: FallbacksCard_module_css_default.hint,
3267
+ children: t("roles.id.hint")
3268
+ })
3269
+ ]
3270
+ })
3271
+ }),
3272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3273
+ className: FallbacksCard_module_css_default.ruleGrid,
3274
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3275
+ className: FallbacksCard_module_css_default.ruleCell,
3276
+ children: [
3277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3278
+ className: FallbacksCard_module_css_default.ruleCellLabel,
3279
+ children: t("roles.persona")
3280
+ }),
3281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3282
+ rows: 3,
3283
+ className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.inputTextarea}`,
3284
+ value: row.persona,
3285
+ placeholder: t("roles.personaPlaceholder"),
3286
+ "aria-label": t("roles.persona"),
3287
+ disabled: !writable,
3288
+ onChange: (event) => {
3289
+ updateRoleRow(index, { persona: event.target.value });
3290
+ }
3291
+ }),
3292
+ seed !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3293
+ className: FallbacksCard_module_css_default.hint,
3294
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3295
+ className: FallbacksCard_module_css_default.pending,
3296
+ children: t(seed ? "roles.seedOverride" : "roles.seedDefault")
3297
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3298
+ variant: "outline",
3299
+ size: "sm",
3300
+ disabled: !writable || saving,
3301
+ onClick: () => {
3302
+ forceReseed.current = true;
3303
+ controller.revertSeed(row.id.trim()).then((persona) => {
3304
+ if (persona === void 0) return;
3305
+ updateRoleRow(index, { persona });
3306
+ });
3307
+ },
3308
+ children: t("roles.revertPersona")
3309
+ })]
3310
+ })
3311
+ ]
3312
+ })
2449
3313
  }),
2450
3314
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2451
- className: FallbacksCard_module_css_default.field,
2452
- children: [
2453
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2454
- className: FallbacksCard_module_css_default.fieldLabel,
2455
- children: [
2456
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2457
- htmlFor: "fallbacks-max-switches",
2458
- children: t("maxSwitchesPerStep.label")
2459
- }),
2460
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2461
- label: t("maxSwitchesPerStep.tooltip"),
2462
- disabled: !writable
2463
- }),
2464
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2465
- className: FallbacksCard_module_css_default.defaultNote,
2466
- children: [
2467
- t("defaults.prefix"),
2468
- ": ",
2469
- state.config.maxSwitchesPerStep
2470
- ]
2471
- })
2472
- ]
2473
- }),
2474
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2475
- id: "fallbacks-max-switches",
2476
- className: FallbacksCard_module_css_default.input,
2477
- type: "number",
2478
- min: 0,
2479
- value: String(scalars.maxSwitchesPerStep),
3315
+ className: FallbacksCard_module_css_default.chainSelectors,
3316
+ children: [row.selectors.map((selector, selectorIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainSelectorEditor, {
3317
+ selector,
3318
+ catalog: catalogOf(state),
3319
+ configuredProviders: state.configuredProviders,
3320
+ disabled: !writable,
3321
+ t,
3322
+ onChange: (patch) => {
3323
+ updateRoleSelector(index, selectorIndex, patch);
3324
+ },
3325
+ onRemove: () => {
3326
+ removeRoleSelector(index, selectorIndex);
3327
+ }
3328
+ }, selectorIndex)), row.selectors.every((selector) => selectorRowToRaw(selector) === "") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3329
+ className: FallbacksCard_module_css_default.hint,
3330
+ children: seed !== void 0 ? t("roles.seedChainOptional", { id: row.id }) : t("validation.roleChainRequired", { id: row.id })
3331
+ })]
3332
+ }),
3333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3334
+ className: FallbacksCard_module_css_default.ruleGrid,
3335
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3336
+ className: FallbacksCard_module_css_default.ruleCell,
3337
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3338
+ className: FallbacksCard_module_css_default.ruleCellLabel,
3339
+ children: t("roles.fallback")
3340
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3341
+ className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
3342
+ value: row.fallback,
3343
+ "aria-label": t("roles.fallback"),
2480
3344
  disabled: !writable,
2481
3345
  onChange: (event) => {
2482
- updateScalars((draft) => {
2483
- draft.maxSwitchesPerStep = parseCount(event.target.value);
2484
- });
2485
- }
2486
- }),
2487
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2488
- className: FallbacksCard_module_css_default.hint,
2489
- children: t("maxSwitchesPerStep.hint")
2490
- })
2491
- ]
3346
+ updateRoleRow(index, { fallback: event.target.value });
3347
+ },
3348
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3349
+ value: "inherit-root",
3350
+ children: t("roles.fallback.inherit-root")
3351
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3352
+ value: "none",
3353
+ children: t("roles.fallback.none")
3354
+ })]
3355
+ })]
3356
+ })
3357
+ }),
3358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3359
+ variant: "outline",
3360
+ size: "sm",
3361
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
3362
+ className: FallbacksCard_module_css_default.addButton,
3363
+ onClick: () => {
3364
+ addRoleSelector(index);
3365
+ },
3366
+ children: t("roles.selector.add")
2492
3367
  }),
3368
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3369
+ className: FallbacksCard_module_css_default.cardFoot,
3370
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3371
+ type: "button",
3372
+ className: `${FallbacksCard_module_css_default.iconButton} ${FallbacksCard_module_css_default.iconButtonDanger}`,
3373
+ "data-tip": t("roles.remove"),
3374
+ "aria-label": t("roles.remove"),
3375
+ onClick: () => {
3376
+ removeRole(index);
3377
+ },
3378
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {})
3379
+ })
3380
+ })
3381
+ ] })]
3382
+ }, index);
3383
+ })
3384
+ }),
3385
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3386
+ variant: "outline",
3387
+ size: "sm",
3388
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
3389
+ className: FallbacksCard_module_css_default.addButton,
3390
+ onClick: addRole,
3391
+ children: t("roles.add")
3392
+ })
3393
+ ]
3394
+ }),
3395
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3396
+ className: FallbacksCard_module_css_default.field,
3397
+ role: "group",
3398
+ "aria-labelledby": "fallbacks-roles-rules",
3399
+ children: [
3400
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3401
+ className: FallbacksCard_module_css_default.fieldLabel,
3402
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3403
+ id: "fallbacks-roles-rules",
3404
+ children: t("roles.rules")
3405
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3406
+ label: t("roles.rules.tooltip"),
3407
+ disabled: !writable
3408
+ })]
3409
+ }),
3410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3411
+ className: FallbacksCard_module_css_default.hint,
3412
+ children: t("roles.rules.hint")
3413
+ }),
3414
+ state.catalogStatus === "error" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3415
+ className: FallbacksCard_module_css_default.hint,
3416
+ children: t("catalog.error", { message: state.catalogError })
3417
+ }),
3418
+ state.catalogStatus === "ready" && state.catalogError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3419
+ className: FallbacksCard_module_css_default.hint,
3420
+ children: t("catalog.partial", { message: state.catalogError })
3421
+ }),
3422
+ state.catalogStatus === "ready" && (state.groups.length === 0 || state.configuredProviders.length === 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3423
+ className: FallbacksCard_module_css_default.hint,
3424
+ children: t("catalog.empty")
3425
+ }),
3426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3427
+ className: FallbacksCard_module_css_default.list,
3428
+ children: ruleRows.map((row, index) => {
3429
+ const catalog = catalogOf(state);
3430
+ const providerRaw = selectionToRaw(row.provider);
3431
+ const group = catalog?.groups.find((entry) => entry.id === providerRaw);
3432
+ const providerOutside = row.provider?.kind === "outside";
3433
+ const providerUnconfigured = !providerOutside && providerRaw !== "" && (catalog?.providers.some((entry) => entry.provider === providerRaw) ?? false) && !state.configuredProviders.some((entry) => entry.provider === providerRaw);
3434
+ const modelOutside = row.model?.kind === "outside";
3435
+ const roleOutside = row.role !== "" && !roleOptions.includes(row.role);
3436
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3437
+ className: FallbacksCard_module_css_default.editorCard,
3438
+ children: [
2493
3439
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2494
- className: FallbacksCard_module_css_default.field,
3440
+ className: FallbacksCard_module_css_default.ruleGrid,
2495
3441
  children: [
2496
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2497
- className: FallbacksCard_module_css_default.fieldLabel,
2498
- children: [
2499
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2500
- htmlFor: "fallbacks-always-cap",
2501
- children: t("alwaysModeRetryCap.label")
2502
- }),
2503
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
2504
- label: t("alwaysModeRetryCap.tooltip"),
2505
- disabled: !writable
2506
- }),
2507
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2508
- className: FallbacksCard_module_css_default.defaultNote,
2509
- children: [
2510
- t("defaults.prefix"),
2511
- ": ",
2512
- state.config.alwaysModeRetryCap
2513
- ]
2514
- })
2515
- ]
3442
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3443
+ className: FallbacksCard_module_css_default.ruleCell,
3444
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3445
+ className: FallbacksCard_module_css_default.ruleCellLabel,
3446
+ children: t("roles.rule.provider")
3447
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3448
+ className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
3449
+ value: providerRaw,
3450
+ onChange: (event) => {
3451
+ if (event.target.value === providerRaw) return;
3452
+ updateRuleRow(index, {
3453
+ provider: classifyProvider(event.target.value, catalog),
3454
+ model: null
3455
+ });
3456
+ },
3457
+ children: [
3458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3459
+ value: "",
3460
+ children: t("roles.rule.provider.any")
3461
+ }),
3462
+ state.configuredProviders.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3463
+ value: entry.provider,
3464
+ children: entry.displayName
3465
+ }, entry.provider)),
3466
+ providerUnconfigured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3467
+ value: providerRaw,
3468
+ children: `${providerRaw}${t("catalog.unconfigured.short")}`
3469
+ }),
3470
+ providerOutside && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3471
+ value: providerRaw,
3472
+ children: `${providerRaw}${t("catalog.outside.short")}`
3473
+ })
3474
+ ]
3475
+ })]
2516
3476
  }),
2517
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2518
- id: "fallbacks-always-cap",
2519
- className: FallbacksCard_module_css_default.input,
2520
- type: "number",
2521
- min: 0,
2522
- value: String(scalars.alwaysModeRetryCap),
2523
- disabled: !writable,
2524
- onChange: (event) => {
2525
- updateScalars((draft) => {
2526
- draft.alwaysModeRetryCap = parseCount(event.target.value);
2527
- });
2528
- }
3477
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3478
+ className: FallbacksCard_module_css_default.ruleCell,
3479
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3480
+ className: FallbacksCard_module_css_default.ruleCellLabel,
3481
+ children: t("roles.rule.model")
3482
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3483
+ className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
3484
+ value: selectionToRaw(row.model),
3485
+ onChange: (event) => {
3486
+ updateRuleRow(index, { model: classifyModel(providerRaw, event.target.value, catalog) });
3487
+ },
3488
+ children: [
3489
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3490
+ value: "",
3491
+ children: t("roles.rule.model.any")
3492
+ }),
3493
+ (group?.models ?? []).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3494
+ value: model.id,
3495
+ children: model.name
3496
+ }, model.id)),
3497
+ modelOutside && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3498
+ value: selectionToRaw(row.model),
3499
+ children: `${selectionToRaw(row.model)}${t("catalog.outside.short")}`
3500
+ })
3501
+ ]
3502
+ })]
2529
3503
  }),
2530
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2531
- className: FallbacksCard_module_css_default.hint,
2532
- children: t("alwaysModeRetryCap.hint")
3504
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3505
+ className: FallbacksCard_module_css_default.ruleCell,
3506
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3507
+ className: FallbacksCard_module_css_default.ruleCellLabel,
3508
+ children: t("roles.rule.role")
3509
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3510
+ className: `${FallbacksCard_module_css_default.input} ${FallbacksCard_module_css_default.selectInput}`,
3511
+ value: row.role,
3512
+ disabled: !writable,
3513
+ onChange: (event) => {
3514
+ updateRuleRow(index, { role: event.target.value });
3515
+ },
3516
+ children: [
3517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3518
+ value: "",
3519
+ children: t("roles.rule.roleSelectPlaceholder")
3520
+ }),
3521
+ roleOptions.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3522
+ value: id,
3523
+ children: id === "inherit" ? t("roles.rule.role.inherit") : id
3524
+ }, id)),
3525
+ roleOutside && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3526
+ value: row.role,
3527
+ children: `${row.role}${t("roles.rule.roleUndeclared.short")}`
3528
+ })
3529
+ ]
3530
+ })]
2533
3531
  })
2534
3532
  ]
3533
+ }),
3534
+ (providerOutside || modelOutside) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3535
+ className: FallbacksCard_module_css_default.hint,
3536
+ children: [t("catalog.outside.hint"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3537
+ label: t("catalog.outside.tooltip"),
3538
+ disabled: !writable
3539
+ })]
3540
+ }),
3541
+ row.role === "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3542
+ className: FallbacksCard_module_css_default.hint,
3543
+ children: t("validation.ruleRoleRequired")
3544
+ }),
3545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3546
+ className: FallbacksCard_module_css_default.cardFoot,
3547
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3548
+ type: "button",
3549
+ className: `${FallbacksCard_module_css_default.iconButton} ${FallbacksCard_module_css_default.iconButtonDanger}`,
3550
+ "data-tip": t("roles.removeRule"),
3551
+ "aria-label": t("roles.removeRule"),
3552
+ onClick: () => {
3553
+ setRuleRows((rows) => rows.filter((_, rowIndex) => rowIndex !== index));
3554
+ },
3555
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {})
3556
+ })
2535
3557
  })
2536
3558
  ]
2537
- })
2538
- ]
2539
- })]
2540
- })
2541
- ]
2542
- })
2543
- ]
2544
- }),
2545
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2546
- className: FallbacksCard_module_css_default.statusBlock,
2547
- children: [
2548
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2549
- className: FallbacksCard_module_css_default.statusTitle,
2550
- children: t("status.title")
2551
- }),
2552
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2553
- className: FallbacksCard_module_css_default.statusLine,
2554
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2555
- className: FallbacksCard_module_css_default.statusLineLabel,
2556
- children: t("status.effectiveModel.label")
2557
- }), effectiveModelLine]
2558
- }),
2559
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2560
- className: FallbacksCard_module_css_default.statusLine,
2561
- role: state.switchesStatus === "error" ? "alert" : void 0,
2562
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2563
- className: FallbacksCard_module_css_default.statusLineLabel,
2564
- children: t("status.switches.label")
2565
- }), switchesLine]
2566
- }),
2567
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2568
- className: FallbacksCard_module_css_default.statusLine,
2569
- children: t("status.selectionNote")
2570
- })
2571
- ]
2572
- }),
2573
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2574
- className: FallbacksCard_module_css_default.footer,
2575
- children: [
2576
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2577
- type: "button",
2578
- className: FallbacksCard_module_css_default.secondaryButton,
2579
- disabled: !dirty || saving,
2580
- onClick: discard,
2581
- children: t("discard")
2582
- }),
2583
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2584
- type: "button",
2585
- className: FallbacksCard_module_css_default.secondaryButton,
2586
- disabled: !writable || saving,
2587
- onClick: () => {
2588
- setConfirmingReset(true);
2589
- },
2590
- children: t("reset")
2591
- }),
2592
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2593
- type: "button",
2594
- className: FallbacksCard_module_css_default.primaryButton,
2595
- disabled: !writable || saving || !dirty,
2596
- onClick: save,
2597
- children: saving ? t("save.saving") : t("save")
2598
- })
2599
- ]
2600
- })
2601
- ]
2602
- }),
2603
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2604
- open: confirmingReset,
2605
- onClose: () => {
2606
- if (!resetting) setConfirmingReset(false);
2607
- },
2608
- title: t("reset.confirmTitle"),
2609
- closeLabel: t("close"),
2610
- description: t("reset.confirm"),
2611
- className: FallbacksCard_module_css_default.resetDialog,
2612
- footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2613
- variant: "outline",
2614
- autoFocus: true,
2615
- disabled: resetting,
2616
- onClick: () => {
2617
- setConfirmingReset(false);
2618
- },
2619
- children: t("reset.confirm.cancel")
2620
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2621
- variant: "outline",
2622
- className: FallbacksCard_module_css_default.confirmDanger,
2623
- disabled: resetting,
2624
- onClick: confirmReset,
2625
- children: resetting ? t("reset.saving") : t("reset.confirm.action")
2626
- })] })
2627
- })
2628
- ]
3559
+ }, index);
3560
+ })
3561
+ }),
3562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3563
+ variant: "outline",
3564
+ size: "sm",
3565
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
3566
+ className: FallbacksCard_module_css_default.addButton,
3567
+ onClick: () => {
3568
+ setRuleRows((rows) => [...rows, {
3569
+ provider: null,
3570
+ model: null,
3571
+ role: ""
3572
+ }]);
3573
+ },
3574
+ children: t("roles.addRule")
3575
+ })
3576
+ ]
3577
+ }),
3578
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3579
+ className: FallbacksCard_module_css_default.field,
3580
+ role: "group",
3581
+ "aria-labelledby": "fallbacks-advanced",
3582
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3583
+ type: "button",
3584
+ className: FallbacksCard_module_css_default.sectionToggle,
3585
+ disabled: !writable,
3586
+ "aria-expanded": advancedVisible,
3587
+ "aria-controls": advancedVisible ? "fallbacks-advanced-body" : void 0,
3588
+ "aria-label": t(advancedVisible ? "advanced.collapse" : "advanced.expand"),
3589
+ onClick: () => {
3590
+ if (writable) setAdvancedOpen(!advancedOpen);
3591
+ },
3592
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3593
+ id: "fallbacks-advanced",
3594
+ className: FallbacksCard_module_css_default.sectionToggleText,
3595
+ children: t("advanced.label")
3596
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: advancedVisible ? `${FallbacksCard_module_css_default.chevron} ${FallbacksCard_module_css_default.chevronOpen}` : FallbacksCard_module_css_default.chevron })]
3597
+ }), advancedVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3598
+ id: "fallbacks-advanced-body",
3599
+ children: [
3600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3601
+ className: FallbacksCard_module_css_default.checkboxRow,
3602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3603
+ className: FallbacksCard_module_css_default.checkLabel,
3604
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3605
+ className: FallbacksCard_module_css_default.checkLabelTitle,
3606
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3607
+ htmlFor: "fallbacks-role-automatch",
3608
+ children: t("roleAutoMatch.label")
3609
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3610
+ label: t("roleAutoMatch.tooltip"),
3611
+ disabled: !writable
3612
+ })]
3613
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3614
+ className: FallbacksCard_module_css_default.checkLabelDesc,
3615
+ children: t("roleAutoMatch.hint")
3616
+ })]
3617
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3618
+ id: "fallbacks-role-automatch",
3619
+ type: "checkbox",
3620
+ className: FallbacksCard_module_css_default.checkbox,
3621
+ checked: scalars.roleAutoMatch,
3622
+ disabled: !writable,
3623
+ onChange: (event) => {
3624
+ updateScalars((draft) => {
3625
+ draft.roleAutoMatch = event.target.checked;
3626
+ });
3627
+ }
3628
+ })]
3629
+ }),
3630
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3631
+ className: FallbacksCard_module_css_default.field,
3632
+ role: "group",
3633
+ "aria-labelledby": "fallbacks-trigger-codes",
3634
+ children: [
3635
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3636
+ className: FallbacksCard_module_css_default.fieldLabel,
3637
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3638
+ id: "fallbacks-trigger-codes",
3639
+ children: t("triggerCodes.label")
3640
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3641
+ label: t("triggerCodes.tooltip"),
3642
+ disabled: !writable
3643
+ })]
3644
+ }),
3645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3646
+ className: FallbacksCard_module_css_default.hint,
3647
+ children: t("triggerCodes.hint")
3648
+ }),
3649
+ KNOWN_TRIGGER_CODES.map((code) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3650
+ className: FallbacksCard_module_css_default.optionRow,
3651
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3652
+ type: "checkbox",
3653
+ checked: scalars.triggerCodes.includes(code),
3654
+ onChange: (event) => {
3655
+ updateScalars((draft) => {
3656
+ draft.triggerCodes = withTriggerCode(draft.triggerCodes, code, event.target.checked);
3657
+ });
3658
+ }
3659
+ }), t(TRIGGER_CODE_LABELS[code])]
3660
+ }, code)),
3661
+ unknownCodes.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3662
+ className: FallbacksCard_module_css_default.hint,
3663
+ children: t("triggerCodes.extra", { codes: unknownCodes.join(", ") })
3664
+ })
3665
+ ]
3666
+ }),
3667
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3668
+ className: FallbacksCard_module_css_default.field,
3669
+ role: "group",
3670
+ "aria-labelledby": "fallbacks-revert-policy",
3671
+ children: [
3672
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3673
+ className: FallbacksCard_module_css_default.fieldLabel,
3674
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3675
+ id: "fallbacks-revert-policy",
3676
+ children: t("revertPolicy.label")
3677
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3678
+ label: t("revertPolicy.tooltip"),
3679
+ disabled: !writable
3680
+ })]
3681
+ }),
3682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3683
+ className: FallbacksCard_module_css_default.hint,
3684
+ children: t("revertPolicy.hint")
3685
+ }),
3686
+ ["cooldown-expiry", "never"].map((policy) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3687
+ className: FallbacksCard_module_css_default.optionRow,
3688
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3689
+ type: "radio",
3690
+ name: "fallbacks-revert-policy",
3691
+ checked: scalars.revertPolicy === policy,
3692
+ onChange: () => {
3693
+ updateScalars((draft) => {
3694
+ draft.revertPolicy = policy;
3695
+ });
3696
+ }
3697
+ }), t(`revertPolicy.${policy}`)]
3698
+ }, policy))
3699
+ ]
3700
+ }),
3701
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3702
+ className: FallbacksCard_module_css_default.numberFields,
3703
+ children: [
3704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3705
+ className: FallbacksCard_module_css_default.field,
3706
+ children: [
3707
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3708
+ className: FallbacksCard_module_css_default.fieldLabel,
3709
+ children: [
3710
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3711
+ htmlFor: "fallbacks-cooldown-ms",
3712
+ children: t("cooldownMs.label")
3713
+ }),
3714
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3715
+ label: t("cooldownMs.tooltip"),
3716
+ disabled: !writable
3717
+ }),
3718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3719
+ className: FallbacksCard_module_css_default.defaultNote,
3720
+ children: [
3721
+ t("defaults.prefix"),
3722
+ ": ",
3723
+ state.config.cooldownMs
3724
+ ]
3725
+ })
3726
+ ]
3727
+ }),
3728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3729
+ id: "fallbacks-cooldown-ms",
3730
+ className: FallbacksCard_module_css_default.input,
3731
+ type: "number",
3732
+ min: 0,
3733
+ value: String(scalars.cooldownMs),
3734
+ disabled: !writable,
3735
+ onChange: (event) => {
3736
+ updateScalars((draft) => {
3737
+ draft.cooldownMs = parseCount(event.target.value);
3738
+ });
3739
+ }
3740
+ }),
3741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3742
+ className: FallbacksCard_module_css_default.hint,
3743
+ children: t("cooldownMs.hint")
3744
+ })
3745
+ ]
3746
+ }),
3747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3748
+ className: FallbacksCard_module_css_default.field,
3749
+ children: [
3750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3751
+ className: FallbacksCard_module_css_default.fieldLabel,
3752
+ children: [
3753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3754
+ htmlFor: "fallbacks-max-switches",
3755
+ children: t("maxSwitchesPerStep.label")
3756
+ }),
3757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3758
+ label: t("maxSwitchesPerStep.tooltip"),
3759
+ disabled: !writable
3760
+ }),
3761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3762
+ className: FallbacksCard_module_css_default.defaultNote,
3763
+ children: [
3764
+ t("defaults.prefix"),
3765
+ ": ",
3766
+ state.config.maxSwitchesPerStep
3767
+ ]
3768
+ })
3769
+ ]
3770
+ }),
3771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3772
+ id: "fallbacks-max-switches",
3773
+ className: FallbacksCard_module_css_default.input,
3774
+ type: "number",
3775
+ min: 0,
3776
+ value: String(scalars.maxSwitchesPerStep),
3777
+ disabled: !writable,
3778
+ onChange: (event) => {
3779
+ updateScalars((draft) => {
3780
+ draft.maxSwitchesPerStep = parseCount(event.target.value);
3781
+ });
3782
+ }
3783
+ }),
3784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3785
+ className: FallbacksCard_module_css_default.hint,
3786
+ children: t("maxSwitchesPerStep.hint")
3787
+ })
3788
+ ]
3789
+ }),
3790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3791
+ className: FallbacksCard_module_css_default.field,
3792
+ children: [
3793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3794
+ className: FallbacksCard_module_css_default.fieldLabel,
3795
+ children: [
3796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3797
+ htmlFor: "fallbacks-always-cap",
3798
+ children: t("alwaysModeRetryCap.label")
3799
+ }),
3800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoHint, {
3801
+ label: t("alwaysModeRetryCap.tooltip"),
3802
+ disabled: !writable
3803
+ }),
3804
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3805
+ className: FallbacksCard_module_css_default.defaultNote,
3806
+ children: [
3807
+ t("defaults.prefix"),
3808
+ ": ",
3809
+ state.config.alwaysModeRetryCap
3810
+ ]
3811
+ })
3812
+ ]
3813
+ }),
3814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3815
+ id: "fallbacks-always-cap",
3816
+ className: FallbacksCard_module_css_default.input,
3817
+ type: "number",
3818
+ min: 0,
3819
+ value: String(scalars.alwaysModeRetryCap),
3820
+ disabled: !writable,
3821
+ onChange: (event) => {
3822
+ updateScalars((draft) => {
3823
+ draft.alwaysModeRetryCap = parseCount(event.target.value);
3824
+ });
3825
+ }
3826
+ }),
3827
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3828
+ className: FallbacksCard_module_css_default.hint,
3829
+ children: t("alwaysModeRetryCap.hint")
3830
+ })
3831
+ ]
3832
+ })
3833
+ ]
3834
+ }),
3835
+ validationErrors.advanced.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3836
+ className: FallbacksCard_module_css_default.error,
3837
+ role: "alert",
3838
+ children: `${t("validation.blocked")}${validationErrors.advanced.join("; ")}`
3839
+ }),
3840
+ lastSaveSection === "advanced" && state.status === "error" && state.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3841
+ className: FallbacksCard_module_css_default.error,
3842
+ role: "alert",
3843
+ children: t("error.generic", { message: state.error })
3844
+ }),
3845
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3846
+ className: FallbacksCard_module_css_default.sectionActions,
3847
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3848
+ type: "button",
3849
+ className: `${FallbacksCard_module_css_default.secondaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
3850
+ disabled: !advancedDirty || saving,
3851
+ onClick: () => {
3852
+ discardSection("advanced");
3853
+ },
3854
+ children: t("discard")
3855
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3856
+ type: "button",
3857
+ className: `${FallbacksCard_module_css_default.primaryButton} ${FallbacksCard_module_css_default.sectionAction}`,
3858
+ disabled: !writable || saving || !advancedDirty,
3859
+ onClick: () => {
3860
+ save("advanced");
3861
+ },
3862
+ children: saving ? t("save.saving") : t("save")
3863
+ })]
3864
+ })
3865
+ ]
3866
+ })]
3867
+ })
3868
+ ]
3869
+ })
3870
+ ]
3871
+ }),
3872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3873
+ className: FallbacksCard_module_css_default.statusBlock,
3874
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3875
+ className: FallbacksCard_module_css_default.statusTitle,
3876
+ children: t("status.title")
3877
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3878
+ className: FallbacksCard_module_css_default.statusLine,
3879
+ role: state.switchesStatus === "error" ? "alert" : void 0,
3880
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3881
+ className: FallbacksCard_module_css_default.statusLineLabel,
3882
+ children: t("status.switches.label")
3883
+ }), switchesLine]
3884
+ })]
3885
+ })
3886
+ ]
3887
+ })]
2629
3888
  });
2630
3889
  }
2631
3890
  //#endregion
@@ -2700,12 +3959,13 @@ window.__ModuleLoader__.load({
2700
3959
  else if (latestSwitch === void 0) summary = t("general.switch.empty");
2701
3960
  else {
2702
3961
  const reasonKey = SWITCH_REASON_KEYS[latestSwitch.reason];
2703
- summary = t("general.switch", {
3962
+ const params = {
2704
3963
  from: `${latestSwitch.from.provider}/${latestSwitch.from.model}`,
2705
3964
  to: `${latestSwitch.to.provider}/${latestSwitch.to.model}`,
2706
3965
  role: latestSwitch.role,
2707
3966
  reason: reasonKey === void 0 ? latestSwitch.reason : t(reasonKey)
2708
- });
3967
+ };
3968
+ summary = latestSwitch.reason === "role-inject" ? t("general.switch.roleInject", params) : t("general.switch", params);
2709
3969
  }
2710
3970
  const alert = state.status === "error" || state.switchesStatus === "error";
2711
3971
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2754,7 +4014,7 @@ window.__ModuleLoader__.load({
2754
4014
  }
2755
4015
  //#endregion
2756
4016
  //#region \0dsh-css:/home/runner/work/dsh-llm-fallbacks/dsh-llm-fallbacks/src/client/ConversationFallbackSwitch.module.css.mjs
2757
- const css = "\n\n._ea99bbef_switchRow {\n display: flex;\n align-items: center;\n padding: 2px 0;\n font-size: 14px;\n line-height: 24px;\n}\n\n\n._02580bfd_switchTitle {\n flex: none;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n._88b2d18b_switchSep {\n flex: none;\n width: 2px;\n height: 2px;\n margin: 0 8px;\n border-radius: 1px;\n background: var(--dsw-alias-label-caption);\n}\n\n\n._7c8c1b6f_switchSummary {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-tertiary);\n}\n";
4017
+ const css = "\n\n._ea99bbef_switchRow {\n display: flex;\n align-items: center;\n padding: 2px 0;\n font-size: 14px;\n line-height: 24px;\n}\n\n\n._02580bfd_switchTitle {\n flex: none;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n._88b2d18b_switchSep {\n flex: none;\n width: 2px;\n height: 2px;\n margin: 0 8px;\n border-radius: 1px;\n background: var(--dsw-alias-label-caption);\n}\n\n\n._7c8c1b6f_switchSummary {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-tertiary);\n}\n\n\n._fdc977bc_roleBadge {\n flex: none;\n max-width: 96px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 12px;\n line-height: 20px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-primary);\n}\n\n\n._dce8ba28_roleModelMap {\n flex: 0 1 auto;\n min-width: 0;\n margin-left: 8px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n";
2758
4018
  const tagId = "dsh-llm-fallbacks/ConversationFallbackSwitch.module.css";
2759
4019
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2760
4020
  const tag = document.createElement("style");
@@ -2767,7 +4027,9 @@ window.__ModuleLoader__.load({
2767
4027
  "switchRow": "_ea99bbef_switchRow",
2768
4028
  "switchTitle": "_02580bfd_switchTitle",
2769
4029
  "switchSep": "_88b2d18b_switchSep",
2770
- "switchSummary": "_7c8c1b6f_switchSummary"
4030
+ "switchSummary": "_7c8c1b6f_switchSummary",
4031
+ "roleBadge": "_fdc977bc_roleBadge",
4032
+ "roleModelMap": "_dce8ba28_roleModelMap"
2771
4033
  };
2772
4034
  //#endregion
2773
4035
  //#region src/client/ConversationFallbackSwitch.tsx
@@ -2843,12 +4105,8 @@ window.__ModuleLoader__.load({
2843
4105
  })
2844
4106
  });
2845
4107
  const reasonKey = SWITCH_REASON_KEYS[data.reason];
2846
- const summary = t("chat.switch.summary", {
2847
- from: `${data.from.provider}/${data.from.model}`,
2848
- to: `${data.to.provider}/${data.to.model}`,
2849
- role: data.role,
2850
- reason: reasonKey === void 0 ? data.reason : t(reasonKey)
2851
- });
4108
+ const reason = reasonKey === void 0 ? data.reason : t(reasonKey);
4109
+ const isRoleMapped = data.role !== "inherit";
2852
4110
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2853
4111
  className: ConversationFallbackSwitch_module_css_default.switchRow,
2854
4112
  role: "status",
@@ -2861,9 +4119,34 @@ window.__ModuleLoader__.load({
2861
4119
  className: ConversationFallbackSwitch_module_css_default.switchSep,
2862
4120
  "aria-hidden": "true"
2863
4121
  }),
2864
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4122
+ isRoleMapped ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4124
+ className: ConversationFallbackSwitch_module_css_default.roleBadge,
4125
+ title: data.role,
4126
+ children: data.role
4127
+ }),
4128
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4129
+ className: ConversationFallbackSwitch_module_css_default.roleModelMap,
4130
+ children: t("chat.switch.roleMap", {
4131
+ role: data.role,
4132
+ model: `${data.to.provider}/${data.to.model}`
4133
+ })
4134
+ }),
4135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4136
+ className: ConversationFallbackSwitch_module_css_default.switchSep,
4137
+ "aria-hidden": "true"
4138
+ }),
4139
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4140
+ className: ConversationFallbackSwitch_module_css_default.switchSummary,
4141
+ children: t("chat.switch.summary.roleInject", { reason })
4142
+ })
4143
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2865
4144
  className: ConversationFallbackSwitch_module_css_default.switchSummary,
2866
- children: summary
4145
+ children: t("chat.switch.summary", {
4146
+ from: `${data.from.provider}/${data.from.model}`,
4147
+ to: `${data.to.provider}/${data.to.model}`,
4148
+ reason
4149
+ })
2867
4150
  })
2868
4151
  ]
2869
4152
  });
@@ -2945,8 +4228,7 @@ window.__ModuleLoader__.load({
2945
4228
  ctx.slots.inject("settings.plugin.item", function* () {
2946
4229
  yield ctx.slots.register({
2947
4230
  name: "settings.plugin.item",
2948
- id: "fallbacks",
2949
- order: 30,
4231
+ key: "fallbacks",
2950
4232
  locale: NS,
2951
4233
  inject: () => ({
2952
4234
  controller,