@jxgame2020/dsh-token-quota 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -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-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_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,57 +129,56 @@ window.__ModuleLoader__.load({
129
129
  document.head.appendChild(tag);
130
130
  }
131
131
  var TokenQuotaPanel_module_css_default = {
132
- "headerText": "NPLLIq_headerText",
133
- "selectBtn": "NPLLIq_selectBtn",
134
132
  "tabLabel": "NPLLIq_tabLabel",
135
- "dialogLabel": "NPLLIq_dialogLabel",
136
- "monitorRow": "NPLLIq_monitorRow",
133
+ "gearBtn": "NPLLIq_gearBtn",
134
+ "controls": "NPLLIq_controls",
135
+ "input": "NPLLIq_input",
136
+ "switch": "NPLLIq_switch",
137
+ "monitorName": "NPLLIq_monitorName",
138
+ "bar": "NPLLIq_bar",
139
+ "dialogBtn": "NPLLIq_dialogBtn",
140
+ "rowHeader": "NPLLIq_rowHeader",
141
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
142
+ "dialogClose": "NPLLIq_dialogClose",
143
+ "currentBadge": "NPLLIq_currentBadge",
144
+ "monitorList": "NPLLIq_monitorList",
145
+ "logDayCol": "NPLLIq_logDayCol",
146
+ "headerText": "NPLLIq_headerText",
147
+ "row": "NPLLIq_row",
148
+ "body": "NPLLIq_body",
149
+ "logModelCol": "NPLLIq_logModelCol",
137
150
  "collapse": "NPLLIq_collapse",
138
- "title": "NPLLIq_title",
139
- "settingsBtn": "NPLLIq_settingsBtn",
140
- "resetRow": "NPLLIq_resetRow",
151
+ "monitorRow": "NPLLIq_monitorRow",
141
152
  "rowName": "NPLLIq_rowName",
142
- "logTable": "NPLLIq_logTable",
143
- "fillOver": "NPLLIq_fillOver",
144
- "logModelCol": "NPLLIq_logModelCol",
145
- "tabIcon": "NPLLIq_tabIcon",
146
- "logUsedCol": "NPLLIq_logUsedCol",
147
153
  "fillWarn": "NPLLIq_fillWarn",
148
- "resetSelect": "NPLLIq_resetSelect",
149
154
  "rowMeta": "NPLLIq_rowMeta",
150
- "dialogTitle": "NPLLIq_dialogTitle",
151
- "monitorName": "NPLLIq_monitorName",
152
- "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
153
- "row": "NPLLIq_row",
154
- "gearBtn": "NPLLIq_gearBtn",
155
- "dialogBtn": "NPLLIq_dialogBtn",
156
- "dialogSection": "NPLLIq_dialogSection",
157
155
  "noticeError": "NPLLIq_noticeError",
158
- "dialog": "NPLLIq_dialog",
159
- "fillIdle": "NPLLIq_fillIdle",
160
- "controls": "NPLLIq_controls",
156
+ "subtitle": "NPLLIq_subtitle",
157
+ "resetRow": "NPLLIq_resetRow",
161
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",
162
166
  "fullNotice": "NPLLIq_fullNotice",
163
- "dialogActions": "NPLLIq_dialogActions",
164
- "subtitle": "NPLLIq_subtitle",
165
- "input": "NPLLIq_input",
167
+ "dialogSection": "NPLLIq_dialogSection",
168
+ "tabIcon": "NPLLIq_tabIcon",
169
+ "settingsBtn": "NPLLIq_settingsBtn",
170
+ "dialogLabel": "NPLLIq_dialogLabel",
171
+ "title": "NPLLIq_title",
166
172
  "notice": "NPLLIq_notice",
173
+ "dialog": "NPLLIq_dialog",
174
+ "header": "NPLLIq_header",
175
+ "fillOver": "NPLLIq_fillOver",
167
176
  "tab": "NPLLIq_tab",
168
- "monitorHint": "NPLLIq_monitorHint",
169
- "dialogHeader": "NPLLIq_dialogHeader",
170
- "logDayCol": "NPLLIq_logDayCol",
171
- "rowHeader": "NPLLIq_rowHeader",
172
- "monitorList": "NPLLIq_monitorList",
173
- "panel": "NPLLIq_panel",
177
+ "dialogTitle": "NPLLIq_dialogTitle",
174
178
  "radioRow": "NPLLIq_radioRow",
175
- "header": "NPLLIq_header",
176
- "bar": "NPLLIq_bar",
177
- "headerActions": "NPLLIq_headerActions",
178
- "body": "NPLLIq_body",
179
- "currentBadge": "NPLLIq_currentBadge",
180
- "switch": "NPLLIq_switch",
181
- "dialogBackdrop": "NPLLIq_dialogBackdrop",
182
- "dialogClose": "NPLLIq_dialogClose"
179
+ "dialogActions": "NPLLIq_dialogActions",
180
+ "logTable": "NPLLIq_logTable",
181
+ "dialogHeader": "NPLLIq_dialogHeader"
183
182
  };
184
183
  //#endregion
185
184
  //#region lib/types/client/TokenQuotaPanel.js
@@ -225,6 +224,47 @@ window.__ModuleLoader__.load({
225
224
  }
226
225
  ];
227
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
+ /**
228
268
  * Render the floating panel (or its collapsed tab).
229
269
  * @param props - composed slot props.
230
270
  * @returns the panel element tree.
@@ -233,6 +273,18 @@ window.__ModuleLoader__.load({
233
273
  const [collapsed, setCollapsed] = (0, react.useState)(false);
234
274
  const [drafts, setDrafts] = (0, react.useState)({});
235
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
+ };
236
288
  const sessionId = useSessions((s) => s.current);
237
289
  const snapshot = useStore((s) => s.snapshot);
238
290
  const groups = useStore((s) => s.groups);
@@ -310,10 +362,19 @@ window.__ModuleLoader__.load({
310
362
  };
311
363
  const visibleRows = rows.filter((row) => isMonitoredKey(row.key) || row.current);
312
364
  return (0, react_jsx_runtime.jsxs)("div", {
365
+ ref: panelRef,
313
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,
314
372
  children: [
315
373
  (0, react_jsx_runtime.jsxs)("div", {
316
374
  className: TokenQuotaPanel_module_css_default.header,
375
+ onPointerDown: (event) => {
376
+ beginDrag(event, panelRef.current, applyPanelPos);
377
+ },
317
378
  children: [(0, react_jsx_runtime.jsxs)("div", {
318
379
  className: TokenQuotaPanel_module_css_default.headerText,
319
380
  children: [(0, react_jsx_runtime.jsx)("div", {
@@ -325,6 +386,9 @@ window.__ModuleLoader__.load({
325
386
  })]
326
387
  }), (0, react_jsx_runtime.jsxs)("div", {
327
388
  className: TokenQuotaPanel_module_css_default.headerActions,
389
+ onPointerDown: (event) => {
390
+ event.stopPropagation();
391
+ },
328
392
  children: [
329
393
  (0, react_jsx_runtime.jsx)("button", {
330
394
  type: "button",
@@ -451,206 +515,209 @@ window.__ModuleLoader__.load({
451
515
  })
452
516
  ]
453
517
  }),
454
- dialogOpen && (0, react_jsx_runtime.jsx)("div", {
455
- className: TokenQuotaPanel_module_css_default.dialogBackdrop,
456
- onClick: () => {
457
- actions.setDialogOpen(false);
458
- },
459
- children: (0, react_jsx_runtime.jsxs)("div", {
460
- className: TokenQuotaPanel_module_css_default.dialog,
461
- onClick: (event) => {
462
- event.stopPropagation();
463
- },
464
- children: [
465
- (0, react_jsx_runtime.jsxs)("div", {
466
- className: TokenQuotaPanel_module_css_default.dialogHeader,
467
- children: [(0, react_jsx_runtime.jsx)("div", {
468
- className: TokenQuotaPanel_module_css_default.dialogTitle,
469
- children: t("settingsTitle")
470
- }), (0, react_jsx_runtime.jsx)("button", {
471
- type: "button",
472
- className: TokenQuotaPanel_module_css_default.dialogClose,
473
- title: t("close"),
474
- onClick: () => {
475
- actions.setDialogOpen(false);
476
- },
477
- children: "×"
478
- })]
479
- }),
480
- (0, react_jsx_runtime.jsxs)("div", {
481
- className: TokenQuotaPanel_module_css_default.dialogSection,
482
- children: [
483
- (0, react_jsx_runtime.jsx)("div", {
484
- className: TokenQuotaPanel_module_css_default.dialogLabel,
485
- children: t("monitorLabel")
486
- }),
487
- (0, react_jsx_runtime.jsx)("div", {
488
- className: TokenQuotaPanel_module_css_default.monitorHint,
489
- children: t("monitorHint")
490
- }),
491
- (0, react_jsx_runtime.jsx)("div", {
492
- className: TokenQuotaPanel_module_css_default.monitorList,
493
- children: allModels.map((model) => (0, react_jsx_runtime.jsxs)("label", {
494
- className: TokenQuotaPanel_module_css_default.monitorRow,
495
- children: [(0, react_jsx_runtime.jsx)("input", {
496
- type: "checkbox",
497
- checked: monitored === null || monitored.includes(model.key),
498
- onChange: (event) => {
499
- const base = monitored === null ? allModels.map((m) => m.key) : monitored;
500
- const next = new Set(base);
501
- if (event.target.checked) next.add(model.key);
502
- else next.delete(model.key);
503
- setMonitored(next.size === allModels.length ? null : [...next]);
504
- }
505
- }), (0, react_jsx_runtime.jsx)("span", {
506
- className: TokenQuotaPanel_module_css_default.monitorName,
507
- title: model.key,
508
- children: model.name
509
- })]
510
- }, model.key))
511
- })
512
- ]
513
- }),
514
- (0, react_jsx_runtime.jsxs)("div", {
515
- className: TokenQuotaPanel_module_css_default.dialogSection,
516
- 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", {
517
552
  className: TokenQuotaPanel_module_css_default.dialogLabel,
518
- children: t("fullActionLabel")
519
- }), FULL_ACTIONS.map((action) => (0, react_jsx_runtime.jsxs)("label", {
520
- className: TokenQuotaPanel_module_css_default.radioRow,
521
- children: [(0, react_jsx_runtime.jsx)("input", {
522
- type: "radio",
523
- name: "token-quota-onfull",
524
- checked: onFull === action.value,
525
- onChange: () => {
526
- setOnFull(action.value);
527
- }
528
- }), (0, react_jsx_runtime.jsx)("span", { children: t(action.labelKey) })]
529
- }, action.value))]
530
- }),
531
- (0, react_jsx_runtime.jsxs)("div", {
532
- className: TokenQuotaPanel_module_css_default.dialogSection,
533
- children: [
534
- (0, react_jsx_runtime.jsx)("div", {
535
- className: TokenQuotaPanel_module_css_default.dialogLabel,
536
- children: t("resetLabel")
537
- }),
538
- (0, react_jsx_runtime.jsx)("div", {
539
- className: TokenQuotaPanel_module_css_default.monitorHint,
540
- children: t("resetHint")
541
- }),
542
- (0, react_jsx_runtime.jsxs)("div", {
543
- className: TokenQuotaPanel_module_css_default.resetRow,
544
- children: [
545
- (0, react_jsx_runtime.jsx)("select", {
546
- className: TokenQuotaPanel_module_css_default.resetSelect,
547
- value: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
548
- onChange: (event) => {
549
- setReset({
550
- offsetHours: Number(event.target.value),
551
- hour: reset?.hour ?? 0,
552
- minute: reset?.minute ?? 0
553
- });
554
- },
555
- children: Array.from({ length: 27 }, (_, i) => i - 12).map((offset) => (0, react_jsx_runtime.jsxs)("option", {
556
- value: offset,
557
- children: ["UTC", offset >= 0 ? `+${offset}` : offset]
558
- }, offset))
559
- }),
560
- (0, react_jsx_runtime.jsx)("select", {
561
- className: TokenQuotaPanel_module_css_default.resetSelect,
562
- value: reset?.hour ?? 0,
563
- onChange: (event) => {
564
- setReset({
565
- offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
566
- hour: Number(event.target.value),
567
- minute: reset?.minute ?? 0
568
- });
569
- },
570
- children: Array.from({ length: 24 }, (_, i) => i).map((hour) => (0, react_jsx_runtime.jsxs)("option", {
571
- value: hour,
572
- children: [String(hour).padStart(2, "0"), " 时"]
573
- }, hour))
574
- }),
575
- (0, react_jsx_runtime.jsx)("select", {
576
- className: TokenQuotaPanel_module_css_default.resetSelect,
577
- value: reset?.minute ?? 0,
578
- onChange: (event) => {
579
- setReset({
580
- offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
581
- hour: reset?.hour ?? 0,
582
- minute: Number(event.target.value)
583
- });
584
- },
585
- children: Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => (0, react_jsx_runtime.jsxs)("option", {
586
- value: minute,
587
- children: [String(minute).padStart(2, "0"), " 分"]
588
- }, minute))
589
- })
590
- ]
591
- })
592
- ]
593
- })
594
- ]
595
- })
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
+ ]
596
663
  }),
597
- logOpen && (0, react_jsx_runtime.jsx)("div", {
598
- className: TokenQuotaPanel_module_css_default.dialogBackdrop,
599
- onClick: () => {
600
- actions.setLogOpen(false);
601
- },
602
- children: (0, react_jsx_runtime.jsxs)("div", {
603
- className: TokenQuotaPanel_module_css_default.dialog,
604
- onClick: (event) => {
605
- event.stopPropagation();
606
- },
607
- children: [
608
- (0, react_jsx_runtime.jsxs)("div", {
609
- className: TokenQuotaPanel_module_css_default.dialogHeader,
610
- children: [(0, react_jsx_runtime.jsx)("div", {
611
- className: TokenQuotaPanel_module_css_default.dialogTitle,
612
- children: t("logsTitle")
613
- }), (0, react_jsx_runtime.jsx)("button", {
614
- type: "button",
615
- className: TokenQuotaPanel_module_css_default.dialogClose,
616
- title: t("close"),
617
- onClick: () => {
618
- actions.setLogOpen(false);
619
- },
620
- children: "×"
621
- })]
622
- }),
623
- log !== null && log.entries.length === 0 && (0, react_jsx_runtime.jsx)("div", {
624
- className: TokenQuotaPanel_module_css_default.notice,
625
- children: t("logEmpty")
626
- }),
627
- (0, react_jsx_runtime.jsxs)("table", {
628
- className: TokenQuotaPanel_module_css_default.logTable,
629
- children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
630
- (0, react_jsx_runtime.jsx)("th", { children: t("logDay") }),
631
- (0, react_jsx_runtime.jsx)("th", { children: t("logModel") }),
632
- (0, react_jsx_runtime.jsx)("th", {
633
- className: TokenQuotaPanel_module_css_default.logUsedCol,
634
- children: t("logUsed")
635
- })
636
- ] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: log?.entries.map((entry) => (0, react_jsx_runtime.jsxs)("tr", { children: [
637
- (0, react_jsx_runtime.jsx)("td", {
638
- className: TokenQuotaPanel_module_css_default.logDayCol,
639
- children: entry.day
640
- }),
641
- (0, react_jsx_runtime.jsx)("td", {
642
- className: TokenQuotaPanel_module_css_default.logModelCol,
643
- title: entry.key,
644
- children: entry.key
645
- }),
646
- (0, react_jsx_runtime.jsx)("td", {
647
- className: TokenQuotaPanel_module_css_default.logUsedCol,
648
- children: formatTokens(entry.used)
649
- })
650
- ] }, `${entry.day}/${entry.key}`)) })]
651
- })
652
- ]
653
- })
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
+ ]
654
721
  })
655
722
  ]
656
723
  });
@@ -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);
@@ -107,7 +170,7 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
107
170
  setLimit(row.key, parsed);
108
171
  };
109
172
  const visibleRows = rows.filter(row => isMonitoredKey(row.key) || row.current);
110
- 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) => {
111
174
  const state = barStateOf(row);
112
175
  const pct = row.limit > 0 ? Math.min(100, Math.round((row.used / row.limit) * 100)) : 0;
113
176
  const barClass = state === 'over'
@@ -120,33 +183,33 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
120
183
  ? `${formatTokens(row.used)} / ${formatTokens(row.limit)}`
121
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')
122
185
  commitLimit(row); } }), _jsx("button", { type: "button", className: css.save, onClick: () => { commitLimit(row); }, children: t('save') })] }))] }, row.key));
123
- })] }), 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) => {
124
- const base = monitored === null ? allModels.map(m => m.key) : monitored;
125
- const next = new Set(base);
126
- if (event.target.checked)
127
- next.add(model.key);
128
- else
129
- next.delete(model.key);
130
- // Auto-save on every change; all selected stores as null.
131
- setMonitored(next.size === allModels.length ? null : [...next]);
132
- } }), _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) => {
133
- setReset({
134
- offsetHours: Number(event.target.value),
135
- hour: reset?.hour ?? 0,
136
- minute: reset?.minute ?? 0,
137
- });
138
- }, 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) => {
139
- setReset({
140
- offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
141
- hour: Number(event.target.value),
142
- minute: reset?.minute ?? 0,
143
- });
144
- }, 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) => {
145
- setReset({
146
- offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
147
- hour: reset?.hour ?? 0,
148
- minute: Number(event.target.value),
149
- });
150
- }, 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}`))) })] })] }))] }));
151
214
  }
152
215
  //# sourceMappingURL=TokenQuotaPanel.js.map
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.1",
4
+ "version": "0.1.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },