@jxgame2020/dsh-token-quota 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -37,6 +37,7 @@ window.__ModuleLoader__.load({
37
37
  onFull: "stop",
38
38
  checkUpdates: true,
39
39
  dimWhenIdle: false,
40
+ order: [],
40
41
  musicEnabled: false,
41
42
  musicVolume: .5,
42
43
  musicStyle: "pentatonic",
@@ -82,6 +83,9 @@ window.__ModuleLoader__.load({
82
83
  setDimWhenIdle: (d, dimWhenIdle) => {
83
84
  d.dimWhenIdle = dimWhenIdle;
84
85
  },
86
+ setOrder: (d, order) => {
87
+ d.order = order;
88
+ },
85
89
  setMusicEnabled: (d, enabled) => {
86
90
  d.musicEnabled = enabled;
87
91
  },
@@ -95,7 +99,7 @@ window.__ModuleLoader__.load({
95
99
  d.musicOnlyCurrentSession = only;
96
100
  },
97
101
  setUpgrade: (d, upgrade) => {
98
- const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.31" ? upgrade : null;
102
+ const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.32" ? upgrade : null;
99
103
  if (d.upgrade === null || d.upgrade.latestVersion !== normalized?.latestVersion) d.upgradeDismissed = false;
100
104
  d.upgrade = normalized;
101
105
  },
@@ -157,9 +161,11 @@ window.__ModuleLoader__.load({
157
161
  * @param groups - advisory provider groups of the current session.
158
162
  * @param snapshot - latest quota snapshot, or null before the first one.
159
163
  * @param current - current model selection reported by the Host.
160
- * @returns rows sorted by key.
164
+ * @param order - user's drag-to-reorder preference (model keys); listed keys
165
+ * come first in that exact order, the rest follow in catalog order.
166
+ * @returns rows in display order.
161
167
  */
162
- function mergeModelRows(groups, snapshot, current) {
168
+ function mergeModelRows(groups, snapshot, current, order = []) {
163
169
  const entryByKey = new Map((snapshot?.entries ?? []).map((entry) => [entry.key, entry]));
164
170
  const rows = /* @__PURE__ */ new Map();
165
171
  for (const group of groups) for (const model of group.models) {
@@ -187,11 +193,33 @@ window.__ModuleLoader__.load({
187
193
  current: current !== null && current.provider === entry.provider && current.model === entry.model
188
194
  });
189
195
  }
190
- return [...rows.values()].sort((left, right) => left.key.localeCompare(right.key));
196
+ const rank = new Map(order.map((key, index) => [key, index]));
197
+ return [...rows.values()].sort((left, right) => {
198
+ const leftRank = rank.get(left.key);
199
+ const rightRank = rank.get(right.key);
200
+ if (leftRank !== void 0 && rightRank !== void 0) return leftRank - rightRank;
201
+ if (leftRank !== void 0) return -1;
202
+ if (rightRank !== void 0) return 1;
203
+ return left.key.localeCompare(right.key);
204
+ });
205
+ }
206
+ /**
207
+ * Move one key to another key's position, returning the full reordered key
208
+ * list (the shape persisted in the settings document's `order`).
209
+ */
210
+ function reorderKeys(keys, from, to) {
211
+ const next = [...keys];
212
+ const fromIndex = next.indexOf(from);
213
+ const toIndex = next.indexOf(to);
214
+ if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) return next;
215
+ const [moved] = next.splice(fromIndex, 1);
216
+ if (moved === void 0) return next;
217
+ next.splice(toIndex, 0, moved);
218
+ return next;
191
219
  }
192
220
  //#endregion
193
221
  //#region \0dsh-css:/Users/jxgame/Desktop/works/deepseek-harness-package/dsh-token-quota/packages/token-quota/src/client/TokenQuotaPanel.module.css.mjs
194
- const css = ".NPLLIq_panel{box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);pointer-events:auto;z-index:40;opacity:1;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);transition:opacity .18s;display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.NPLLIq_panelDim{opacity:.55}.NPLLIq_panelDimStrong{opacity:.3}.NPLLIq_header{border-bottom:1px solid var(--dsw-alias-border-l1);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:flex-start;gap:8px;padding:10px 12px;display:flex}.NPLLIq_header:active{cursor:grabbing}.NPLLIq_headerText{flex-direction:column;gap:2px;min-width:0;display:flex}.NPLLIq_title{color:var(--dsw-alias-label-primary);align-items:baseline;gap:6px;font-size:13px;font-weight:600;display:flex}.NPLLIq_version{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:10px;font-weight:500}.NPLLIq_versionNew{color:var(--dsw-alias-status-warning-foreground,#b56900);background:var(--dsw-alias-status-warning-background,#fff6d7);cursor:default;border-radius:4px;padding:1px 6px}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_settingsBtnActive{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2,#0000000f);box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary);font-weight:700}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_upgradeBanner{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-divider-1,#0000001a);border-radius:8px;margin:6px 12px;padding:10px 30px 10px 10px;font-size:12px;position:relative}@keyframes NPLLIq_upgradeBannerFlash{0%,to{border-color:var(--dsw-alias-divider-1,#0000001a);box-shadow:0 0 #f0a02000}50%{border-color:var(--dsw-alias-status-warning-foreground,#b56900);box-shadow:0 0 0 5px #f0a02073}}.NPLLIq_upgradeBannerFlash{border-color:var(--dsw-alias-status-warning-foreground,#b56900);animation:.6s ease-in-out 4 NPLLIq_upgradeBannerFlash}.NPLLIq_upgradeBannerText{margin-bottom:6px;font-weight:600}.NPLLIq_upgradeCommands{flex-direction:column;gap:4px;display:flex}.NPLLIq_upgradeCommandRow{align-items:center;gap:6px;display:flex}.NPLLIq_upgradeCommand{background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:nowrap;border-radius:4px;flex:1;min-width:0;padding:4px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow-x:auto}.NPLLIq_copyBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;flex:none;padding:3px 8px;font-size:11px;transition:transform 80ms,background .12s,color .12s}.NPLLIq_copyBtn:hover{background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_copyBtn:active{transform:scale(.92)}.NPLLIq_copyBtnDone{background:var(--dsw-alias-status-success-background,#14963c1f);border-color:var(--dsw-alias-status-success-foreground,#0a7d32);color:var(--dsw-alias-status-success-foreground,#0a7d32);font-weight:600}.NPLLIq_upgradeHint{color:var(--dsw-alias-label-secondary);margin-top:6px;font-size:11px}.NPLLIq_upgradeClose{width:20px;height:20px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;font-size:16px;line-height:1;position:absolute;top:4px;right:6px}.NPLLIq_upgradeClose:hover{background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary)}.NPLLIq_checkUpdatesRow{justify-content:space-between;align-items:center;gap:12px;display:flex}.NPLLIq_checkUpdatesLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}.NPLLIq_checkUpdatesLabel input{margin:0}.NPLLIq_checkUpdatesLabel input[type=range]{min-width:90px;accent-color:var(--dsw-alias-brand-primary);flex:1}.NPLLIq_checkUpdatesLabel select{background:var(--dsw-alias-bg-layer-2,#0000000a);min-width:90px;color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-divider-1,#00000026);border-radius:4px;flex:1;padding:2px 4px;font-size:12px}.NPLLIq_checkBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-align:center;border-radius:4px;flex:none;min-width:72px;padding:4px 10px;font-size:12px}.NPLLIq_checkBtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_checkBtn:disabled{opacity:.6;cursor:default}.NPLLIq_checkErrorHint{color:var(--dsw-alias-state-error-primary,#c00);background:var(--dsw-alias-state-error-background,#ff00000f);border:1px solid var(--dsw-alias-state-error-border,#b4000040);word-break:break-all;border-radius:6px;margin-top:8px;padding:6px 8px;font-size:11px;line-height:1.4}.NPLLIq_dialog{z-index:100;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;gap:12px;width:430px;max-width:94vw;max-height:80vh;padding:14px;display:flex;position:fixed;top:50%;left:50%;overflow-y:auto;transform:translate(-50%,-50%);box-shadow:0 8px 28px #00000040}.NPLLIq_dialogTabs{border-bottom:1px solid var(--dsw-alias-divider-1,#00000014);gap:2px;margin:-6px -14px 0;padding:0 14px;display:flex}.NPLLIq_dialogTab{appearance:none;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px 8px;font-size:12px}.NPLLIq_dialogTab:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_dialogTabActive{color:var(--dsw-alias-brand-primary);border-bottom-color:var(--dsw-alias-brand-primary);font-weight:600}.NPLLIq_settingsDialog{height:min(560px,80vh)}.NPLLIq_dialogTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.NPLLIq_dialogSection{flex-direction:column;gap:6px;display:flex}.NPLLIq_dialogLabel{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500}.NPLLIq_monitorHint{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_dialogHint{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:11px;line-height:1.4}.NPLLIq_monitorList{flex-direction:column;gap:2px;max-height:180px;display:flex;overflow-y:auto}.NPLLIq_monitorRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:2px 4px;font-size:12px;display:flex}.NPLLIq_monitorRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_monitorName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.NPLLIq_radioRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:3px 4px;font-size:12px;display:flex}.NPLLIq_radioRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_dialogActions{justify-content:flex-end;gap:6px;display:flex}.NPLLIq_dialogBtn,.NPLLIq_dialogBtnPrimary{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;border-radius:6px;padding:4px 12px;font-size:11px}.NPLLIq_dialogBtn{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.NPLLIq_dialogBtnPrimary{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay)}.NPLLIq_dialogHeader{z-index:2;background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:center;gap:8px;margin:-14px -14px 0;padding:14px 14px 8px;display:flex;position:sticky;top:-14px}.NPLLIq_dialogHeader:active{cursor:grabbing}.NPLLIq_dialogClose{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;font-size:18px;line-height:1;display:flex}.NPLLIq_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_resetRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.NPLLIq_resetSelect{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:3px 6px;font-size:12px}.NPLLIq_logTable{border-collapse:collapse;width:100%;font-size:11px}.NPLLIq_logTable th{text-align:left;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);padding:4px 6px;font-weight:500}.NPLLIq_logTable td{color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l1);font-variant-numeric:tabular-nums;padding:4px 6px}.NPLLIq_logDayCol{white-space:nowrap;color:var(--dsw-alias-label-secondary)}.NPLLIq_logModelCol{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.NPLLIq_logUsedCol{text-align:right}.NPLLIq_logDialog{gap:0;width:380px;max-height:min(72vh,480px);padding:0}.NPLLIq_logHeader{background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;flex:none;justify-content:space-between;align-items:center;gap:8px;padding:14px 14px 8px;display:flex}.NPLLIq_logHeader:active{cursor:grabbing}.NPLLIq_logScroll{flex:1;min-height:0;padding:0 14px 14px;overflow:auto}.NPLLIq_logToolbar{flex:1;align-items:center;gap:6px;display:flex}.NPLLIq_logSearch{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);min-width:0;color:var(--dsw-alias-label-primary);border-radius:4px;flex:1;padding:3px 6px;font-size:11px}.NPLLIq_logSearch:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}.NPLLIq_logClearBtn{border:1px solid var(--dsw-alias-state-error-border,#b400004d);color:var(--dsw-alias-state-error-primary,#c00);cursor:pointer;white-space:nowrap;background:0 0;border-radius:4px;flex:none;padding:3px 8px;font-size:11px}.NPLLIq_logClearBtn:hover{background:var(--dsw-alias-state-error-background,#ff00000f)}.NPLLIq_logPager{border-top:1px solid var(--dsw-alias-divider-1,#00000014);background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);flex:none;justify-content:space-between;align-items:center;gap:8px;padding:6px 14px;font-size:11px;display:flex}.NPLLIq_logPagerInfo{white-space:nowrap}.NPLLIq_logPagerControls{align-items:center;gap:4px;display:flex}.NPLLIq_logPagerBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:24px;height:24px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;font-size:13px;line-height:1}.NPLLIq_logPagerBtn:disabled{opacity:.4;cursor:default}.NPLLIq_logPagerPages{text-align:center;min-width:50px;color:var(--dsw-alias-label-secondary)}.NPLLIq_logPageSizeSelect{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;padding:2px 4px;font-size:11px}.NPLLIq_dialogOverlay{z-index:100001;background:#0000004d;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.NPLLIq_logClearDialog{gap:0;width:320px;padding:16px}.NPLLIq_dialogBody{color:var(--dsw-alias-label-secondary);margin:6px 0 10px;font-size:12px;line-height:1.5}.NPLLIq_logClearDate{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:100%;color:var(--dsw-alias-label-primary);border-radius:6px;margin-bottom:12px;padding:6px 8px;font-size:13px}.NPLLIq_logClearDate:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}";
222
+ const css = ".NPLLIq_panel{box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);pointer-events:auto;z-index:40;opacity:1;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);transition:opacity .18s;display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.NPLLIq_panelDim{opacity:.55}.NPLLIq_panelDimStrong{opacity:.3}.NPLLIq_header{border-bottom:1px solid var(--dsw-alias-border-l1);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:flex-start;gap:8px;padding:10px 12px;display:flex}.NPLLIq_header:active{cursor:grabbing}.NPLLIq_headerText{flex-direction:column;gap:2px;min-width:0;display:flex}.NPLLIq_title{color:var(--dsw-alias-label-primary);align-items:baseline;gap:6px;font-size:13px;font-weight:600;display:flex}.NPLLIq_version{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:10px;font-weight:500}.NPLLIq_versionNew{color:var(--dsw-alias-status-warning-foreground,#b56900);background:var(--dsw-alias-status-warning-background,#fff6d7);cursor:default;border-radius:4px;padding:1px 6px}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowDrag{cursor:grab;color:var(--dsw-alias-label-secondary);user-select:none;border-radius:4px;flex:none;margin-right:2px;padding:2px 3px;font-size:11px;line-height:1}.NPLLIq_rowDrag:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_rowDrag:active{cursor:grabbing}.NPLLIq_rowDragging{opacity:.45}.NPLLIq_rowDropTarget{box-shadow:inset 0 2px 0 var(--dsw-alias-brand-primary)}.NPLLIq_rowName{min-width:0;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_settingsBtnActive{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2,#0000000f);box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary);font-weight:700}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_upgradeBanner{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-divider-1,#0000001a);border-radius:8px;margin:6px 12px;padding:10px 30px 10px 10px;font-size:12px;position:relative}@keyframes NPLLIq_upgradeBannerFlash{0%,to{border-color:var(--dsw-alias-divider-1,#0000001a);box-shadow:0 0 #f0a02000}50%{border-color:var(--dsw-alias-status-warning-foreground,#b56900);box-shadow:0 0 0 5px #f0a02073}}.NPLLIq_upgradeBannerFlash{border-color:var(--dsw-alias-status-warning-foreground,#b56900);animation:.6s ease-in-out 4 NPLLIq_upgradeBannerFlash}.NPLLIq_upgradeBannerText{margin-bottom:6px;font-weight:600}.NPLLIq_upgradeCommands{flex-direction:column;gap:4px;display:flex}.NPLLIq_upgradeCommandRow{align-items:center;gap:6px;display:flex}.NPLLIq_upgradeCommand{background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:nowrap;border-radius:4px;flex:1;min-width:0;padding:4px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow-x:auto}.NPLLIq_copyBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;flex:none;padding:3px 8px;font-size:11px;transition:transform 80ms,background .12s,color .12s}.NPLLIq_copyBtn:hover{background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_copyBtn:active{transform:scale(.92)}.NPLLIq_copyBtnDone{background:var(--dsw-alias-status-success-background,#14963c1f);border-color:var(--dsw-alias-status-success-foreground,#0a7d32);color:var(--dsw-alias-status-success-foreground,#0a7d32);font-weight:600}.NPLLIq_upgradeHint{color:var(--dsw-alias-label-secondary);margin-top:6px;font-size:11px}.NPLLIq_upgradeClose{width:20px;height:20px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;font-size:16px;line-height:1;position:absolute;top:4px;right:6px}.NPLLIq_upgradeClose:hover{background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary)}.NPLLIq_checkUpdatesRow{justify-content:space-between;align-items:center;gap:12px;display:flex}.NPLLIq_checkUpdatesLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}.NPLLIq_checkUpdatesLabel input{margin:0}.NPLLIq_checkUpdatesLabel input[type=range]{min-width:90px;accent-color:var(--dsw-alias-brand-primary);flex:1}.NPLLIq_checkUpdatesLabel select{background:var(--dsw-alias-bg-layer-2,#0000000a);min-width:90px;color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-divider-1,#00000026);border-radius:4px;flex:1;padding:2px 4px;font-size:12px}.NPLLIq_checkBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-align:center;border-radius:4px;flex:none;min-width:72px;padding:4px 10px;font-size:12px}.NPLLIq_checkBtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_checkBtn:disabled{opacity:.6;cursor:default}.NPLLIq_checkErrorHint{color:var(--dsw-alias-state-error-primary,#c00);background:var(--dsw-alias-state-error-background,#ff00000f);border:1px solid var(--dsw-alias-state-error-border,#b4000040);word-break:break-all;border-radius:6px;margin-top:8px;padding:6px 8px;font-size:11px;line-height:1.4}.NPLLIq_dialog{z-index:100;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;gap:12px;width:430px;max-width:94vw;max-height:80vh;padding:14px;display:flex;position:fixed;top:50%;left:50%;overflow-y:auto;transform:translate(-50%,-50%);box-shadow:0 8px 28px #00000040}.NPLLIq_dialogTabs{border-bottom:1px solid var(--dsw-alias-divider-1,#00000014);gap:2px;margin:-6px -14px 0;padding:0 14px;display:flex}.NPLLIq_dialogTab{appearance:none;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px 8px;font-size:12px}.NPLLIq_dialogTab:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_dialogTabActive{color:var(--dsw-alias-brand-primary);border-bottom-color:var(--dsw-alias-brand-primary);font-weight:600}.NPLLIq_settingsDialog{height:min(560px,80vh)}.NPLLIq_dialogTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.NPLLIq_dialogSection{flex-direction:column;gap:6px;display:flex}.NPLLIq_dialogLabel{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500}.NPLLIq_monitorHint{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_dialogHint{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:11px;line-height:1.4}.NPLLIq_monitorList{flex-direction:column;gap:2px;max-height:180px;display:flex;overflow-y:auto}.NPLLIq_monitorRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:2px 4px;font-size:12px;display:flex}.NPLLIq_monitorRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_monitorName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.NPLLIq_radioRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:3px 4px;font-size:12px;display:flex}.NPLLIq_radioRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_dialogActions{justify-content:flex-end;gap:6px;display:flex}.NPLLIq_dialogBtn,.NPLLIq_dialogBtnPrimary{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;border-radius:6px;padding:4px 12px;font-size:11px}.NPLLIq_dialogBtn{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.NPLLIq_dialogBtnPrimary{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay)}.NPLLIq_dialogHeader{z-index:2;background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:center;gap:8px;margin:-14px -14px 0;padding:14px 14px 8px;display:flex;position:sticky;top:-14px}.NPLLIq_dialogHeader:active{cursor:grabbing}.NPLLIq_dialogClose{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;font-size:18px;line-height:1;display:flex}.NPLLIq_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_resetRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.NPLLIq_resetSelect{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:3px 6px;font-size:12px}.NPLLIq_logTable{border-collapse:collapse;width:100%;font-size:11px}.NPLLIq_logTable th{text-align:left;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);padding:4px 6px;font-weight:500}.NPLLIq_logTable td{color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l1);font-variant-numeric:tabular-nums;padding:4px 6px}.NPLLIq_logDayCol{white-space:nowrap;color:var(--dsw-alias-label-secondary)}.NPLLIq_logModelCol{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.NPLLIq_logUsedCol{text-align:right}.NPLLIq_logDialog{gap:0;width:380px;max-height:min(72vh,480px);padding:0}.NPLLIq_logHeader{background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;flex:none;justify-content:space-between;align-items:center;gap:8px;padding:14px 14px 8px;display:flex}.NPLLIq_logHeader:active{cursor:grabbing}.NPLLIq_logScroll{flex:1;min-height:0;padding:0 14px 14px;overflow:auto}.NPLLIq_logToolbar{flex:1;align-items:center;gap:6px;display:flex}.NPLLIq_logSearch{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);min-width:0;color:var(--dsw-alias-label-primary);border-radius:4px;flex:1;padding:3px 6px;font-size:11px}.NPLLIq_logSearch:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}.NPLLIq_logClearBtn{border:1px solid var(--dsw-alias-state-error-border,#b400004d);color:var(--dsw-alias-state-error-primary,#c00);cursor:pointer;white-space:nowrap;background:0 0;border-radius:4px;flex:none;padding:3px 8px;font-size:11px}.NPLLIq_logClearBtn:hover{background:var(--dsw-alias-state-error-background,#ff00000f)}.NPLLIq_logPager{border-top:1px solid var(--dsw-alias-divider-1,#00000014);background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);flex:none;justify-content:space-between;align-items:center;gap:8px;padding:6px 14px;font-size:11px;display:flex}.NPLLIq_logPagerInfo{white-space:nowrap}.NPLLIq_logPagerControls{align-items:center;gap:4px;display:flex}.NPLLIq_logPagerBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:24px;height:24px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;font-size:13px;line-height:1}.NPLLIq_logPagerBtn:disabled{opacity:.4;cursor:default}.NPLLIq_logPagerPages{text-align:center;min-width:50px;color:var(--dsw-alias-label-secondary)}.NPLLIq_logPageSizeSelect{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;padding:2px 4px;font-size:11px}.NPLLIq_dialogOverlay{z-index:100001;background:#0000004d;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.NPLLIq_logClearDialog{gap:0;width:320px;padding:16px}.NPLLIq_dialogBody{color:var(--dsw-alias-label-secondary);margin:6px 0 10px;font-size:12px;line-height:1.5}.NPLLIq_logClearDate{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:100%;color:var(--dsw-alias-label-primary);border-radius:6px;margin-bottom:12px;padding:6px 8px;font-size:13px}.NPLLIq_logClearDate:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}";
195
223
  const tagId = "@jxgame2020/dsh-token-quota/TokenQuotaPanel.module.css";
196
224
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
197
225
  const tag = document.createElement("style");
@@ -201,96 +229,99 @@ window.__ModuleLoader__.load({
201
229
  document.head.appendChild(tag);
202
230
  }
203
231
  var TokenQuotaPanel_module_css_default = {
204
- "body": "NPLLIq_body",
232
+ "subtitle": "NPLLIq_subtitle",
233
+ "headerActions": "NPLLIq_headerActions",
205
234
  "bar": "NPLLIq_bar",
206
- "rowName": "NPLLIq_rowName",
207
- "rowHeader": "NPLLIq_rowHeader",
208
- "gearBtn": "NPLLIq_gearBtn",
209
- "radioRow": "NPLLIq_radioRow",
235
+ "collapse": "NPLLIq_collapse",
236
+ "tabLabel": "NPLLIq_tabLabel",
237
+ "rowMeta": "NPLLIq_rowMeta",
238
+ "dialog": "NPLLIq_dialog",
239
+ "panelDimStrong": "NPLLIq_panelDimStrong",
240
+ "settingsBtn": "NPLLIq_settingsBtn",
241
+ "settingsDialog": "NPLLIq_settingsDialog",
242
+ "controls": "NPLLIq_controls",
210
243
  "resetRow": "NPLLIq_resetRow",
211
- "logModelCol": "NPLLIq_logModelCol",
212
- "logClearDialog": "NPLLIq_logClearDialog",
213
- "upgradeClose": "NPLLIq_upgradeClose",
214
- "subtitle": "NPLLIq_subtitle",
244
+ "fillOver": "NPLLIq_fillOver",
245
+ "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
246
+ "radioRow": "NPLLIq_radioRow",
247
+ "rowHeader": "NPLLIq_rowHeader",
248
+ "fillIdle": "NPLLIq_fillIdle",
249
+ "logTable": "NPLLIq_logTable",
250
+ "fullNotice": "NPLLIq_fullNotice",
251
+ "rowDragging": "NPLLIq_rowDragging",
252
+ "dialogBody": "NPLLIq_dialogBody",
253
+ "logDialog": "NPLLIq_logDialog",
254
+ "rowDropTarget": "NPLLIq_rowDropTarget",
255
+ "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
256
+ "dialogTabs": "NPLLIq_dialogTabs",
257
+ "resetSelect": "NPLLIq_resetSelect",
258
+ "logUsedCol": "NPLLIq_logUsedCol",
215
259
  "fillWarn": "NPLLIq_fillWarn",
260
+ "selectBtn": "NPLLIq_selectBtn",
261
+ "versionNew": "NPLLIq_versionNew",
262
+ "dialogTab": "NPLLIq_dialogTab",
263
+ "dialogTitle": "NPLLIq_dialogTitle",
264
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
265
+ "logHeader": "NPLLIq_logHeader",
266
+ "rowDrag": "NPLLIq_rowDrag",
267
+ "monitorHint": "NPLLIq_monitorHint",
268
+ "body": "NPLLIq_body",
269
+ "rowName": "NPLLIq_rowName",
216
270
  "noticeError": "NPLLIq_noticeError",
217
271
  "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
218
- "logDialog": "NPLLIq_logDialog",
219
- "dialogLabel": "NPLLIq_dialogLabel",
220
- "settingsBtn": "NPLLIq_settingsBtn",
221
272
  "dialogHeader": "NPLLIq_dialogHeader",
222
- "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
223
- "copyBtnDone": "NPLLIq_copyBtnDone",
224
- "upgradeBanner": "NPLLIq_upgradeBanner",
225
- "logScroll": "NPLLIq_logScroll",
226
- "logDayCol": "NPLLIq_logDayCol",
227
- "logPager": "NPLLIq_logPager",
228
- "upgradeCommands": "NPLLIq_upgradeCommands",
229
- "fillOver": "NPLLIq_fillOver",
273
+ "logClearBtn": "NPLLIq_logClearBtn",
274
+ "logPagerInfo": "NPLLIq_logPagerInfo",
230
275
  "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
231
- "headerText": "NPLLIq_headerText",
232
- "switch": "NPLLIq_switch",
233
- "dialogHint": "NPLLIq_dialogHint",
234
- "dialogOverlay": "NPLLIq_dialogOverlay",
235
- "version": "NPLLIq_version",
276
+ "dialogLabel": "NPLLIq_dialogLabel",
277
+ "header": "NPLLIq_header",
278
+ "logPager": "NPLLIq_logPager",
279
+ "copyBtn": "NPLLIq_copyBtn",
236
280
  "input": "NPLLIq_input",
237
- "logPagerControls": "NPLLIq_logPagerControls",
238
- "settingsDialog": "NPLLIq_settingsDialog",
281
+ "logClearDialog": "NPLLIq_logClearDialog",
282
+ "logClearDate": "NPLLIq_logClearDate",
283
+ "upgradeClose": "NPLLIq_upgradeClose",
284
+ "dialogBtn": "NPLLIq_dialogBtn",
239
285
  "logPageSizeSelect": "NPLLIq_logPageSizeSelect",
240
- "panelDim": "NPLLIq_panelDim",
241
- "settingsBtnActive": "NPLLIq_settingsBtnActive",
242
- "versionNew": "NPLLIq_versionNew",
243
- "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
244
- "dialogTabActive": "NPLLIq_dialogTabActive",
245
- "copyBtn": "NPLLIq_copyBtn",
246
- "headerActions": "NPLLIq_headerActions",
247
- "logSearch": "NPLLIq_logSearch",
248
- "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
249
- "dialogTab": "NPLLIq_dialogTab",
286
+ "logScroll": "NPLLIq_logScroll",
250
287
  "upgradeHint": "NPLLIq_upgradeHint",
251
- "collapse": "NPLLIq_collapse",
252
- "dialogTabs": "NPLLIq_dialogTabs",
288
+ "logModelCol": "NPLLIq_logModelCol",
289
+ "gearBtn": "NPLLIq_gearBtn",
290
+ "monitorName": "NPLLIq_monitorName",
291
+ "dialogOverlay": "NPLLIq_dialogOverlay",
292
+ "currentBadge": "NPLLIq_currentBadge",
293
+ "upgradeBannerText": "NPLLIq_upgradeBannerText",
294
+ "switch": "NPLLIq_switch",
295
+ "logToolbar": "NPLLIq_logToolbar",
296
+ "logSearch": "NPLLIq_logSearch",
297
+ "logDayCol": "NPLLIq_logDayCol",
298
+ "dialogTabActive": "NPLLIq_dialogTabActive",
299
+ "row": "NPLLIq_row",
300
+ "upgradeBanner": "NPLLIq_upgradeBanner",
301
+ "headerText": "NPLLIq_headerText",
253
302
  "title": "NPLLIq_title",
254
- "controls": "NPLLIq_controls",
255
- "logClearBtn": "NPLLIq_logClearBtn",
256
- "header": "NPLLIq_header",
257
- "logPagerInfo": "NPLLIq_logPagerInfo",
258
- "logPagerPages": "NPLLIq_logPagerPages",
303
+ "dialogSection": "NPLLIq_dialogSection",
304
+ "logPagerControls": "NPLLIq_logPagerControls",
305
+ "settingsBtnActive": "NPLLIq_settingsBtnActive",
306
+ "version": "NPLLIq_version",
307
+ "tab": "NPLLIq_tab",
308
+ "dialogHint": "NPLLIq_dialogHint",
259
309
  "dialogClose": "NPLLIq_dialogClose",
260
- "logHeader": "NPLLIq_logHeader",
261
- "notice": "NPLLIq_notice",
262
- "dialogBody": "NPLLIq_dialogBody",
263
- "dialog": "NPLLIq_dialog",
264
- "panelDimStrong": "NPLLIq_panelDimStrong",
265
- "logClearDate": "NPLLIq_logClearDate",
266
- "save": "NPLLIq_save",
267
- "logUsedCol": "NPLLIq_logUsedCol",
268
- "monitorName": "NPLLIq_monitorName",
310
+ "panel": "NPLLIq_panel",
311
+ "tabIcon": "NPLLIq_tabIcon",
312
+ "logPagerPages": "NPLLIq_logPagerPages",
313
+ "panelDim": "NPLLIq_panelDim",
314
+ "copyBtnDone": "NPLLIq_copyBtnDone",
315
+ "monitorList": "NPLLIq_monitorList",
316
+ "logPagerBtn": "NPLLIq_logPagerBtn",
269
317
  "monitorRow": "NPLLIq_monitorRow",
270
- "dialogBtn": "NPLLIq_dialogBtn",
271
318
  "dialogActions": "NPLLIq_dialogActions",
272
- "upgradeBannerText": "NPLLIq_upgradeBannerText",
273
- "selectBtn": "NPLLIq_selectBtn",
274
- "logPagerBtn": "NPLLIq_logPagerBtn",
319
+ "notice": "NPLLIq_notice",
275
320
  "upgradeCommand": "NPLLIq_upgradeCommand",
276
- "row": "NPLLIq_row",
277
- "currentBadge": "NPLLIq_currentBadge",
278
- "monitorList": "NPLLIq_monitorList",
279
- "tabLabel": "NPLLIq_tabLabel",
280
- "fillIdle": "NPLLIq_fillIdle",
281
321
  "checkErrorHint": "NPLLIq_checkErrorHint",
282
- "dialogTitle": "NPLLIq_dialogTitle",
283
- "monitorHint": "NPLLIq_monitorHint",
284
- "tabIcon": "NPLLIq_tabIcon",
285
- "panel": "NPLLIq_panel",
286
- "resetSelect": "NPLLIq_resetSelect",
287
322
  "checkBtn": "NPLLIq_checkBtn",
288
- "logTable": "NPLLIq_logTable",
289
- "tab": "NPLLIq_tab",
290
- "rowMeta": "NPLLIq_rowMeta",
291
- "logToolbar": "NPLLIq_logToolbar",
292
- "fullNotice": "NPLLIq_fullNotice",
293
- "dialogSection": "NPLLIq_dialogSection"
323
+ "upgradeCommands": "NPLLIq_upgradeCommands",
324
+ "save": "NPLLIq_save"
294
325
  };
295
326
  //#endregion
296
327
  //#region lib/types/client/TokenQuotaPanel.js
@@ -377,13 +408,15 @@ window.__ModuleLoader__.load({
377
408
  * @param props - composed slot props.
378
409
  * @returns the panel element tree.
379
410
  */
380
- function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions }) {
411
+ function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setModelOrder, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions }) {
381
412
  const [collapsed, setCollapsed] = (0, react.useState)(false);
382
413
  const [drafts, setDrafts] = (0, react.useState)({});
383
414
  const [editingKey, setEditingKey] = (0, react.useState)(null);
384
415
  const [hovered, setHovered] = (0, react.useState)(false);
385
416
  const [inputActive, setInputActive] = (0, react.useState)(false);
386
417
  const [settingsTab, setSettingsTab] = (0, react.useState)("quota");
418
+ const [dragKey, setDragKey] = (0, react.useState)(null);
419
+ const [dropKey, setDropKey] = (0, react.useState)(null);
387
420
  const [copiedCmd, setCopiedCmd] = (0, react.useState)(null);
388
421
  const copyTimerRef = (0, react.useRef)(null);
389
422
  const [upgradeFlash, setUpgradeFlash] = (0, react.useState)(false);
@@ -409,6 +442,7 @@ window.__ModuleLoader__.load({
409
442
  const onFull = useStore((s) => s.onFull);
410
443
  const checkUpdates = useStore((s) => s.checkUpdates);
411
444
  const dimWhenIdle = useStore((s) => s.dimWhenIdle);
445
+ const order = useStore((s) => s.order);
412
446
  const musicEnabled = useStore((s) => s.musicEnabled);
413
447
  const musicVolume = useStore((s) => s.musicVolume);
414
448
  const musicStyle = useStore((s) => s.musicStyle);
@@ -464,15 +498,27 @@ window.__ModuleLoader__.load({
464
498
  if (copyTimerRef.current !== null) clearTimeout(copyTimerRef.current);
465
499
  };
466
500
  }, []);
467
- const rows = (0, react.useMemo)(() => mergeModelRows(groups, snapshot, current), [
501
+ const rows = (0, react.useMemo)(() => mergeModelRows(groups, snapshot, current, order), [
468
502
  groups,
469
503
  snapshot,
470
- current
504
+ current,
505
+ order
471
506
  ]);
472
- const allModels = (0, react.useMemo)(() => groups.flatMap((group) => group.models.map((model) => ({
473
- key: `${group.id}/${model.id}`,
474
- name: model.name
475
- }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
507
+ const allModels = (0, react.useMemo)(() => {
508
+ const list = groups.flatMap((group) => group.models.map((model) => ({
509
+ key: `${group.id}/${model.id}`,
510
+ name: model.name
511
+ })));
512
+ const rank = new Map(order.map((key, index) => [key, index]));
513
+ return list.sort((left, right) => {
514
+ const leftRank = rank.get(left.key);
515
+ const rightRank = rank.get(right.key);
516
+ if (leftRank !== void 0 && rightRank !== void 0) return leftRank - rightRank;
517
+ if (leftRank !== void 0) return -1;
518
+ if (rightRank !== void 0) return 1;
519
+ return left.key.localeCompare(right.key);
520
+ });
521
+ }, [groups, order]);
476
522
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
477
523
  const filteredLogEntries = (0, react.useMemo)(() => {
478
524
  if (log === null) return [];
@@ -597,13 +643,13 @@ window.__ModuleLoader__.load({
597
643
  title: t("upgradeAvailableHint"),
598
644
  children: [
599
645
  "v",
600
- "0.1.31",
646
+ "0.1.32",
601
647
  " → v",
602
648
  upgrade.latestVersion
603
649
  ]
604
650
  }) : (0, react_jsx_runtime.jsxs)("span", {
605
651
  className: TokenQuotaPanel_module_css_default.version,
606
- children: ["v", "0.1.31"]
652
+ children: ["v", "0.1.32"]
607
653
  })]
608
654
  }), (0, react_jsx_runtime.jsx)("div", {
609
655
  className: TokenQuotaPanel_module_css_default.subtitle,
@@ -716,40 +762,69 @@ window.__ModuleLoader__.load({
716
762
  const barClass = state === "over" ? TokenQuotaPanel_module_css_default.fillOver : state === "warn" ? TokenQuotaPanel_module_css_default.fillWarn : TokenQuotaPanel_module_css_default.fillIdle;
717
763
  const editing = editingKey === row.key;
718
764
  return (0, react_jsx_runtime.jsxs)("div", {
719
- className: TokenQuotaPanel_module_css_default.row,
765
+ className: `${TokenQuotaPanel_module_css_default.row}${dragKey === row.key ? ` ${TokenQuotaPanel_module_css_default.rowDragging}` : ""}${dropKey === row.key && dragKey !== row.key ? ` ${TokenQuotaPanel_module_css_default.rowDropTarget}` : ""}`,
766
+ onDragOver: (event) => {
767
+ if (dragKey === null || dragKey === row.key) return;
768
+ event.preventDefault();
769
+ setDropKey(row.key);
770
+ },
771
+ onDrop: (event) => {
772
+ event.preventDefault();
773
+ if (dragKey !== null && dragKey !== row.key) setModelOrder(reorderKeys(rows.map((r) => r.key), dragKey, row.key));
774
+ setDragKey(null);
775
+ setDropKey(null);
776
+ },
777
+ onDragEnd: () => {
778
+ setDragKey(null);
779
+ setDropKey(null);
780
+ },
720
781
  children: [
721
782
  (0, react_jsx_runtime.jsxs)("div", {
722
783
  className: TokenQuotaPanel_module_css_default.rowHeader,
723
- children: [(0, react_jsx_runtime.jsxs)("span", {
724
- className: TokenQuotaPanel_module_css_default.rowName,
725
- title: row.key,
726
- children: [
727
- row.name,
728
- row.current && (0, react_jsx_runtime.jsx)("span", {
729
- className: TokenQuotaPanel_module_css_default.currentBadge,
730
- children: t("current")
731
- }),
732
- sessionId !== void 0 && !row.current && (0, react_jsx_runtime.jsx)("button", {
784
+ children: [
785
+ (0, react_jsx_runtime.jsx)("span", {
786
+ className: TokenQuotaPanel_module_css_default.rowDrag,
787
+ draggable: true,
788
+ title: t("reorderHint"),
789
+ onDragStart: (event) => {
790
+ setDragKey(row.key);
791
+ event.dataTransfer.effectAllowed = "move";
792
+ event.dataTransfer.setData("text/plain", row.key);
793
+ },
794
+ children: "⠿"
795
+ }),
796
+ (0, react_jsx_runtime.jsxs)("span", {
797
+ className: TokenQuotaPanel_module_css_default.rowName,
798
+ title: row.key,
799
+ children: [
800
+ row.name,
801
+ row.current && (0, react_jsx_runtime.jsx)("span", {
802
+ className: TokenQuotaPanel_module_css_default.currentBadge,
803
+ children: t("current")
804
+ }),
805
+ sessionId !== void 0 && !row.current && (0, react_jsx_runtime.jsx)("button", {
806
+ type: "button",
807
+ className: TokenQuotaPanel_module_css_default.selectBtn,
808
+ onClick: () => {
809
+ selectModel(sessionId, row.provider, row.model);
810
+ },
811
+ children: t("select")
812
+ })
813
+ ]
814
+ }),
815
+ (0, react_jsx_runtime.jsxs)("span", {
816
+ className: TokenQuotaPanel_module_css_default.rowMeta,
817
+ children: [row.limit > 0 ? `${formatTokens(row.used)} / ${formatTokens(row.limit)}` : `${formatTokens(row.used)} · ${t("unlimited")}`, (0, react_jsx_runtime.jsx)("button", {
733
818
  type: "button",
734
- className: TokenQuotaPanel_module_css_default.selectBtn,
819
+ className: TokenQuotaPanel_module_css_default.gearBtn,
820
+ title: t("setLimitHint"),
735
821
  onClick: () => {
736
- selectModel(sessionId, row.provider, row.model);
822
+ setEditingKey(editing ? null : row.key);
737
823
  },
738
- children: t("select")
739
- })
740
- ]
741
- }), (0, react_jsx_runtime.jsxs)("span", {
742
- className: TokenQuotaPanel_module_css_default.rowMeta,
743
- children: [row.limit > 0 ? `${formatTokens(row.used)} / ${formatTokens(row.limit)}` : `${formatTokens(row.used)} · ${t("unlimited")}`, (0, react_jsx_runtime.jsx)("button", {
744
- type: "button",
745
- className: TokenQuotaPanel_module_css_default.gearBtn,
746
- title: t("setLimitHint"),
747
- onClick: () => {
748
- setEditingKey(editing ? null : row.key);
749
- },
750
- children: "⚙"
751
- })]
752
- })]
824
+ children: "⚙"
825
+ })]
826
+ })
827
+ ]
753
828
  }),
754
829
  (0, react_jsx_runtime.jsx)("div", {
755
830
  className: TokenQuotaPanel_module_css_default.bar,
@@ -1918,6 +1993,7 @@ window.__ModuleLoader__.load({
1918
1993
  settingsTabQuota: "限额设置",
1919
1994
  settingsTabMusic: "音乐设置",
1920
1995
  settingsTabOther: "其它",
1996
+ reorderHint: "拖动排序",
1921
1997
  monitorLabel: "监控模型",
1922
1998
  monitorHint: "未勾选的模型不显示、不计入用量、不受限额限制",
1923
1999
  monitorAll: "全部",
@@ -1999,6 +2075,7 @@ window.__ModuleLoader__.load({
1999
2075
  settingsTabQuota: "Quota",
2000
2076
  settingsTabMusic: "Music",
2001
2077
  settingsTabOther: "Other",
2078
+ reorderHint: "Drag to reorder",
2002
2079
  monitorLabel: "Monitored models",
2003
2080
  monitorHint: "Unchecked models are hidden, not metered, and never capped",
2004
2081
  monitorAll: "All",
@@ -2250,6 +2327,7 @@ window.__ModuleLoader__.load({
2250
2327
  bound?.setSettings(lastMonitored, lastOnFull);
2251
2328
  bound?.setCheckUpdates(doc?.checkUpdates ?? true);
2252
2329
  bound?.setDimWhenIdle(doc?.dimWhenIdle ?? false);
2330
+ bound?.setOrder(Array.isArray(doc?.order) ? doc.order : []);
2253
2331
  bound?.setReset(lastReset);
2254
2332
  const musicSettings = musicDefaults(doc?.music);
2255
2333
  lastOnlyCurrentSession = musicSettings.onlyCurrentSession;
@@ -2330,6 +2408,11 @@ window.__ModuleLoader__.load({
2330
2408
  bound?.setDimWhenIdle(enabled);
2331
2409
  scope.set("dimWhenIdle", enabled);
2332
2410
  };
2411
+ /** Persist the drag-to-reorder display order of model rows. */
2412
+ const setModelOrder = (order) => {
2413
+ bound?.setOrder(order);
2414
+ scope.set("order", order);
2415
+ };
2333
2416
  const setMusicEnabled = (enabled) => {
2334
2417
  const doc = scope.getSnapshot().value;
2335
2418
  const current = musicDefaults(doc?.music);
@@ -2442,7 +2525,8 @@ window.__ModuleLoader__.load({
2442
2525
  setMusicEnabled,
2443
2526
  setMusicVolume,
2444
2527
  setMusicStyle,
2445
- setMusicOnlyCurrentSession
2528
+ setMusicOnlyCurrentSession,
2529
+ setModelOrder
2446
2530
  };
2447
2531
  };
2448
2532
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
package/lib/index.js CHANGED
@@ -214,6 +214,7 @@ const TOKEN_QUOTA_SETTINGS_SCHEMA = z.object({
214
214
  ]).default("stop"),
215
215
  checkUpdates: z.boolean().default(true),
216
216
  dimWhenIdle: z.boolean().default(false),
217
+ order: z.array(z.string()).default([]),
217
218
  reset: z.any().default(null),
218
219
  music: z.object({
219
220
  enabled: z.boolean().default(TOKEN_QUOTA_DEFAULT_MUSIC.enabled),
@@ -281,7 +282,8 @@ var TokenQuotaService = class TokenQuotaService extends Service {
281
282
  onFull: "stop",
282
283
  checkUpdates: true,
283
284
  dimWhenIdle: false,
284
- music: { ...TOKEN_QUOTA_DEFAULT_MUSIC }
285
+ music: { ...TOKEN_QUOTA_DEFAULT_MUSIC },
286
+ order: []
285
287
  });
286
288
  /** Whether update checks are enabled (mirrors the settings document). */
287
289
  checkUpdates = true;
@@ -334,6 +336,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
334
336
  onFull: doc.onFull === "switchPriority" ? "switchAll" : doc.onFull ?? "stop",
335
337
  checkUpdates: doc.checkUpdates ?? true,
336
338
  dimWhenIdle: doc.dimWhenIdle ?? false,
339
+ order: Array.isArray(doc.order) ? doc.order : [],
337
340
  reset: doc.reset ?? void 0,
338
341
  music: {
339
342
  enabled: doc.music?.enabled ?? TOKEN_QUOTA_DEFAULT_MUSIC.enabled,
@@ -515,7 +518,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
515
518
  return this.upgradeCheck;
516
519
  }
517
520
  async doRefreshUpgrade() {
518
- const current = "0.1.31";
521
+ const current = "0.1.32";
519
522
  let latest;
520
523
  try {
521
524
  const response = await fetch(REGISTRY_LATEST_URL, {
@@ -20,6 +20,8 @@ export interface TokenQuotaPanelInjected {
20
20
  setCheckUpdates: (enabled: boolean) => void;
21
21
  /** Persist whether the panel dims while the pointer is away / typing. */
22
22
  setDimWhenIdle: (enabled: boolean) => void;
23
+ /** Persist the drag-to-reorder display order of model rows. */
24
+ setModelOrder: (order: string[]) => void;
23
25
  /** Toggle the live music output (requires a user gesture to start audio). */
24
26
  setMusicEnabled: (enabled: boolean) => void;
25
27
  /** Change the live-music master volume 0..1. */
@@ -40,5 +42,5 @@ export type TokenQuotaPanelComponentProps = PropsRuntime<'shell.overlay'> & Prop
40
42
  * @param props - composed slot props.
41
43
  * @returns the panel element tree.
42
44
  */
43
- export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
45
+ export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setModelOrder, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
44
46
  //# sourceMappingURL=TokenQuotaPanel.d.ts.map
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
10
10
  * callbacks.
11
11
  */
12
12
  import { useEffect, useMemo, useRef, useState } from 'react';
13
- import { mergeModelRows } from "./store.js";
13
+ import { mergeModelRows, reorderKeys } from "./store.js";
14
14
  import css from './TokenQuotaPanel.module.css';
15
15
  /** Compact a token count for display. */
16
16
  function formatTokens(n) {
@@ -86,7 +86,7 @@ function loadPanelPos() {
86
86
  * @param props - composed slot props.
87
87
  * @returns the panel element tree.
88
88
  */
89
- export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }) {
89
+ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setModelOrder, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }) {
90
90
  const [collapsed, setCollapsed] = useState(false);
91
91
  const [drafts, setDrafts] = useState({});
92
92
  const [editingKey, setEditingKey] = useState(null);
@@ -97,6 +97,10 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
97
97
  const [inputActive, setInputActive] = useState(false);
98
98
  // Active settings-dialog tab: quota limits vs. live-music vs. misc.
99
99
  const [settingsTab, setSettingsTab] = useState('quota');
100
+ // Drag-to-reorder state for the model rows: the key being dragged and
101
+ // the key currently hovered as the drop target.
102
+ const [dragKey, setDragKey] = useState(null);
103
+ const [dropKey, setDropKey] = useState(null);
100
104
  // Copy-button feedback: which command was just copied (label flips to
101
105
  // 「已复制」for a moment so the click is perceivable).
102
106
  const [copiedCmd, setCopiedCmd] = useState(null);
@@ -131,6 +135,7 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
131
135
  const onFull = useStore(s => s.onFull);
132
136
  const checkUpdates = useStore(s => s.checkUpdates);
133
137
  const dimWhenIdle = useStore(s => s.dimWhenIdle);
138
+ const order = useStore(s => s.order);
134
139
  const musicEnabled = useStore(s => s.musicEnabled);
135
140
  const musicVolume = useStore(s => s.musicVolume);
136
141
  const musicStyle = useStore(s => s.musicStyle);
@@ -198,12 +203,27 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
198
203
  clearTimeout(copyTimerRef.current);
199
204
  };
200
205
  }, []);
201
- const rows = useMemo(() => mergeModelRows(groups, snapshot, current), [groups, snapshot, current]);
202
- // All directory models, flattened for the monitoring picker.
203
- const allModels = useMemo(() => groups.flatMap(group => group.models.map((model) => ({
204
- key: `${group.id}/${model.id}`,
205
- name: model.name,
206
- }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
206
+ const rows = useMemo(() => mergeModelRows(groups, snapshot, current, order), [groups, snapshot, current, order]);
207
+ // All directory models, flattened for the monitoring picker — same drag
208
+ // order as the panel rows so both lists read identically.
209
+ const allModels = useMemo(() => {
210
+ const list = groups.flatMap(group => group.models.map((model) => ({
211
+ key: `${group.id}/${model.id}`,
212
+ name: model.name,
213
+ })));
214
+ const rank = new Map(order.map((key, index) => [key, index]));
215
+ return list.sort((left, right) => {
216
+ const leftRank = rank.get(left.key);
217
+ const rightRank = rank.get(right.key);
218
+ if (leftRank !== undefined && rightRank !== undefined)
219
+ return leftRank - rightRank;
220
+ if (leftRank !== undefined)
221
+ return -1;
222
+ if (rightRank !== undefined)
223
+ return 1;
224
+ return left.key.localeCompare(right.key);
225
+ });
226
+ }, [groups, order]);
207
227
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
208
228
  // Log dialog: filter by search, then paginate.
209
229
  const filteredLogEntries = useMemo(() => {
@@ -309,7 +329,24 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
309
329
  ? css.fillWarn
310
330
  : css.fillIdle;
311
331
  const editing = editingKey === row.key;
312
- return (_jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowHeader, children: [_jsxs("span", { className: css.rowName, title: row.key, children: [row.name, row.current && _jsx("span", { className: css.currentBadge, children: t('current') }), sessionId !== undefined && !row.current && (_jsx("button", { type: "button", className: css.selectBtn, onClick: () => { selectModel(sessionId, row.provider, row.model); }, children: t('select') }))] }), _jsxs("span", { className: css.rowMeta, children: [row.limit > 0
332
+ return (_jsxs("div", { className: `${css.row}${dragKey === row.key ? ` ${css.rowDragging}` : ''}${dropKey === row.key && dragKey !== row.key ? ` ${css.rowDropTarget}` : ''}`, onDragOver: (event) => {
333
+ if (dragKey === null || dragKey === row.key)
334
+ return;
335
+ event.preventDefault();
336
+ setDropKey(row.key);
337
+ }, onDrop: (event) => {
338
+ event.preventDefault();
339
+ if (dragKey !== null && dragKey !== row.key) {
340
+ setModelOrder(reorderKeys(rows.map(r => r.key), dragKey, row.key));
341
+ }
342
+ setDragKey(null);
343
+ setDropKey(null);
344
+ }, onDragEnd: () => { setDragKey(null); setDropKey(null); }, children: [_jsxs("div", { className: css.rowHeader, children: [_jsx("span", { className: css.rowDrag, draggable: true, title: t('reorderHint'), onDragStart: (event) => {
345
+ setDragKey(row.key);
346
+ event.dataTransfer.effectAllowed = 'move';
347
+ // Firefox only starts a drag when some data is set.
348
+ event.dataTransfer.setData('text/plain', row.key);
349
+ }, children: "\u283F" }), _jsxs("span", { className: css.rowName, title: row.key, children: [row.name, row.current && _jsx("span", { className: css.currentBadge, children: t('current') }), sessionId !== undefined && !row.current && (_jsx("button", { type: "button", className: css.selectBtn, onClick: () => { selectModel(sessionId, row.provider, row.model); }, children: t('select') }))] }), _jsxs("span", { className: css.rowMeta, children: [row.limit > 0
313
350
  ? `${formatTokens(row.used)} / ${formatTokens(row.limit)}`
314
351
  : `${formatTokens(row.used)} · ${t('unlimited')}`, _jsx("button", { type: "button", className: css.gearBtn, title: t('setLimitHint'), onClick: () => { setEditingKey(editing ? null : row.key); }, children: "\u2699" })] })] }), _jsx("div", { className: css.bar, children: _jsx("div", { className: barClass, style: { width: `${pct}%` } }) }), editing && (_jsxs("div", { className: css.controls, children: [_jsx("input", { className: css.input, type: "number", min: 0, step: 10000, placeholder: t('limitPlaceholder'), value: drafts[row.key] ?? '', onChange: (event) => { setDrafts(prev => ({ ...prev, [row.key]: event.target.value })); }, onKeyDown: (event) => { if (event.key === 'Enter')
315
352
  commitLimit(row); } }), _jsx("button", { type: "button", className: css.save, onClick: () => { commitLimit(row); }, children: t('save') })] }))] }, row.key));
@@ -250,6 +250,7 @@ export function apply(ctx) {
250
250
  bound?.setSettings(lastMonitored, lastOnFull);
251
251
  bound?.setCheckUpdates(doc?.checkUpdates ?? true);
252
252
  bound?.setDimWhenIdle(doc?.dimWhenIdle ?? false);
253
+ bound?.setOrder(Array.isArray(doc?.order) ? doc.order : []);
253
254
  bound?.setReset(lastReset);
254
255
  const musicSettings = musicDefaults(doc?.music);
255
256
  lastOnlyCurrentSession = musicSettings.onlyCurrentSession;
@@ -332,6 +333,11 @@ export function apply(ctx) {
332
333
  bound?.setDimWhenIdle(enabled);
333
334
  void scope.set('dimWhenIdle', enabled);
334
335
  };
336
+ /** Persist the drag-to-reorder display order of model rows. */
337
+ const setModelOrder = (order) => {
338
+ bound?.setOrder(order);
339
+ void scope.set('order', order);
340
+ };
335
341
  const setMusicEnabled = (enabled) => {
336
342
  const doc = scope.getSnapshot().value;
337
343
  const current = musicDefaults(doc?.music);
@@ -449,6 +455,7 @@ export function apply(ctx) {
449
455
  load, setLimit, selectModel, setMonitored, setOnFull, setReset,
450
456
  setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle,
451
457
  setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession,
458
+ setModelOrder,
452
459
  };
453
460
  };
454
461
  ctx.slots.inject('shell.overlay', () => ctx.slots.register({
@@ -28,6 +28,7 @@ export declare const zh: {
28
28
  settingsTabQuota: string;
29
29
  settingsTabMusic: string;
30
30
  settingsTabOther: string;
31
+ reorderHint: string;
31
32
  monitorLabel: string;
32
33
  monitorHint: string;
33
34
  monitorAll: string;
@@ -111,6 +112,7 @@ export declare const en: {
111
112
  settingsTabQuota: string;
112
113
  settingsTabMusic: string;
113
114
  settingsTabOther: string;
115
+ reorderHint: string;
114
116
  monitorLabel: string;
115
117
  monitorHint: string;
116
118
  monitorAll: string;
@@ -28,6 +28,7 @@ export const zh = {
28
28
  settingsTabQuota: '限额设置',
29
29
  settingsTabMusic: '音乐设置',
30
30
  settingsTabOther: '其它',
31
+ reorderHint: '拖动排序',
31
32
  monitorLabel: '监控模型',
32
33
  monitorHint: '未勾选的模型不显示、不计入用量、不受限额限制',
33
34
  monitorAll: '全部',
@@ -109,6 +110,7 @@ export const en = {
109
110
  settingsTabQuota: 'Quota',
110
111
  settingsTabMusic: 'Music',
111
112
  settingsTabOther: 'Other',
113
+ reorderHint: 'Drag to reorder',
112
114
  monitorLabel: 'Monitored models',
113
115
  monitorHint: 'Unchecked models are hidden, not metered, and never capped',
114
116
  monitorAll: 'All',
@@ -50,6 +50,11 @@ export interface TokenQuotaPanelState {
50
50
  checkUpdates: boolean;
51
51
  /** Whether the panel dims while the pointer is away / while typing. */
52
52
  dimWhenIdle: boolean;
53
+ /**
54
+ * Preferred display order of model keys, written by dragging panel rows.
55
+ * Keys not listed keep their catalog order after the listed ones.
56
+ */
57
+ order: string[];
53
58
  /** Live music settings (mirrored from the settings document). */
54
59
  musicEnabled: boolean;
55
60
  musicVolume: number;
@@ -97,6 +102,8 @@ export type TokenQuotaPanelActions = {
97
102
  setSettings: (d: TokenQuotaPanelState, monitored: string[] | null, onFull: TokenQuotaFullAction) => void;
98
103
  setCheckUpdates: (d: TokenQuotaPanelState, checkUpdates: boolean) => void;
99
104
  setDimWhenIdle: (d: TokenQuotaPanelState, dimWhenIdle: boolean) => void;
105
+ /** Replace the drag-to-reorder model display order. */
106
+ setOrder: (d: TokenQuotaPanelState, order: string[]) => void;
100
107
  setMusicEnabled: (d: TokenQuotaPanelState, enabled: boolean) => void;
101
108
  setMusicVolume: (d: TokenQuotaPanelState, volume: number) => void;
102
109
  setMusicStyle: (d: TokenQuotaPanelState, style: TokenQuotaMusicStyle) => void;
@@ -131,7 +138,14 @@ export declare function createTokenQuotaPanelStore(): EngineStoreHandle<TokenQuo
131
138
  * @param groups - advisory provider groups of the current session.
132
139
  * @param snapshot - latest quota snapshot, or null before the first one.
133
140
  * @param current - current model selection reported by the Host.
134
- * @returns rows sorted by key.
141
+ * @param order - user's drag-to-reorder preference (model keys); listed keys
142
+ * come first in that exact order, the rest follow in catalog order.
143
+ * @returns rows in display order.
144
+ */
145
+ export declare function mergeModelRows(groups: readonly ModelProviderGroup[], snapshot: TokenQuotaSnapshot | null, current: ModelSelection | null, order?: readonly string[]): ModelQuotaRow[];
146
+ /**
147
+ * Move one key to another key's position, returning the full reordered key
148
+ * list (the shape persisted in the settings document's `order`).
135
149
  */
136
- export declare function mergeModelRows(groups: readonly ModelProviderGroup[], snapshot: TokenQuotaSnapshot | null, current: ModelSelection | null): ModelQuotaRow[];
150
+ export declare function reorderKeys(keys: readonly string[], from: string, to: string): string[];
137
151
  //# sourceMappingURL=store.d.ts.map
@@ -28,6 +28,7 @@ export function createTokenQuotaPanelStore() {
28
28
  onFull: 'stop',
29
29
  checkUpdates: true,
30
30
  dimWhenIdle: false,
31
+ order: [],
31
32
  musicEnabled: false,
32
33
  musicVolume: 0.5,
33
34
  musicStyle: 'pentatonic',
@@ -57,6 +58,7 @@ export function createTokenQuotaPanelStore() {
57
58
  setSettings: (d, monitored, onFull) => { d.monitored = monitored; d.onFull = onFull; },
58
59
  setCheckUpdates: (d, checkUpdates) => { d.checkUpdates = checkUpdates; },
59
60
  setDimWhenIdle: (d, dimWhenIdle) => { d.dimWhenIdle = dimWhenIdle; },
61
+ setOrder: (d, order) => { d.order = order; },
60
62
  setMusicEnabled: (d, enabled) => { d.musicEnabled = enabled; },
61
63
  setMusicVolume: (d, volume) => { d.musicVolume = volume; },
62
64
  setMusicStyle: (d, style) => { d.musicStyle = style; },
@@ -104,9 +106,11 @@ export function createTokenQuotaPanelStore() {
104
106
  * @param groups - advisory provider groups of the current session.
105
107
  * @param snapshot - latest quota snapshot, or null before the first one.
106
108
  * @param current - current model selection reported by the Host.
107
- * @returns rows sorted by key.
109
+ * @param order - user's drag-to-reorder preference (model keys); listed keys
110
+ * come first in that exact order, the rest follow in catalog order.
111
+ * @returns rows in display order.
108
112
  */
109
- export function mergeModelRows(groups, snapshot, current) {
113
+ export function mergeModelRows(groups, snapshot, current, order = []) {
110
114
  const entryByKey = new Map((snapshot?.entries ?? []).map(entry => [entry.key, entry]));
111
115
  const rows = new Map();
112
116
  for (const group of groups) {
@@ -137,6 +141,35 @@ export function mergeModelRows(groups, snapshot, current) {
137
141
  current: current !== null && current.provider === entry.provider && current.model === entry.model,
138
142
  });
139
143
  }
140
- return [...rows.values()].sort((left, right) => left.key.localeCompare(right.key));
144
+ // Drag preference first (in its own order), everything else after it,
145
+ // alphabetically — so a newly discovered model still lands somewhere stable.
146
+ const rank = new Map(order.map((key, index) => [key, index]));
147
+ return [...rows.values()].sort((left, right) => {
148
+ const leftRank = rank.get(left.key);
149
+ const rightRank = rank.get(right.key);
150
+ if (leftRank !== undefined && rightRank !== undefined)
151
+ return leftRank - rightRank;
152
+ if (leftRank !== undefined)
153
+ return -1;
154
+ if (rightRank !== undefined)
155
+ return 1;
156
+ return left.key.localeCompare(right.key);
157
+ });
158
+ }
159
+ /**
160
+ * Move one key to another key's position, returning the full reordered key
161
+ * list (the shape persisted in the settings document's `order`).
162
+ */
163
+ export function reorderKeys(keys, from, to) {
164
+ const next = [...keys];
165
+ const fromIndex = next.indexOf(from);
166
+ const toIndex = next.indexOf(to);
167
+ if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex)
168
+ return next;
169
+ const [moved] = next.splice(fromIndex, 1);
170
+ if (moved === undefined)
171
+ return next;
172
+ next.splice(toIndex, 0, moved);
173
+ return next;
141
174
  }
142
175
  //# sourceMappingURL=store.js.map
@@ -202,6 +202,8 @@ const TOKEN_QUOTA_SETTINGS_SCHEMA = z.object({
202
202
  checkUpdates: z.boolean().default(true),
203
203
  // Client-side appearance preference; the host only stores it.
204
204
  dimWhenIdle: z.boolean().default(false),
205
+ // Drag-to-reorder: display order of model keys, written by the panel.
206
+ order: z.array(z.string()).default([]),
205
207
  // `reset` is a user-facing preference outside the validated surface: the
206
208
  // panel writes it and the host validates the shape at runtime. `z.any` with
207
209
  // a null default keeps it out of the strict fields above.
@@ -273,7 +275,7 @@ export class TokenQuotaService extends Service {
273
275
  history = {};
274
276
  limits = {};
275
277
  monitored = undefined;
276
- settingsSource = () => ({ limits: {}, monitored: [], onFull: 'stop', checkUpdates: true, dimWhenIdle: false, music: { ...TOKEN_QUOTA_DEFAULT_MUSIC } });
278
+ settingsSource = () => ({ limits: {}, monitored: [], onFull: 'stop', checkUpdates: true, dimWhenIdle: false, music: { ...TOKEN_QUOTA_DEFAULT_MUSIC }, order: [] });
277
279
  /** Whether update checks are enabled (mirrors the settings document). */
278
280
  checkUpdates = true;
279
281
  /** Cached upgrade availability; recomputed by {@link refreshUpgrade}. */
@@ -337,6 +339,7 @@ export class TokenQuotaService extends Service {
337
339
  onFull: doc.onFull === 'switchPriority' ? 'switchAll' : (doc.onFull ?? 'stop'),
338
340
  checkUpdates: doc.checkUpdates ?? true,
339
341
  dimWhenIdle: doc.dimWhenIdle ?? false,
342
+ order: Array.isArray(doc.order) ? doc.order : [],
340
343
  reset: doc.reset ?? undefined,
341
344
  music: {
342
345
  enabled: doc.music?.enabled ?? TOKEN_QUOTA_DEFAULT_MUSIC.enabled,
@@ -143,6 +143,12 @@ export interface TokenQuotaSettings {
143
143
  * an attached MIDI output. Off by default.
144
144
  */
145
145
  music: TokenQuotaMusicSettings;
146
+ /**
147
+ * Preferred display order of model keys (`provider/model`), set by dragging
148
+ * rows in the panel. Keys not listed here keep their catalog order after the
149
+ * listed ones. Empty by default (pure catalog/alphabetical order).
150
+ */
151
+ order: string[];
146
152
  }
147
153
  /** Token-quota plugin configuration. */
148
154
  export interface TokenQuotaConfig {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jxgame2020/dsh-token-quota",
3
3
  "description": "Daily per-model token quota accounting, enforcement, and a floating quota panel for the DeepSeek Harness — a fully self-contained plugin (no core-Harness wiring, no generated RPC contract)",
4
- "version": "0.1.31",
4
+ "version": "0.1.32",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },