@jxgame2020/dsh-token-quota 0.1.1 → 0.1.3

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}.NPLLIq_logDialog{gap:0;width:380px;max-height:min(72vh,480px);padding:0}.NPLLIq_logHeader{background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;flex:none;justify-content:space-between;align-items:center;gap:8px;padding:14px 14px 8px;display:flex}.NPLLIq_logHeader:active{cursor:grabbing}.NPLLIq_logScroll{flex:1;min-height:0;padding:0 14px 14px;overflow:auto}";
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,59 @@ 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
- "tabLabel": "NPLLIq_tabLabel",
135
- "dialogLabel": "NPLLIq_dialogLabel",
136
- "monitorRow": "NPLLIq_monitorRow",
137
- "collapse": "NPLLIq_collapse",
138
- "title": "NPLLIq_title",
139
- "settingsBtn": "NPLLIq_settingsBtn",
140
- "resetRow": "NPLLIq_resetRow",
141
- "rowName": "NPLLIq_rowName",
142
- "logTable": "NPLLIq_logTable",
143
- "fillOver": "NPLLIq_fillOver",
144
- "logModelCol": "NPLLIq_logModelCol",
145
- "tabIcon": "NPLLIq_tabIcon",
146
132
  "logUsedCol": "NPLLIq_logUsedCol",
147
- "fillWarn": "NPLLIq_fillWarn",
148
- "resetSelect": "NPLLIq_resetSelect",
149
- "rowMeta": "NPLLIq_rowMeta",
150
- "dialogTitle": "NPLLIq_dialogTitle",
151
- "monitorName": "NPLLIq_monitorName",
152
- "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
133
+ "fillIdle": "NPLLIq_fillIdle",
134
+ "monitorHint": "NPLLIq_monitorHint",
135
+ "logScroll": "NPLLIq_logScroll",
136
+ "title": "NPLLIq_title",
137
+ "body": "NPLLIq_body",
153
138
  "row": "NPLLIq_row",
154
139
  "gearBtn": "NPLLIq_gearBtn",
155
- "dialogBtn": "NPLLIq_dialogBtn",
140
+ "monitorName": "NPLLIq_monitorName",
141
+ "radioRow": "NPLLIq_radioRow",
142
+ "header": "NPLLIq_header",
143
+ "fillOver": "NPLLIq_fillOver",
144
+ "tabLabel": "NPLLIq_tabLabel",
145
+ "selectBtn": "NPLLIq_selectBtn",
146
+ "input": "NPLLIq_input",
147
+ "notice": "NPLLIq_notice",
156
148
  "dialogSection": "NPLLIq_dialogSection",
157
- "noticeError": "NPLLIq_noticeError",
149
+ "dialogLabel": "NPLLIq_dialogLabel",
150
+ "dialogActions": "NPLLIq_dialogActions",
151
+ "headerActions": "NPLLIq_headerActions",
152
+ "rowName": "NPLLIq_rowName",
153
+ "rowMeta": "NPLLIq_rowMeta",
154
+ "collapse": "NPLLIq_collapse",
155
+ "save": "NPLLIq_save",
156
+ "resetSelect": "NPLLIq_resetSelect",
157
+ "logDialog": "NPLLIq_logDialog",
158
+ "dialogClose": "NPLLIq_dialogClose",
158
159
  "dialog": "NPLLIq_dialog",
159
- "fillIdle": "NPLLIq_fillIdle",
160
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
161
+ "rowHeader": "NPLLIq_rowHeader",
160
162
  "controls": "NPLLIq_controls",
161
- "save": "NPLLIq_save",
162
- "fullNotice": "NPLLIq_fullNotice",
163
- "dialogActions": "NPLLIq_dialogActions",
164
- "subtitle": "NPLLIq_subtitle",
165
- "input": "NPLLIq_input",
166
- "notice": "NPLLIq_notice",
167
- "tab": "NPLLIq_tab",
168
- "monitorHint": "NPLLIq_monitorHint",
163
+ "currentBadge": "NPLLIq_currentBadge",
164
+ "tabIcon": "NPLLIq_tabIcon",
169
165
  "dialogHeader": "NPLLIq_dialogHeader",
166
+ "tab": "NPLLIq_tab",
167
+ "dialogTitle": "NPLLIq_dialogTitle",
170
168
  "logDayCol": "NPLLIq_logDayCol",
171
- "rowHeader": "NPLLIq_rowHeader",
172
- "monitorList": "NPLLIq_monitorList",
173
- "panel": "NPLLIq_panel",
174
- "radioRow": "NPLLIq_radioRow",
175
- "header": "NPLLIq_header",
169
+ "settingsBtn": "NPLLIq_settingsBtn",
170
+ "fillWarn": "NPLLIq_fillWarn",
171
+ "fullNotice": "NPLLIq_fullNotice",
176
172
  "bar": "NPLLIq_bar",
177
- "headerActions": "NPLLIq_headerActions",
178
- "body": "NPLLIq_body",
179
- "currentBadge": "NPLLIq_currentBadge",
173
+ "dialogBtn": "NPLLIq_dialogBtn",
180
174
  "switch": "NPLLIq_switch",
181
- "dialogBackdrop": "NPLLIq_dialogBackdrop",
182
- "dialogClose": "NPLLIq_dialogClose"
175
+ "panel": "NPLLIq_panel",
176
+ "headerText": "NPLLIq_headerText",
177
+ "monitorRow": "NPLLIq_monitorRow",
178
+ "logHeader": "NPLLIq_logHeader",
179
+ "resetRow": "NPLLIq_resetRow",
180
+ "monitorList": "NPLLIq_monitorList",
181
+ "logTable": "NPLLIq_logTable",
182
+ "subtitle": "NPLLIq_subtitle",
183
+ "noticeError": "NPLLIq_noticeError",
184
+ "logModelCol": "NPLLIq_logModelCol"
183
185
  };
184
186
  //#endregion
185
187
  //#region lib/types/client/TokenQuotaPanel.js
@@ -225,6 +227,47 @@ window.__ModuleLoader__.load({
225
227
  }
226
228
  ];
227
229
  /**
230
+ * Pointer-driven drag of a fixed-positioned element. Deltas are applied
231
+ * relative to the element's current on-screen rect, so the first move is
232
+ * seamless even when the element sits centered via transform (dialogs) or
233
+ * relies on CSS defaults (panel). The panel/dialog can be dragged anywhere
234
+ * on screen — no longer locked inside the overlay seat.
235
+ */
236
+ function beginDrag(event, el, apply) {
237
+ if (el === null) return;
238
+ event.preventDefault();
239
+ const startX = event.clientX;
240
+ const startY = event.clientY;
241
+ const rect = el.getBoundingClientRect();
242
+ const baseX = rect.left;
243
+ const baseY = rect.top;
244
+ const onMove = (ev) => {
245
+ apply({
246
+ x: baseX + (ev.clientX - startX),
247
+ y: baseY + (ev.clientY - startY)
248
+ });
249
+ };
250
+ const onUp = () => {
251
+ window.removeEventListener("pointermove", onMove);
252
+ window.removeEventListener("pointerup", onUp);
253
+ window.removeEventListener("pointercancel", onUp);
254
+ };
255
+ window.addEventListener("pointermove", onMove);
256
+ window.addEventListener("pointerup", onUp);
257
+ window.addEventListener("pointercancel", onUp);
258
+ }
259
+ /** Persisted panel placement so a dragged position survives reloads. */
260
+ const PANEL_POS_KEY = "dsh-token-quota:panel-pos";
261
+ function loadPanelPos() {
262
+ try {
263
+ const raw = window.localStorage.getItem(PANEL_POS_KEY);
264
+ if (raw === null) return null;
265
+ const parsed = JSON.parse(raw);
266
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.x === "number" && typeof parsed.y === "number") return parsed;
267
+ } catch {}
268
+ return null;
269
+ }
270
+ /**
228
271
  * Render the floating panel (or its collapsed tab).
229
272
  * @param props - composed slot props.
230
273
  * @returns the panel element tree.
@@ -233,6 +276,18 @@ window.__ModuleLoader__.load({
233
276
  const [collapsed, setCollapsed] = (0, react.useState)(false);
234
277
  const [drafts, setDrafts] = (0, react.useState)({});
235
278
  const [editingKey, setEditingKey] = (0, react.useState)(null);
279
+ const [panelPos, setPanelPos] = (0, react.useState)(loadPanelPos);
280
+ const [settingsPos, setSettingsPos] = (0, react.useState)(null);
281
+ const [logPos, setLogPos] = (0, react.useState)(null);
282
+ const panelRef = (0, react.useRef)(null);
283
+ const settingsRef = (0, react.useRef)(null);
284
+ const logRef = (0, react.useRef)(null);
285
+ const applyPanelPos = (pos) => {
286
+ setPanelPos(pos);
287
+ try {
288
+ window.localStorage.setItem(PANEL_POS_KEY, JSON.stringify(pos));
289
+ } catch {}
290
+ };
236
291
  const sessionId = useSessions((s) => s.current);
237
292
  const snapshot = useStore((s) => s.snapshot);
238
293
  const groups = useStore((s) => s.groups);
@@ -259,6 +314,16 @@ window.__ModuleLoader__.load({
259
314
  name: model.name
260
315
  }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
261
316
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
317
+ (0, react.useEffect)(() => {
318
+ if (!logOpen) return;
319
+ let cancelled = false;
320
+ fetch("/token-quota/log", { headers: { accept: "application/json" } }).then((response) => response.json(), () => null).then((data) => {
321
+ if (!cancelled) actions.setLog(data);
322
+ });
323
+ return () => {
324
+ cancelled = true;
325
+ };
326
+ }, [logOpen, actions]);
262
327
  if (collapsed) return (0, react_jsx_runtime.jsxs)("div", {
263
328
  className: TokenQuotaPanel_module_css_default.tab,
264
329
  role: "button",
@@ -286,16 +351,6 @@ window.__ModuleLoader__.load({
286
351
  const openSettings = () => {
287
352
  actions.setDialogOpen(true);
288
353
  };
289
- (0, react.useEffect)(() => {
290
- if (!logOpen) return;
291
- let cancelled = false;
292
- fetch("/token-quota/log", { headers: { accept: "application/json" } }).then((response) => response.json(), () => null).then((data) => {
293
- if (!cancelled) actions.setLog(data);
294
- });
295
- return () => {
296
- cancelled = true;
297
- };
298
- }, [logOpen, actions]);
299
354
  const commitLimit = (row) => {
300
355
  const raw = drafts[row.key]?.trim();
301
356
  setDrafts((prev) => {
@@ -310,10 +365,19 @@ window.__ModuleLoader__.load({
310
365
  };
311
366
  const visibleRows = rows.filter((row) => isMonitoredKey(row.key) || row.current);
312
367
  return (0, react_jsx_runtime.jsxs)("div", {
368
+ ref: panelRef,
313
369
  className: TokenQuotaPanel_module_css_default.panel,
370
+ style: panelPos !== null ? {
371
+ left: panelPos.x,
372
+ top: panelPos.y,
373
+ right: "auto"
374
+ } : void 0,
314
375
  children: [
315
376
  (0, react_jsx_runtime.jsxs)("div", {
316
377
  className: TokenQuotaPanel_module_css_default.header,
378
+ onPointerDown: (event) => {
379
+ beginDrag(event, panelRef.current, applyPanelPos);
380
+ },
317
381
  children: [(0, react_jsx_runtime.jsxs)("div", {
318
382
  className: TokenQuotaPanel_module_css_default.headerText,
319
383
  children: [(0, react_jsx_runtime.jsx)("div", {
@@ -325,6 +389,9 @@ window.__ModuleLoader__.load({
325
389
  })]
326
390
  }), (0, react_jsx_runtime.jsxs)("div", {
327
391
  className: TokenQuotaPanel_module_css_default.headerActions,
392
+ onPointerDown: (event) => {
393
+ event.stopPropagation();
394
+ },
328
395
  children: [
329
396
  (0, react_jsx_runtime.jsx)("button", {
330
397
  type: "button",
@@ -451,180 +518,186 @@ window.__ModuleLoader__.load({
451
518
  })
452
519
  ]
453
520
  }),
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", {
521
+ dialogOpen && (0, react_jsx_runtime.jsxs)("div", {
522
+ ref: settingsRef,
523
+ className: TokenQuotaPanel_module_css_default.dialog,
524
+ style: settingsPos !== null ? {
525
+ left: settingsPos.x,
526
+ top: settingsPos.y,
527
+ transform: "none"
528
+ } : void 0,
529
+ children: [
530
+ (0, react_jsx_runtime.jsxs)("div", {
531
+ className: TokenQuotaPanel_module_css_default.dialogHeader,
532
+ onPointerDown: (event) => {
533
+ beginDrag(event, settingsRef.current, setSettingsPos);
534
+ },
535
+ children: [(0, react_jsx_runtime.jsx)("div", {
536
+ className: TokenQuotaPanel_module_css_default.dialogTitle,
537
+ children: t("settingsTitle")
538
+ }), (0, react_jsx_runtime.jsx)("button", {
539
+ type: "button",
540
+ className: TokenQuotaPanel_module_css_default.dialogClose,
541
+ title: t("close"),
542
+ onClick: () => {
543
+ actions.setDialogOpen(false);
544
+ },
545
+ onPointerDown: (event) => {
546
+ event.stopPropagation();
547
+ },
548
+ children: "×"
549
+ })]
550
+ }),
551
+ (0, react_jsx_runtime.jsxs)("div", {
552
+ className: TokenQuotaPanel_module_css_default.dialogSection,
553
+ children: [
554
+ (0, react_jsx_runtime.jsx)("div", {
517
555
  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
- })
556
+ children: t("monitorLabel")
557
+ }),
558
+ (0, react_jsx_runtime.jsx)("div", {
559
+ className: TokenQuotaPanel_module_css_default.monitorHint,
560
+ children: t("monitorHint")
561
+ }),
562
+ (0, react_jsx_runtime.jsx)("div", {
563
+ className: TokenQuotaPanel_module_css_default.monitorList,
564
+ children: allModels.map((model) => (0, react_jsx_runtime.jsxs)("label", {
565
+ className: TokenQuotaPanel_module_css_default.monitorRow,
566
+ children: [(0, react_jsx_runtime.jsx)("input", {
567
+ type: "checkbox",
568
+ checked: monitored === null || monitored.includes(model.key),
569
+ onChange: (event) => {
570
+ const base = monitored === null ? allModels.map((m) => m.key) : monitored;
571
+ const next = new Set(base);
572
+ if (event.target.checked) next.add(model.key);
573
+ else next.delete(model.key);
574
+ setMonitored(next.size === allModels.length ? null : [...next]);
575
+ }
576
+ }), (0, react_jsx_runtime.jsx)("span", {
577
+ className: TokenQuotaPanel_module_css_default.monitorName,
578
+ title: model.key,
579
+ children: model.name
580
+ })]
581
+ }, model.key))
582
+ })
583
+ ]
584
+ }),
585
+ (0, react_jsx_runtime.jsxs)("div", {
586
+ className: TokenQuotaPanel_module_css_default.dialogSection,
587
+ children: [(0, react_jsx_runtime.jsx)("div", {
588
+ className: TokenQuotaPanel_module_css_default.dialogLabel,
589
+ children: t("fullActionLabel")
590
+ }), FULL_ACTIONS.map((action) => (0, react_jsx_runtime.jsxs)("label", {
591
+ className: TokenQuotaPanel_module_css_default.radioRow,
592
+ children: [(0, react_jsx_runtime.jsx)("input", {
593
+ type: "radio",
594
+ name: "token-quota-onfull",
595
+ checked: onFull === action.value,
596
+ onChange: () => {
597
+ setOnFull(action.value);
598
+ }
599
+ }), (0, react_jsx_runtime.jsx)("span", { children: t(action.labelKey) })]
600
+ }, action.value))]
601
+ }),
602
+ (0, react_jsx_runtime.jsxs)("div", {
603
+ className: TokenQuotaPanel_module_css_default.dialogSection,
604
+ children: [
605
+ (0, react_jsx_runtime.jsx)("div", {
606
+ className: TokenQuotaPanel_module_css_default.dialogLabel,
607
+ children: t("resetLabel")
608
+ }),
609
+ (0, react_jsx_runtime.jsx)("div", {
610
+ className: TokenQuotaPanel_module_css_default.monitorHint,
611
+ children: t("resetHint")
612
+ }),
613
+ (0, react_jsx_runtime.jsxs)("div", {
614
+ className: TokenQuotaPanel_module_css_default.resetRow,
615
+ children: [
616
+ (0, react_jsx_runtime.jsx)("select", {
617
+ className: TokenQuotaPanel_module_css_default.resetSelect,
618
+ value: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
619
+ onChange: (event) => {
620
+ setReset({
621
+ offsetHours: Number(event.target.value),
622
+ hour: reset?.hour ?? 0,
623
+ minute: reset?.minute ?? 0
624
+ });
625
+ },
626
+ children: Array.from({ length: 27 }, (_, i) => i - 12).map((offset) => (0, react_jsx_runtime.jsxs)("option", {
627
+ value: offset,
628
+ children: ["UTC", offset >= 0 ? `+${offset}` : offset]
629
+ }, offset))
630
+ }),
631
+ (0, react_jsx_runtime.jsx)("select", {
632
+ className: TokenQuotaPanel_module_css_default.resetSelect,
633
+ value: reset?.hour ?? 0,
634
+ onChange: (event) => {
635
+ setReset({
636
+ offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
637
+ hour: Number(event.target.value),
638
+ minute: reset?.minute ?? 0
639
+ });
640
+ },
641
+ children: Array.from({ length: 24 }, (_, i) => i).map((hour) => (0, react_jsx_runtime.jsxs)("option", {
642
+ value: hour,
643
+ children: [String(hour).padStart(2, "0"), " 时"]
644
+ }, hour))
645
+ }),
646
+ (0, react_jsx_runtime.jsx)("select", {
647
+ className: TokenQuotaPanel_module_css_default.resetSelect,
648
+ value: reset?.minute ?? 0,
649
+ onChange: (event) => {
650
+ setReset({
651
+ offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
652
+ hour: reset?.hour ?? 0,
653
+ minute: Number(event.target.value)
654
+ });
655
+ },
656
+ children: Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => (0, react_jsx_runtime.jsxs)("option", {
657
+ value: minute,
658
+ children: [String(minute).padStart(2, "0"), " 分"]
659
+ }, minute))
660
+ })
661
+ ]
662
+ })
663
+ ]
664
+ })
665
+ ]
596
666
  }),
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", {
667
+ logOpen && (0, react_jsx_runtime.jsxs)("div", {
668
+ ref: logRef,
669
+ className: `${TokenQuotaPanel_module_css_default.dialog} ${TokenQuotaPanel_module_css_default.logDialog}`,
670
+ style: logPos !== null ? {
671
+ left: logPos.x,
672
+ top: logPos.y,
673
+ transform: "none"
674
+ } : void 0,
675
+ children: [
676
+ (0, react_jsx_runtime.jsxs)("div", {
677
+ className: TokenQuotaPanel_module_css_default.logHeader,
678
+ onPointerDown: (event) => {
679
+ beginDrag(event, logRef.current, setLogPos);
680
+ },
681
+ children: [(0, react_jsx_runtime.jsx)("div", {
682
+ className: TokenQuotaPanel_module_css_default.dialogTitle,
683
+ children: t("logsTitle")
684
+ }), (0, react_jsx_runtime.jsx)("button", {
685
+ type: "button",
686
+ className: TokenQuotaPanel_module_css_default.dialogClose,
687
+ title: t("close"),
688
+ onClick: () => {
689
+ actions.setLogOpen(false);
690
+ },
691
+ children: "×"
692
+ })]
693
+ }),
694
+ log !== null && log.entries.length === 0 && (0, react_jsx_runtime.jsx)("div", {
695
+ className: TokenQuotaPanel_module_css_default.notice,
696
+ children: t("logEmpty")
697
+ }),
698
+ (0, react_jsx_runtime.jsx)("div", {
699
+ className: TokenQuotaPanel_module_css_default.logScroll,
700
+ children: (0, react_jsx_runtime.jsxs)("table", {
628
701
  className: TokenQuotaPanel_module_css_default.logTable,
629
702
  children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
630
703
  (0, react_jsx_runtime.jsx)("th", { children: t("logDay") }),
@@ -649,8 +722,8 @@ window.__ModuleLoader__.load({
649
722
  })
650
723
  ] }, `${entry.day}/${entry.key}`)) })]
651
724
  })
652
- ]
653
- })
725
+ })
726
+ ]
654
727
  })
655
728
  ]
656
729
  });
@@ -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);
@@ -70,6 +133,20 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
70
133
  name: model.name,
71
134
  }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
72
135
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
136
+ // Fetch the usage log whenever the log dialog opens. Kept ABOVE the
137
+ // collapsed early-return: every hook must run on every render, otherwise
138
+ // React unmounts the component the moment the panel collapses (the old
139
+ // placement made the collapsed tab crash instead of showing).
140
+ useEffect(() => {
141
+ if (!logOpen)
142
+ return;
143
+ let cancelled = false;
144
+ void fetch('/token-quota/log', { headers: { accept: 'application/json' } }).then((response) => response.json(), () => null).then((data) => {
145
+ if (!cancelled)
146
+ actions.setLog(data);
147
+ });
148
+ return () => { cancelled = true; };
149
+ }, [logOpen, actions]);
73
150
  if (collapsed) {
74
151
  return (_jsxs("div", { className: css.tab, role: "button", tabIndex: 0, title: t('expandHint'), "aria-label": t('expandHint'), onClick: () => { setCollapsed(false); }, onKeyDown: (event) => {
75
152
  if (event.key === 'Enter' || event.key === ' ') {
@@ -81,17 +158,6 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
81
158
  const openSettings = () => {
82
159
  actions.setDialogOpen(true);
83
160
  };
84
- // Fetch the usage log whenever the log dialog opens.
85
- useEffect(() => {
86
- if (!logOpen)
87
- return;
88
- let cancelled = false;
89
- void fetch('/token-quota/log', { headers: { accept: 'application/json' } }).then((response) => response.json(), () => null).then((data) => {
90
- if (!cancelled)
91
- actions.setLog(data);
92
- });
93
- return () => { cancelled = true; };
94
- }, [logOpen, actions]);
95
161
  const commitLimit = (row) => {
96
162
  const raw = drafts[row.key]?.trim();
97
163
  setDrafts((prev) => {
@@ -107,7 +173,7 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
107
173
  setLimit(row.key, parsed);
108
174
  };
109
175
  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) => {
176
+ 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
177
  const state = barStateOf(row);
112
178
  const pct = row.limit > 0 ? Math.min(100, Math.round((row.used / row.limit) * 100)) : 0;
113
179
  const barClass = state === 'over'
@@ -120,33 +186,33 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
120
186
  ? `${formatTokens(row.used)} / ${formatTokens(row.limit)}`
121
187
  : `${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
188
  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}`))) })] })] }) }))] }));
189
+ })] }), 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) => {
190
+ const base = monitored === null ? allModels.map(m => m.key) : monitored;
191
+ const next = new Set(base);
192
+ if (event.target.checked)
193
+ next.add(model.key);
194
+ else
195
+ next.delete(model.key);
196
+ // Auto-save on every change; all selected stores as null.
197
+ setMonitored(next.size === allModels.length ? null : [...next]);
198
+ } }), _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) => {
199
+ setReset({
200
+ offsetHours: Number(event.target.value),
201
+ hour: reset?.hour ?? 0,
202
+ minute: reset?.minute ?? 0,
203
+ });
204
+ }, 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) => {
205
+ setReset({
206
+ offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
207
+ hour: Number(event.target.value),
208
+ minute: reset?.minute ?? 0,
209
+ });
210
+ }, 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) => {
211
+ setReset({
212
+ offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
213
+ hour: reset?.hour ?? 0,
214
+ minute: Number(event.target.value),
215
+ });
216
+ }, 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} ${css.logDialog}`, style: logPos !== null ? { left: logPos.x, top: logPos.y, transform: 'none' } : undefined, children: [_jsxs("div", { className: css.logHeader, 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') })), _jsx("div", { className: css.logScroll, children: _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
217
  }
152
218
  //# 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.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },