@jxgame2020/dsh-token-quota 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -119,7 +119,7 @@ window.__ModuleLoader__.load({
119
119
  }
120
120
  //#endregion
121
121
  //#region \0dsh-css:/Users/jxgame/Desktop/works/deepseek-harness-package/dsh-token-quota/packages/token-quota/src/client/TokenQuotaPanel.module.css.mjs
122
- 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;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.NPLLIq_header{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:flex-start;gap:8px;padding:10px 12px;display:flex}.NPLLIq_headerText{flex-direction:column;gap:2px;min-width:0;display:flex}.NPLLIq_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.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-bg-overlay);border:1px solid var(--dsw-alias-border-l1);pointer-events:auto;cursor:pointer;z-index:40;border-right:none;border-radius:8px 0 0 8px;padding:10px 6px;position:fixed;top:50%;right:0;transform:translateY(-50%);box-shadow:0 4px 16px #0000001f}.NPLLIq_tab:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_tabLabel{writing-mode:vertical-rl;color:var(--dsw-alias-label-secondary);font-size:11px}.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_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_dialogBackdrop{z-index:50;background:#00000059;border-radius:12px;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.NPLLIq_dialog{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:280px;max-height:80%;padding:14px;display:flex;overflow-y:auto;box-shadow:0 8px 28px #00000040}.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_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{justify-content:space-between;align-items:center;gap:8px;display:flex}.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}";
122
+ 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;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.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);font-size:13px;font-weight:600}.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_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_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:280px;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_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_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}";
123
123
  const tagId = "@jxgame2020/dsh-token-quota/TokenQuotaPanel.module.css";
124
124
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
125
125
  const tag = document.createElement("style");
@@ -129,56 +129,56 @@ window.__ModuleLoader__.load({
129
129
  document.head.appendChild(tag);
130
130
  }
131
131
  var TokenQuotaPanel_module_css_default = {
132
- "save": "NPLLIq_save",
133
- "row": "NPLLIq_row",
134
- "dialog": "NPLLIq_dialog",
135
- "dialogTitle": "NPLLIq_dialogTitle",
132
+ "tabLabel": "NPLLIq_tabLabel",
133
+ "gearBtn": "NPLLIq_gearBtn",
136
134
  "controls": "NPLLIq_controls",
137
- "monitorRow": "NPLLIq_monitorRow",
138
- "bar": "NPLLIq_bar",
139
- "fillIdle": "NPLLIq_fillIdle",
135
+ "input": "NPLLIq_input",
140
136
  "switch": "NPLLIq_switch",
141
- "dialogBackdrop": "NPLLIq_dialogBackdrop",
142
- "currentBadge": "NPLLIq_currentBadge",
143
- "header": "NPLLIq_header",
144
- "dialogClose": "NPLLIq_dialogClose",
145
- "logModelCol": "NPLLIq_logModelCol",
146
- "panel": "NPLLIq_panel",
147
- "fillWarn": "NPLLIq_fillWarn",
148
- "headerActions": "NPLLIq_headerActions",
149
- "resetSelect": "NPLLIq_resetSelect",
150
- "logDayCol": "NPLLIq_logDayCol",
151
- "notice": "NPLLIq_notice",
152
137
  "monitorName": "NPLLIq_monitorName",
153
- "tab": "NPLLIq_tab",
154
- "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
155
- "logUsedCol": "NPLLIq_logUsedCol",
156
- "settingsBtn": "NPLLIq_settingsBtn",
157
- "headerText": "NPLLIq_headerText",
158
- "dialogActions": "NPLLIq_dialogActions",
159
- "monitorHint": "NPLLIq_monitorHint",
160
- "subtitle": "NPLLIq_subtitle",
138
+ "bar": "NPLLIq_bar",
139
+ "dialogBtn": "NPLLIq_dialogBtn",
161
140
  "rowHeader": "NPLLIq_rowHeader",
162
- "input": "NPLLIq_input",
163
- "dialogSection": "NPLLIq_dialogSection",
141
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
142
+ "dialogClose": "NPLLIq_dialogClose",
143
+ "currentBadge": "NPLLIq_currentBadge",
164
144
  "monitorList": "NPLLIq_monitorList",
165
- "fullNotice": "NPLLIq_fullNotice",
166
- "dialogBtn": "NPLLIq_dialogBtn",
167
- "dialogHeader": "NPLLIq_dialogHeader",
145
+ "logDayCol": "NPLLIq_logDayCol",
146
+ "headerText": "NPLLIq_headerText",
147
+ "row": "NPLLIq_row",
168
148
  "body": "NPLLIq_body",
169
- "noticeError": "NPLLIq_noticeError",
170
- "selectBtn": "NPLLIq_selectBtn",
149
+ "logModelCol": "NPLLIq_logModelCol",
171
150
  "collapse": "NPLLIq_collapse",
151
+ "monitorRow": "NPLLIq_monitorRow",
152
+ "rowName": "NPLLIq_rowName",
153
+ "fillWarn": "NPLLIq_fillWarn",
172
154
  "rowMeta": "NPLLIq_rowMeta",
173
- "radioRow": "NPLLIq_radioRow",
155
+ "noticeError": "NPLLIq_noticeError",
156
+ "subtitle": "NPLLIq_subtitle",
174
157
  "resetRow": "NPLLIq_resetRow",
175
- "fillOver": "NPLLIq_fillOver",
176
- "logTable": "NPLLIq_logTable",
158
+ "save": "NPLLIq_save",
159
+ "selectBtn": "NPLLIq_selectBtn",
160
+ "resetSelect": "NPLLIq_resetSelect",
161
+ "fillIdle": "NPLLIq_fillIdle",
162
+ "logUsedCol": "NPLLIq_logUsedCol",
163
+ "headerActions": "NPLLIq_headerActions",
164
+ "panel": "NPLLIq_panel",
165
+ "monitorHint": "NPLLIq_monitorHint",
166
+ "fullNotice": "NPLLIq_fullNotice",
167
+ "dialogSection": "NPLLIq_dialogSection",
168
+ "tabIcon": "NPLLIq_tabIcon",
169
+ "settingsBtn": "NPLLIq_settingsBtn",
177
170
  "dialogLabel": "NPLLIq_dialogLabel",
178
171
  "title": "NPLLIq_title",
179
- "tabLabel": "NPLLIq_tabLabel",
180
- "gearBtn": "NPLLIq_gearBtn",
181
- "rowName": "NPLLIq_rowName"
172
+ "notice": "NPLLIq_notice",
173
+ "dialog": "NPLLIq_dialog",
174
+ "header": "NPLLIq_header",
175
+ "fillOver": "NPLLIq_fillOver",
176
+ "tab": "NPLLIq_tab",
177
+ "dialogTitle": "NPLLIq_dialogTitle",
178
+ "radioRow": "NPLLIq_radioRow",
179
+ "dialogActions": "NPLLIq_dialogActions",
180
+ "logTable": "NPLLIq_logTable",
181
+ "dialogHeader": "NPLLIq_dialogHeader"
182
182
  };
183
183
  //#endregion
184
184
  //#region lib/types/client/TokenQuotaPanel.js
@@ -224,6 +224,47 @@ window.__ModuleLoader__.load({
224
224
  }
225
225
  ];
226
226
  /**
227
+ * Pointer-driven drag of a fixed-positioned element. Deltas are applied
228
+ * relative to the element's current on-screen rect, so the first move is
229
+ * seamless even when the element sits centered via transform (dialogs) or
230
+ * relies on CSS defaults (panel). The panel/dialog can be dragged anywhere
231
+ * on screen — no longer locked inside the overlay seat.
232
+ */
233
+ function beginDrag(event, el, apply) {
234
+ if (el === null) return;
235
+ event.preventDefault();
236
+ const startX = event.clientX;
237
+ const startY = event.clientY;
238
+ const rect = el.getBoundingClientRect();
239
+ const baseX = rect.left;
240
+ const baseY = rect.top;
241
+ const onMove = (ev) => {
242
+ apply({
243
+ x: baseX + (ev.clientX - startX),
244
+ y: baseY + (ev.clientY - startY)
245
+ });
246
+ };
247
+ const onUp = () => {
248
+ window.removeEventListener("pointermove", onMove);
249
+ window.removeEventListener("pointerup", onUp);
250
+ window.removeEventListener("pointercancel", onUp);
251
+ };
252
+ window.addEventListener("pointermove", onMove);
253
+ window.addEventListener("pointerup", onUp);
254
+ window.addEventListener("pointercancel", onUp);
255
+ }
256
+ /** Persisted panel placement so a dragged position survives reloads. */
257
+ const PANEL_POS_KEY = "dsh-token-quota:panel-pos";
258
+ function loadPanelPos() {
259
+ try {
260
+ const raw = window.localStorage.getItem(PANEL_POS_KEY);
261
+ if (raw === null) return null;
262
+ const parsed = JSON.parse(raw);
263
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.x === "number" && typeof parsed.y === "number") return parsed;
264
+ } catch {}
265
+ return null;
266
+ }
267
+ /**
227
268
  * Render the floating panel (or its collapsed tab).
228
269
  * @param props - composed slot props.
229
270
  * @returns the panel element tree.
@@ -232,6 +273,18 @@ window.__ModuleLoader__.load({
232
273
  const [collapsed, setCollapsed] = (0, react.useState)(false);
233
274
  const [drafts, setDrafts] = (0, react.useState)({});
234
275
  const [editingKey, setEditingKey] = (0, react.useState)(null);
276
+ const [panelPos, setPanelPos] = (0, react.useState)(loadPanelPos);
277
+ const [settingsPos, setSettingsPos] = (0, react.useState)(null);
278
+ const [logPos, setLogPos] = (0, react.useState)(null);
279
+ const panelRef = (0, react.useRef)(null);
280
+ const settingsRef = (0, react.useRef)(null);
281
+ const logRef = (0, react.useRef)(null);
282
+ const applyPanelPos = (pos) => {
283
+ setPanelPos(pos);
284
+ try {
285
+ window.localStorage.setItem(PANEL_POS_KEY, JSON.stringify(pos));
286
+ } catch {}
287
+ };
235
288
  const sessionId = useSessions((s) => s.current);
236
289
  const snapshot = useStore((s) => s.snapshot);
237
290
  const groups = useStore((s) => s.groups);
@@ -258,18 +311,29 @@ window.__ModuleLoader__.load({
258
311
  name: model.name
259
312
  }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
260
313
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
261
- if (collapsed) return (0, react_jsx_runtime.jsx)("div", {
314
+ if (collapsed) return (0, react_jsx_runtime.jsxs)("div", {
262
315
  className: TokenQuotaPanel_module_css_default.tab,
263
316
  role: "button",
264
317
  tabIndex: 0,
265
- title: t("title"),
318
+ title: t("expandHint"),
319
+ "aria-label": t("expandHint"),
266
320
  onClick: () => {
267
321
  setCollapsed(false);
268
322
  },
269
- children: (0, react_jsx_runtime.jsx)("span", {
323
+ onKeyDown: (event) => {
324
+ if (event.key === "Enter" || event.key === " ") {
325
+ event.preventDefault();
326
+ setCollapsed(false);
327
+ }
328
+ },
329
+ children: [(0, react_jsx_runtime.jsx)("span", {
330
+ className: TokenQuotaPanel_module_css_default.tabIcon,
331
+ "aria-hidden": "true",
332
+ children: "▸"
333
+ }), (0, react_jsx_runtime.jsx)("span", {
270
334
  className: TokenQuotaPanel_module_css_default.tabLabel,
271
335
  children: t("expand")
272
- })
336
+ })]
273
337
  });
274
338
  const openSettings = () => {
275
339
  actions.setDialogOpen(true);
@@ -298,10 +362,19 @@ window.__ModuleLoader__.load({
298
362
  };
299
363
  const visibleRows = rows.filter((row) => isMonitoredKey(row.key) || row.current);
300
364
  return (0, react_jsx_runtime.jsxs)("div", {
365
+ ref: panelRef,
301
366
  className: TokenQuotaPanel_module_css_default.panel,
367
+ style: panelPos !== null ? {
368
+ left: panelPos.x,
369
+ top: panelPos.y,
370
+ right: "auto"
371
+ } : void 0,
302
372
  children: [
303
373
  (0, react_jsx_runtime.jsxs)("div", {
304
374
  className: TokenQuotaPanel_module_css_default.header,
375
+ onPointerDown: (event) => {
376
+ beginDrag(event, panelRef.current, applyPanelPos);
377
+ },
305
378
  children: [(0, react_jsx_runtime.jsxs)("div", {
306
379
  className: TokenQuotaPanel_module_css_default.headerText,
307
380
  children: [(0, react_jsx_runtime.jsx)("div", {
@@ -313,6 +386,9 @@ window.__ModuleLoader__.load({
313
386
  })]
314
387
  }), (0, react_jsx_runtime.jsxs)("div", {
315
388
  className: TokenQuotaPanel_module_css_default.headerActions,
389
+ onPointerDown: (event) => {
390
+ event.stopPropagation();
391
+ },
316
392
  children: [
317
393
  (0, react_jsx_runtime.jsx)("button", {
318
394
  type: "button",
@@ -439,206 +515,209 @@ window.__ModuleLoader__.load({
439
515
  })
440
516
  ]
441
517
  }),
442
- dialogOpen && (0, react_jsx_runtime.jsx)("div", {
443
- className: TokenQuotaPanel_module_css_default.dialogBackdrop,
444
- onClick: () => {
445
- actions.setDialogOpen(false);
446
- },
447
- children: (0, react_jsx_runtime.jsxs)("div", {
448
- className: TokenQuotaPanel_module_css_default.dialog,
449
- onClick: (event) => {
450
- event.stopPropagation();
451
- },
452
- children: [
453
- (0, react_jsx_runtime.jsxs)("div", {
454
- className: TokenQuotaPanel_module_css_default.dialogHeader,
455
- children: [(0, react_jsx_runtime.jsx)("div", {
456
- className: TokenQuotaPanel_module_css_default.dialogTitle,
457
- children: t("settingsTitle")
458
- }), (0, react_jsx_runtime.jsx)("button", {
459
- type: "button",
460
- className: TokenQuotaPanel_module_css_default.dialogClose,
461
- title: t("close"),
462
- onClick: () => {
463
- actions.setDialogOpen(false);
464
- },
465
- children: "×"
466
- })]
467
- }),
468
- (0, react_jsx_runtime.jsxs)("div", {
469
- className: TokenQuotaPanel_module_css_default.dialogSection,
470
- children: [
471
- (0, react_jsx_runtime.jsx)("div", {
472
- className: TokenQuotaPanel_module_css_default.dialogLabel,
473
- children: t("monitorLabel")
474
- }),
475
- (0, react_jsx_runtime.jsx)("div", {
476
- className: TokenQuotaPanel_module_css_default.monitorHint,
477
- children: t("monitorHint")
478
- }),
479
- (0, react_jsx_runtime.jsx)("div", {
480
- className: TokenQuotaPanel_module_css_default.monitorList,
481
- children: allModels.map((model) => (0, react_jsx_runtime.jsxs)("label", {
482
- className: TokenQuotaPanel_module_css_default.monitorRow,
483
- children: [(0, react_jsx_runtime.jsx)("input", {
484
- type: "checkbox",
485
- checked: monitored === null || monitored.includes(model.key),
486
- onChange: (event) => {
487
- const base = monitored === null ? allModels.map((m) => m.key) : monitored;
488
- const next = new Set(base);
489
- if (event.target.checked) next.add(model.key);
490
- else next.delete(model.key);
491
- setMonitored(next.size === allModels.length ? null : [...next]);
492
- }
493
- }), (0, react_jsx_runtime.jsx)("span", {
494
- className: TokenQuotaPanel_module_css_default.monitorName,
495
- title: model.key,
496
- children: model.name
497
- })]
498
- }, model.key))
499
- })
500
- ]
501
- }),
502
- (0, react_jsx_runtime.jsxs)("div", {
503
- className: TokenQuotaPanel_module_css_default.dialogSection,
504
- children: [(0, react_jsx_runtime.jsx)("div", {
518
+ dialogOpen && (0, react_jsx_runtime.jsxs)("div", {
519
+ ref: settingsRef,
520
+ className: TokenQuotaPanel_module_css_default.dialog,
521
+ style: settingsPos !== null ? {
522
+ left: settingsPos.x,
523
+ top: settingsPos.y,
524
+ transform: "none"
525
+ } : void 0,
526
+ children: [
527
+ (0, react_jsx_runtime.jsxs)("div", {
528
+ className: TokenQuotaPanel_module_css_default.dialogHeader,
529
+ onPointerDown: (event) => {
530
+ beginDrag(event, settingsRef.current, setSettingsPos);
531
+ },
532
+ children: [(0, react_jsx_runtime.jsx)("div", {
533
+ className: TokenQuotaPanel_module_css_default.dialogTitle,
534
+ children: t("settingsTitle")
535
+ }), (0, react_jsx_runtime.jsx)("button", {
536
+ type: "button",
537
+ className: TokenQuotaPanel_module_css_default.dialogClose,
538
+ title: t("close"),
539
+ onClick: () => {
540
+ actions.setDialogOpen(false);
541
+ },
542
+ onPointerDown: (event) => {
543
+ event.stopPropagation();
544
+ },
545
+ children: "×"
546
+ })]
547
+ }),
548
+ (0, react_jsx_runtime.jsxs)("div", {
549
+ className: TokenQuotaPanel_module_css_default.dialogSection,
550
+ children: [
551
+ (0, react_jsx_runtime.jsx)("div", {
505
552
  className: TokenQuotaPanel_module_css_default.dialogLabel,
506
- children: t("fullActionLabel")
507
- }), FULL_ACTIONS.map((action) => (0, react_jsx_runtime.jsxs)("label", {
508
- className: TokenQuotaPanel_module_css_default.radioRow,
509
- children: [(0, react_jsx_runtime.jsx)("input", {
510
- type: "radio",
511
- name: "token-quota-onfull",
512
- checked: onFull === action.value,
513
- onChange: () => {
514
- setOnFull(action.value);
515
- }
516
- }), (0, react_jsx_runtime.jsx)("span", { children: t(action.labelKey) })]
517
- }, action.value))]
518
- }),
519
- (0, react_jsx_runtime.jsxs)("div", {
520
- className: TokenQuotaPanel_module_css_default.dialogSection,
521
- children: [
522
- (0, react_jsx_runtime.jsx)("div", {
523
- className: TokenQuotaPanel_module_css_default.dialogLabel,
524
- children: t("resetLabel")
525
- }),
526
- (0, react_jsx_runtime.jsx)("div", {
527
- className: TokenQuotaPanel_module_css_default.monitorHint,
528
- children: t("resetHint")
529
- }),
530
- (0, react_jsx_runtime.jsxs)("div", {
531
- className: TokenQuotaPanel_module_css_default.resetRow,
532
- children: [
533
- (0, react_jsx_runtime.jsx)("select", {
534
- className: TokenQuotaPanel_module_css_default.resetSelect,
535
- value: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
536
- onChange: (event) => {
537
- setReset({
538
- offsetHours: Number(event.target.value),
539
- hour: reset?.hour ?? 0,
540
- minute: reset?.minute ?? 0
541
- });
542
- },
543
- children: Array.from({ length: 27 }, (_, i) => i - 12).map((offset) => (0, react_jsx_runtime.jsxs)("option", {
544
- value: offset,
545
- children: ["UTC", offset >= 0 ? `+${offset}` : offset]
546
- }, offset))
547
- }),
548
- (0, react_jsx_runtime.jsx)("select", {
549
- className: TokenQuotaPanel_module_css_default.resetSelect,
550
- value: reset?.hour ?? 0,
551
- onChange: (event) => {
552
- setReset({
553
- offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
554
- hour: Number(event.target.value),
555
- minute: reset?.minute ?? 0
556
- });
557
- },
558
- children: Array.from({ length: 24 }, (_, i) => i).map((hour) => (0, react_jsx_runtime.jsxs)("option", {
559
- value: hour,
560
- children: [String(hour).padStart(2, "0"), " 时"]
561
- }, hour))
562
- }),
563
- (0, react_jsx_runtime.jsx)("select", {
564
- className: TokenQuotaPanel_module_css_default.resetSelect,
565
- value: reset?.minute ?? 0,
566
- onChange: (event) => {
567
- setReset({
568
- offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
569
- hour: reset?.hour ?? 0,
570
- minute: Number(event.target.value)
571
- });
572
- },
573
- children: Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => (0, react_jsx_runtime.jsxs)("option", {
574
- value: minute,
575
- children: [String(minute).padStart(2, "0"), " 分"]
576
- }, minute))
577
- })
578
- ]
579
- })
580
- ]
581
- })
582
- ]
583
- })
553
+ children: t("monitorLabel")
554
+ }),
555
+ (0, react_jsx_runtime.jsx)("div", {
556
+ className: TokenQuotaPanel_module_css_default.monitorHint,
557
+ children: t("monitorHint")
558
+ }),
559
+ (0, react_jsx_runtime.jsx)("div", {
560
+ className: TokenQuotaPanel_module_css_default.monitorList,
561
+ children: allModels.map((model) => (0, react_jsx_runtime.jsxs)("label", {
562
+ className: TokenQuotaPanel_module_css_default.monitorRow,
563
+ children: [(0, react_jsx_runtime.jsx)("input", {
564
+ type: "checkbox",
565
+ checked: monitored === null || monitored.includes(model.key),
566
+ onChange: (event) => {
567
+ const base = monitored === null ? allModels.map((m) => m.key) : monitored;
568
+ const next = new Set(base);
569
+ if (event.target.checked) next.add(model.key);
570
+ else next.delete(model.key);
571
+ setMonitored(next.size === allModels.length ? null : [...next]);
572
+ }
573
+ }), (0, react_jsx_runtime.jsx)("span", {
574
+ className: TokenQuotaPanel_module_css_default.monitorName,
575
+ title: model.key,
576
+ children: model.name
577
+ })]
578
+ }, model.key))
579
+ })
580
+ ]
581
+ }),
582
+ (0, react_jsx_runtime.jsxs)("div", {
583
+ className: TokenQuotaPanel_module_css_default.dialogSection,
584
+ children: [(0, react_jsx_runtime.jsx)("div", {
585
+ className: TokenQuotaPanel_module_css_default.dialogLabel,
586
+ children: t("fullActionLabel")
587
+ }), FULL_ACTIONS.map((action) => (0, react_jsx_runtime.jsxs)("label", {
588
+ className: TokenQuotaPanel_module_css_default.radioRow,
589
+ children: [(0, react_jsx_runtime.jsx)("input", {
590
+ type: "radio",
591
+ name: "token-quota-onfull",
592
+ checked: onFull === action.value,
593
+ onChange: () => {
594
+ setOnFull(action.value);
595
+ }
596
+ }), (0, react_jsx_runtime.jsx)("span", { children: t(action.labelKey) })]
597
+ }, action.value))]
598
+ }),
599
+ (0, react_jsx_runtime.jsxs)("div", {
600
+ className: TokenQuotaPanel_module_css_default.dialogSection,
601
+ children: [
602
+ (0, react_jsx_runtime.jsx)("div", {
603
+ className: TokenQuotaPanel_module_css_default.dialogLabel,
604
+ children: t("resetLabel")
605
+ }),
606
+ (0, react_jsx_runtime.jsx)("div", {
607
+ className: TokenQuotaPanel_module_css_default.monitorHint,
608
+ children: t("resetHint")
609
+ }),
610
+ (0, react_jsx_runtime.jsxs)("div", {
611
+ className: TokenQuotaPanel_module_css_default.resetRow,
612
+ children: [
613
+ (0, react_jsx_runtime.jsx)("select", {
614
+ className: TokenQuotaPanel_module_css_default.resetSelect,
615
+ value: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
616
+ onChange: (event) => {
617
+ setReset({
618
+ offsetHours: Number(event.target.value),
619
+ hour: reset?.hour ?? 0,
620
+ minute: reset?.minute ?? 0
621
+ });
622
+ },
623
+ children: Array.from({ length: 27 }, (_, i) => i - 12).map((offset) => (0, react_jsx_runtime.jsxs)("option", {
624
+ value: offset,
625
+ children: ["UTC", offset >= 0 ? `+${offset}` : offset]
626
+ }, offset))
627
+ }),
628
+ (0, react_jsx_runtime.jsx)("select", {
629
+ className: TokenQuotaPanel_module_css_default.resetSelect,
630
+ value: reset?.hour ?? 0,
631
+ onChange: (event) => {
632
+ setReset({
633
+ offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
634
+ hour: Number(event.target.value),
635
+ minute: reset?.minute ?? 0
636
+ });
637
+ },
638
+ children: Array.from({ length: 24 }, (_, i) => i).map((hour) => (0, react_jsx_runtime.jsxs)("option", {
639
+ value: hour,
640
+ children: [String(hour).padStart(2, "0"), " 时"]
641
+ }, hour))
642
+ }),
643
+ (0, react_jsx_runtime.jsx)("select", {
644
+ className: TokenQuotaPanel_module_css_default.resetSelect,
645
+ value: reset?.minute ?? 0,
646
+ onChange: (event) => {
647
+ setReset({
648
+ offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
649
+ hour: reset?.hour ?? 0,
650
+ minute: Number(event.target.value)
651
+ });
652
+ },
653
+ children: Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => (0, react_jsx_runtime.jsxs)("option", {
654
+ value: minute,
655
+ children: [String(minute).padStart(2, "0"), " 分"]
656
+ }, minute))
657
+ })
658
+ ]
659
+ })
660
+ ]
661
+ })
662
+ ]
584
663
  }),
585
- logOpen && (0, react_jsx_runtime.jsx)("div", {
586
- className: TokenQuotaPanel_module_css_default.dialogBackdrop,
587
- onClick: () => {
588
- actions.setLogOpen(false);
589
- },
590
- children: (0, react_jsx_runtime.jsxs)("div", {
591
- className: TokenQuotaPanel_module_css_default.dialog,
592
- onClick: (event) => {
593
- event.stopPropagation();
594
- },
595
- children: [
596
- (0, react_jsx_runtime.jsxs)("div", {
597
- className: TokenQuotaPanel_module_css_default.dialogHeader,
598
- children: [(0, react_jsx_runtime.jsx)("div", {
599
- className: TokenQuotaPanel_module_css_default.dialogTitle,
600
- children: t("logsTitle")
601
- }), (0, react_jsx_runtime.jsx)("button", {
602
- type: "button",
603
- className: TokenQuotaPanel_module_css_default.dialogClose,
604
- title: t("close"),
605
- onClick: () => {
606
- actions.setLogOpen(false);
607
- },
608
- children: "×"
609
- })]
610
- }),
611
- log !== null && log.entries.length === 0 && (0, react_jsx_runtime.jsx)("div", {
612
- className: TokenQuotaPanel_module_css_default.notice,
613
- children: t("logEmpty")
614
- }),
615
- (0, react_jsx_runtime.jsxs)("table", {
616
- className: TokenQuotaPanel_module_css_default.logTable,
617
- children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
618
- (0, react_jsx_runtime.jsx)("th", { children: t("logDay") }),
619
- (0, react_jsx_runtime.jsx)("th", { children: t("logModel") }),
620
- (0, react_jsx_runtime.jsx)("th", {
621
- className: TokenQuotaPanel_module_css_default.logUsedCol,
622
- children: t("logUsed")
623
- })
624
- ] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: log?.entries.map((entry) => (0, react_jsx_runtime.jsxs)("tr", { children: [
625
- (0, react_jsx_runtime.jsx)("td", {
626
- className: TokenQuotaPanel_module_css_default.logDayCol,
627
- children: entry.day
628
- }),
629
- (0, react_jsx_runtime.jsx)("td", {
630
- className: TokenQuotaPanel_module_css_default.logModelCol,
631
- title: entry.key,
632
- children: entry.key
633
- }),
634
- (0, react_jsx_runtime.jsx)("td", {
635
- className: TokenQuotaPanel_module_css_default.logUsedCol,
636
- children: formatTokens(entry.used)
637
- })
638
- ] }, `${entry.day}/${entry.key}`)) })]
639
- })
640
- ]
641
- })
664
+ logOpen && (0, react_jsx_runtime.jsxs)("div", {
665
+ ref: logRef,
666
+ className: TokenQuotaPanel_module_css_default.dialog,
667
+ style: logPos !== null ? {
668
+ left: logPos.x,
669
+ top: logPos.y,
670
+ transform: "none"
671
+ } : void 0,
672
+ children: [
673
+ (0, react_jsx_runtime.jsxs)("div", {
674
+ className: TokenQuotaPanel_module_css_default.dialogHeader,
675
+ onPointerDown: (event) => {
676
+ beginDrag(event, logRef.current, setLogPos);
677
+ },
678
+ children: [(0, react_jsx_runtime.jsx)("div", {
679
+ className: TokenQuotaPanel_module_css_default.dialogTitle,
680
+ children: t("logsTitle")
681
+ }), (0, react_jsx_runtime.jsx)("button", {
682
+ type: "button",
683
+ className: TokenQuotaPanel_module_css_default.dialogClose,
684
+ title: t("close"),
685
+ onClick: () => {
686
+ actions.setLogOpen(false);
687
+ },
688
+ children: "×"
689
+ })]
690
+ }),
691
+ log !== null && log.entries.length === 0 && (0, react_jsx_runtime.jsx)("div", {
692
+ className: TokenQuotaPanel_module_css_default.notice,
693
+ children: t("logEmpty")
694
+ }),
695
+ (0, react_jsx_runtime.jsxs)("table", {
696
+ className: TokenQuotaPanel_module_css_default.logTable,
697
+ children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
698
+ (0, react_jsx_runtime.jsx)("th", { children: t("logDay") }),
699
+ (0, react_jsx_runtime.jsx)("th", { children: t("logModel") }),
700
+ (0, react_jsx_runtime.jsx)("th", {
701
+ className: TokenQuotaPanel_module_css_default.logUsedCol,
702
+ children: t("logUsed")
703
+ })
704
+ ] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: log?.entries.map((entry) => (0, react_jsx_runtime.jsxs)("tr", { children: [
705
+ (0, react_jsx_runtime.jsx)("td", {
706
+ className: TokenQuotaPanel_module_css_default.logDayCol,
707
+ children: entry.day
708
+ }),
709
+ (0, react_jsx_runtime.jsx)("td", {
710
+ className: TokenQuotaPanel_module_css_default.logModelCol,
711
+ title: entry.key,
712
+ children: entry.key
713
+ }),
714
+ (0, react_jsx_runtime.jsx)("td", {
715
+ className: TokenQuotaPanel_module_css_default.logUsedCol,
716
+ children: formatTokens(entry.used)
717
+ })
718
+ ] }, `${entry.day}/${entry.key}`)) })]
719
+ })
720
+ ]
642
721
  })
643
722
  ]
644
723
  });
@@ -669,6 +748,7 @@ window.__ModuleLoader__.load({
669
748
  empty: "暂无模型数据",
670
749
  collapse: "收起",
671
750
  expand: "展开",
751
+ expandHint: "展开每日 Token 限额面板",
672
752
  settings: "设置",
673
753
  settingsTitle: "限额设置",
674
754
  monitorLabel: "监控模型",
@@ -713,6 +793,7 @@ window.__ModuleLoader__.load({
713
793
  empty: "No model data",
714
794
  collapse: "Collapse",
715
795
  expand: "Expand",
796
+ expandHint: "Expand the daily token quota panel",
716
797
  settings: "Settings",
717
798
  settingsTitle: "Quota Settings",
718
799
  monitorLabel: "Monitored models",
@@ -9,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  * arrives through the props shares and every mutation through the injected
10
10
  * callbacks.
11
11
  */
12
- import { useEffect, useMemo, useState } from 'react';
12
+ import { useEffect, useMemo, useRef, useState } from 'react';
13
13
  import { mergeModelRows } from "./store.js";
14
14
  import css from './TokenQuotaPanel.module.css';
15
15
  /** Compact a token count for display. */
@@ -36,6 +36,52 @@ const FULL_ACTIONS = [
36
36
  { value: 'switchAll', labelKey: 'fullSwitchAll' },
37
37
  { value: 'switchPriority', labelKey: 'fullSwitchPriority' },
38
38
  ];
39
+ /**
40
+ * Pointer-driven drag of a fixed-positioned element. Deltas are applied
41
+ * relative to the element's current on-screen rect, so the first move is
42
+ * seamless even when the element sits centered via transform (dialogs) or
43
+ * relies on CSS defaults (panel). The panel/dialog can be dragged anywhere
44
+ * on screen — no longer locked inside the overlay seat.
45
+ */
46
+ function beginDrag(event, el, apply) {
47
+ if (el === null)
48
+ return;
49
+ event.preventDefault();
50
+ const startX = event.clientX;
51
+ const startY = event.clientY;
52
+ const rect = el.getBoundingClientRect();
53
+ const baseX = rect.left;
54
+ const baseY = rect.top;
55
+ const onMove = (ev) => {
56
+ apply({ x: baseX + (ev.clientX - startX), y: baseY + (ev.clientY - startY) });
57
+ };
58
+ const onUp = () => {
59
+ window.removeEventListener('pointermove', onMove);
60
+ window.removeEventListener('pointerup', onUp);
61
+ window.removeEventListener('pointercancel', onUp);
62
+ };
63
+ window.addEventListener('pointermove', onMove);
64
+ window.addEventListener('pointerup', onUp);
65
+ window.addEventListener('pointercancel', onUp);
66
+ }
67
+ /** Persisted panel placement so a dragged position survives reloads. */
68
+ const PANEL_POS_KEY = 'dsh-token-quota:panel-pos';
69
+ function loadPanelPos() {
70
+ try {
71
+ const raw = window.localStorage.getItem(PANEL_POS_KEY);
72
+ if (raw === null)
73
+ return null;
74
+ const parsed = JSON.parse(raw);
75
+ if (typeof parsed === 'object' && parsed !== null
76
+ && typeof parsed.x === 'number' && typeof parsed.y === 'number') {
77
+ return parsed;
78
+ }
79
+ }
80
+ catch {
81
+ // Corrupted or unavailable storage: fall back to the default placement.
82
+ }
83
+ return null;
84
+ }
39
85
  /**
40
86
  * Render the floating panel (or its collapsed tab).
41
87
  * @param props - composed slot props.
@@ -45,6 +91,23 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
45
91
  const [collapsed, setCollapsed] = useState(false);
46
92
  const [drafts, setDrafts] = useState({});
47
93
  const [editingKey, setEditingKey] = useState(null);
94
+ // Draggable placements: the panel persists across reloads; the dialogs
95
+ // start centered (null) and remember where they were dragged to.
96
+ const [panelPos, setPanelPos] = useState(loadPanelPos);
97
+ const [settingsPos, setSettingsPos] = useState(null);
98
+ const [logPos, setLogPos] = useState(null);
99
+ const panelRef = useRef(null);
100
+ const settingsRef = useRef(null);
101
+ const logRef = useRef(null);
102
+ const applyPanelPos = (pos) => {
103
+ setPanelPos(pos);
104
+ try {
105
+ window.localStorage.setItem(PANEL_POS_KEY, JSON.stringify(pos));
106
+ }
107
+ catch {
108
+ // Storage unavailable: the position still applies for this session.
109
+ }
110
+ };
48
111
  const sessionId = useSessions(s => s.current);
49
112
  const snapshot = useStore(s => s.snapshot);
50
113
  const groups = useStore(s => s.groups);
@@ -71,7 +134,12 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
71
134
  }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
72
135
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
73
136
  if (collapsed) {
74
- return (_jsx("div", { className: css.tab, role: "button", tabIndex: 0, title: t('title'), onClick: () => { setCollapsed(false); }, children: _jsx("span", { className: css.tabLabel, children: t('expand') }) }));
137
+ return (_jsxs("div", { className: css.tab, role: "button", tabIndex: 0, title: t('expandHint'), "aria-label": t('expandHint'), onClick: () => { setCollapsed(false); }, onKeyDown: (event) => {
138
+ if (event.key === 'Enter' || event.key === ' ') {
139
+ event.preventDefault();
140
+ setCollapsed(false);
141
+ }
142
+ }, children: [_jsx("span", { className: css.tabIcon, "aria-hidden": "true", children: "\u25B8" }), _jsx("span", { className: css.tabLabel, children: t('expand') })] }));
75
143
  }
76
144
  const openSettings = () => {
77
145
  actions.setDialogOpen(true);
@@ -102,7 +170,7 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
102
170
  setLimit(row.key, parsed);
103
171
  };
104
172
  const visibleRows = rows.filter(row => isMonitoredKey(row.key) || row.current);
105
- return (_jsxs("div", { className: css.panel, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.headerText, children: [_jsx("div", { className: css.title, children: t('title') }), _jsx("div", { className: css.subtitle, children: t('subtitle') })] }), _jsxs("div", { className: css.headerActions, children: [_jsx("button", { type: "button", className: css.settingsBtn, onClick: () => { actions.setLogOpen(true); }, children: t('logs') }), _jsx("button", { type: "button", className: css.settingsBtn, onClick: openSettings, children: t('settings') }), _jsx("button", { type: "button", className: css.collapse, onClick: () => { setCollapsed(true); }, children: t('collapse') })] })] }), _jsxs("div", { className: css.body, children: [loading && _jsx("div", { className: css.notice, children: t('loading') }), error !== null && _jsx("div", { className: css.noticeError, children: error }), fullNotice !== null && (_jsx("div", { className: css.fullNotice, role: "alert", children: fullNotice })), !loading && error === null && visibleRows.length === 0 && (_jsx("div", { className: css.notice, children: monitored !== null && monitored.length === 0 ? t('noMonitored') : t('waiting') })), visibleRows.map((row) => {
173
+ return (_jsxs("div", { ref: panelRef, className: css.panel, style: panelPos !== null ? { left: panelPos.x, top: panelPos.y, right: 'auto' } : undefined, children: [_jsxs("div", { className: css.header, onPointerDown: (event) => { beginDrag(event, panelRef.current, applyPanelPos); }, children: [_jsxs("div", { className: css.headerText, children: [_jsx("div", { className: css.title, children: t('title') }), _jsx("div", { className: css.subtitle, children: t('subtitle') })] }), _jsxs("div", { className: css.headerActions, onPointerDown: (event) => { event.stopPropagation(); }, children: [_jsx("button", { type: "button", className: css.settingsBtn, onClick: () => { actions.setLogOpen(true); }, children: t('logs') }), _jsx("button", { type: "button", className: css.settingsBtn, onClick: openSettings, children: t('settings') }), _jsx("button", { type: "button", className: css.collapse, onClick: () => { setCollapsed(true); }, children: t('collapse') })] })] }), _jsxs("div", { className: css.body, children: [loading && _jsx("div", { className: css.notice, children: t('loading') }), error !== null && _jsx("div", { className: css.noticeError, children: error }), fullNotice !== null && (_jsx("div", { className: css.fullNotice, role: "alert", children: fullNotice })), !loading && error === null && visibleRows.length === 0 && (_jsx("div", { className: css.notice, children: monitored !== null && monitored.length === 0 ? t('noMonitored') : t('waiting') })), visibleRows.map((row) => {
106
174
  const state = barStateOf(row);
107
175
  const pct = row.limit > 0 ? Math.min(100, Math.round((row.used / row.limit) * 100)) : 0;
108
176
  const barClass = state === 'over'
@@ -115,33 +183,33 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
115
183
  ? `${formatTokens(row.used)} / ${formatTokens(row.limit)}`
116
184
  : `${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')
117
185
  commitLimit(row); } }), _jsx("button", { type: "button", className: css.save, onClick: () => { commitLimit(row); }, children: t('save') })] }))] }, row.key));
118
- })] }), dialogOpen && (_jsx("div", { className: css.dialogBackdrop, onClick: () => { actions.setDialogOpen(false); }, children: _jsxs("div", { className: css.dialog, onClick: (event) => { event.stopPropagation(); }, children: [_jsxs("div", { className: css.dialogHeader, children: [_jsx("div", { className: css.dialogTitle, children: t('settingsTitle') }), _jsx("button", { type: "button", className: css.dialogClose, title: t('close'), onClick: () => { actions.setDialogOpen(false); }, children: "\u00D7" })] }), _jsxs("div", { className: css.dialogSection, children: [_jsx("div", { className: css.dialogLabel, children: t('monitorLabel') }), _jsx("div", { className: css.monitorHint, children: t('monitorHint') }), _jsx("div", { className: css.monitorList, children: allModels.map(model => (_jsxs("label", { className: css.monitorRow, children: [_jsx("input", { type: "checkbox", checked: monitored === null || monitored.includes(model.key), onChange: (event) => {
119
- const base = monitored === null ? allModels.map(m => m.key) : monitored;
120
- const next = new Set(base);
121
- if (event.target.checked)
122
- next.add(model.key);
123
- else
124
- next.delete(model.key);
125
- // Auto-save on every change; all selected stores as null.
126
- setMonitored(next.size === allModels.length ? null : [...next]);
127
- } }), _jsx("span", { className: css.monitorName, title: model.key, children: model.name })] }, model.key))) })] }), _jsxs("div", { className: css.dialogSection, children: [_jsx("div", { className: css.dialogLabel, children: t('fullActionLabel') }), FULL_ACTIONS.map(action => (_jsxs("label", { className: css.radioRow, children: [_jsx("input", { type: "radio", name: "token-quota-onfull", checked: onFull === action.value, onChange: () => { setOnFull(action.value); } }), _jsx("span", { children: t(action.labelKey) })] }, action.value)))] }), _jsxs("div", { className: css.dialogSection, children: [_jsx("div", { className: css.dialogLabel, children: t('resetLabel') }), _jsx("div", { className: css.monitorHint, children: t('resetHint') }), _jsxs("div", { className: css.resetRow, children: [_jsx("select", { className: css.resetSelect, value: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60, onChange: (event) => {
128
- setReset({
129
- offsetHours: Number(event.target.value),
130
- hour: reset?.hour ?? 0,
131
- minute: reset?.minute ?? 0,
132
- });
133
- }, children: Array.from({ length: 27 }, (_, i) => i - 12).map(offset => (_jsxs("option", { value: offset, children: ["UTC", offset >= 0 ? `+${offset}` : offset] }, offset))) }), _jsx("select", { className: css.resetSelect, value: reset?.hour ?? 0, onChange: (event) => {
134
- setReset({
135
- offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
136
- hour: Number(event.target.value),
137
- minute: reset?.minute ?? 0,
138
- });
139
- }, children: Array.from({ length: 24 }, (_, i) => i).map(hour => (_jsxs("option", { value: hour, children: [String(hour).padStart(2, '0'), " \u65F6"] }, hour))) }), _jsx("select", { className: css.resetSelect, value: reset?.minute ?? 0, onChange: (event) => {
140
- setReset({
141
- offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
142
- hour: reset?.hour ?? 0,
143
- minute: Number(event.target.value),
144
- });
145
- }, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] })] }) })), logOpen && (_jsx("div", { className: css.dialogBackdrop, onClick: () => { actions.setLogOpen(false); }, children: _jsxs("div", { className: css.dialog, onClick: (event) => { event.stopPropagation(); }, children: [_jsxs("div", { className: css.dialogHeader, children: [_jsx("div", { className: css.dialogTitle, children: t('logsTitle') }), _jsx("button", { type: "button", className: css.dialogClose, title: t('close'), onClick: () => { actions.setLogOpen(false); }, children: "\u00D7" })] }), log !== null && log.entries.length === 0 && (_jsx("div", { className: css.notice, children: t('logEmpty') })), _jsxs("table", { className: css.logTable, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t('logDay') }), _jsx("th", { children: t('logModel') }), _jsx("th", { className: css.logUsedCol, children: t('logUsed') })] }) }), _jsx("tbody", { children: log?.entries.map(entry => (_jsxs("tr", { children: [_jsx("td", { className: css.logDayCol, children: entry.day }), _jsx("td", { className: css.logModelCol, title: entry.key, children: entry.key }), _jsx("td", { className: css.logUsedCol, children: formatTokens(entry.used) })] }, `${entry.day}/${entry.key}`))) })] })] }) }))] }));
186
+ })] }), dialogOpen && (_jsxs("div", { ref: settingsRef, className: css.dialog, style: settingsPos !== null ? { left: settingsPos.x, top: settingsPos.y, transform: 'none' } : undefined, children: [_jsxs("div", { className: css.dialogHeader, onPointerDown: (event) => { beginDrag(event, settingsRef.current, setSettingsPos); }, children: [_jsx("div", { className: css.dialogTitle, children: t('settingsTitle') }), _jsx("button", { type: "button", className: css.dialogClose, title: t('close'), onClick: () => { actions.setDialogOpen(false); }, onPointerDown: (event) => { event.stopPropagation(); }, children: "\u00D7" })] }), _jsxs("div", { className: css.dialogSection, children: [_jsx("div", { className: css.dialogLabel, children: t('monitorLabel') }), _jsx("div", { className: css.monitorHint, children: t('monitorHint') }), _jsx("div", { className: css.monitorList, children: allModels.map(model => (_jsxs("label", { className: css.monitorRow, children: [_jsx("input", { type: "checkbox", checked: monitored === null || monitored.includes(model.key), onChange: (event) => {
187
+ const base = monitored === null ? allModels.map(m => m.key) : monitored;
188
+ const next = new Set(base);
189
+ if (event.target.checked)
190
+ next.add(model.key);
191
+ else
192
+ next.delete(model.key);
193
+ // Auto-save on every change; all selected stores as null.
194
+ setMonitored(next.size === allModels.length ? null : [...next]);
195
+ } }), _jsx("span", { className: css.monitorName, title: model.key, children: model.name })] }, model.key))) })] }), _jsxs("div", { className: css.dialogSection, children: [_jsx("div", { className: css.dialogLabel, children: t('fullActionLabel') }), FULL_ACTIONS.map(action => (_jsxs("label", { className: css.radioRow, children: [_jsx("input", { type: "radio", name: "token-quota-onfull", checked: onFull === action.value, onChange: () => { setOnFull(action.value); } }), _jsx("span", { children: t(action.labelKey) })] }, action.value)))] }), _jsxs("div", { className: css.dialogSection, children: [_jsx("div", { className: css.dialogLabel, children: t('resetLabel') }), _jsx("div", { className: css.monitorHint, children: t('resetHint') }), _jsxs("div", { className: css.resetRow, children: [_jsx("select", { className: css.resetSelect, value: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60, onChange: (event) => {
196
+ setReset({
197
+ offsetHours: Number(event.target.value),
198
+ hour: reset?.hour ?? 0,
199
+ minute: reset?.minute ?? 0,
200
+ });
201
+ }, children: Array.from({ length: 27 }, (_, i) => i - 12).map(offset => (_jsxs("option", { value: offset, children: ["UTC", offset >= 0 ? `+${offset}` : offset] }, offset))) }), _jsx("select", { className: css.resetSelect, value: reset?.hour ?? 0, onChange: (event) => {
202
+ setReset({
203
+ offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
204
+ hour: Number(event.target.value),
205
+ minute: reset?.minute ?? 0,
206
+ });
207
+ }, children: Array.from({ length: 24 }, (_, i) => i).map(hour => (_jsxs("option", { value: hour, children: [String(hour).padStart(2, '0'), " \u65F6"] }, hour))) }), _jsx("select", { className: css.resetSelect, value: reset?.minute ?? 0, onChange: (event) => {
208
+ setReset({
209
+ offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
210
+ hour: reset?.hour ?? 0,
211
+ minute: Number(event.target.value),
212
+ });
213
+ }, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] })] })), logOpen && (_jsxs("div", { ref: logRef, className: css.dialog, style: logPos !== null ? { left: logPos.x, top: logPos.y, transform: 'none' } : undefined, children: [_jsxs("div", { className: css.dialogHeader, onPointerDown: (event) => { beginDrag(event, logRef.current, setLogPos); }, children: [_jsx("div", { className: css.dialogTitle, children: t('logsTitle') }), _jsx("button", { type: "button", className: css.dialogClose, title: t('close'), onClick: () => { actions.setLogOpen(false); }, children: "\u00D7" })] }), log !== null && log.entries.length === 0 && (_jsx("div", { className: css.notice, children: t('logEmpty') })), _jsxs("table", { className: css.logTable, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t('logDay') }), _jsx("th", { children: t('logModel') }), _jsx("th", { className: css.logUsedCol, children: t('logUsed') })] }) }), _jsx("tbody", { children: log?.entries.map(entry => (_jsxs("tr", { children: [_jsx("td", { className: css.logDayCol, children: entry.day }), _jsx("td", { className: css.logModelCol, title: entry.key, children: entry.key }), _jsx("td", { className: css.logUsedCol, children: formatTokens(entry.used) })] }, `${entry.day}/${entry.key}`))) })] })] }))] }));
146
214
  }
147
215
  //# sourceMappingURL=TokenQuotaPanel.js.map
@@ -22,6 +22,7 @@ export declare const zh: {
22
22
  empty: string;
23
23
  collapse: string;
24
24
  expand: string;
25
+ expandHint: string;
25
26
  settings: string;
26
27
  settingsTitle: string;
27
28
  monitorLabel: string;
@@ -68,6 +69,7 @@ export declare const en: {
68
69
  empty: string;
69
70
  collapse: string;
70
71
  expand: string;
72
+ expandHint: string;
71
73
  settings: string;
72
74
  settingsTitle: string;
73
75
  monitorLabel: string;
@@ -22,6 +22,7 @@ export const zh = {
22
22
  empty: '暂无模型数据',
23
23
  collapse: '收起',
24
24
  expand: '展开',
25
+ expandHint: '展开每日 Token 限额面板',
25
26
  settings: '设置',
26
27
  settingsTitle: '限额设置',
27
28
  monitorLabel: '监控模型',
@@ -66,6 +67,7 @@ export const en = {
66
67
  empty: 'No model data',
67
68
  collapse: 'Collapse',
68
69
  expand: 'Expand',
70
+ expandHint: 'Expand the daily token quota panel',
69
71
  settings: 'Settings',
70
72
  settingsTitle: 'Quota Settings',
71
73
  monitorLabel: 'Monitored models',
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.0",
4
+ "version": "0.1.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },