@jxgame2020/dsh-token-quota 0.1.11 → 0.1.12

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
@@ -39,11 +39,18 @@ window.__ModuleLoader__.load({
39
39
  upgrade: null,
40
40
  upgradeDismissed: false,
41
41
  checkingUpdates: false,
42
+ lastCheckResult: "idle",
42
43
  dialogOpen: false,
43
44
  fullNotice: null,
44
45
  reset: null,
45
46
  logOpen: false,
46
- log: null
47
+ log: null,
48
+ logSearch: "",
49
+ logPage: 0,
50
+ logPageSize: 15,
51
+ logClearOpen: false,
52
+ logClearBefore: "",
53
+ logClearing: false
47
54
  }),
48
55
  actions: {
49
56
  setSnapshot: (d, snapshot) => {
@@ -76,6 +83,9 @@ window.__ModuleLoader__.load({
76
83
  setCheckingUpdates: (d, checking) => {
77
84
  d.checkingUpdates = checking;
78
85
  },
86
+ setLastCheckResult: (d, result) => {
87
+ d.lastCheckResult = result;
88
+ },
79
89
  setDialogOpen: (d, open) => {
80
90
  d.dialogOpen = open;
81
91
  },
@@ -90,6 +100,26 @@ window.__ModuleLoader__.load({
90
100
  },
91
101
  setLog: (d, log) => {
92
102
  d.log = log;
103
+ },
104
+ setLogSearch: (d, search) => {
105
+ d.logSearch = search;
106
+ d.logPage = 0;
107
+ },
108
+ setLogPage: (d, page) => {
109
+ d.logPage = page;
110
+ },
111
+ setLogPageSize: (d, size) => {
112
+ d.logPageSize = size;
113
+ d.logPage = 0;
114
+ },
115
+ setLogClearOpen: (d, open) => {
116
+ d.logClearOpen = open;
117
+ },
118
+ setLogClearBefore: (d, before) => {
119
+ d.logClearBefore = before;
120
+ },
121
+ setLogClearing: (d, clearing) => {
122
+ d.logClearing = clearing;
93
123
  }
94
124
  }
95
125
  });
@@ -136,7 +166,7 @@ window.__ModuleLoader__.load({
136
166
  }
137
167
  //#endregion
138
168
  //#region \0dsh-css:/Users/jxgame/Desktop/works/deepseek-harness-package/dsh-token-quota/packages/token-quota/src/client/TokenQuotaPanel.module.css.mjs
139
- 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);align-items:baseline;gap:6px;font-size:13px;font-weight:600;display:flex}.NPLLIq_version{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:10px;font-weight:500}.NPLLIq_versionNew{color:var(--dsw-alias-status-warning-foreground,#b56900);background:var(--dsw-alias-status-warning-background,#fff6d7);cursor:default;border-radius:4px;padding:1px 6px}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_upgradeBanner{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-divider-1,#0000001a);border-radius:8px;margin:6px 12px;padding:10px 30px 10px 10px;font-size:12px;position:relative}.NPLLIq_upgradeBannerText{margin-bottom:6px;font-weight:600}.NPLLIq_upgradeCommands{flex-direction:column;gap:4px;display:flex}.NPLLIq_upgradeCommandRow{align-items:center;gap:6px;display:flex}.NPLLIq_upgradeCommand{background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:nowrap;border-radius:4px;flex:1;min-width:0;padding:4px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow-x:auto}.NPLLIq_copyBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;flex:none;padding:3px 8px;font-size:11px}.NPLLIq_copyBtn:hover{background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_upgradeHint{color:var(--dsw-alias-label-secondary);margin-top:6px;font-size:11px}.NPLLIq_upgradeClose{width:20px;height:20px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;font-size:16px;line-height:1;position:absolute;top:4px;right:6px}.NPLLIq_upgradeClose:hover{background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary)}.NPLLIq_checkUpdatesRow{align-items:center;gap:8px;margin-top:8px;display:flex}.NPLLIq_checkBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;padding:4px 10px;font-size:12px}.NPLLIq_checkBtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_checkBtn:disabled{opacity:.6;cursor:default}.NPLLIq_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}";
169
+ 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);align-items:baseline;gap:6px;font-size:13px;font-weight:600;display:flex}.NPLLIq_version{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:10px;font-weight:500}.NPLLIq_versionNew{color:var(--dsw-alias-status-warning-foreground,#b56900);background:var(--dsw-alias-status-warning-background,#fff6d7);cursor:default;border-radius:4px;padding:1px 6px}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_upgradeBanner{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-divider-1,#0000001a);border-radius:8px;margin:6px 12px;padding:10px 30px 10px 10px;font-size:12px;position:relative}.NPLLIq_upgradeBannerText{margin-bottom:6px;font-weight:600}.NPLLIq_upgradeCommands{flex-direction:column;gap:4px;display:flex}.NPLLIq_upgradeCommandRow{align-items:center;gap:6px;display:flex}.NPLLIq_upgradeCommand{background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:nowrap;border-radius:4px;flex:1;min-width:0;padding:4px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow-x:auto}.NPLLIq_copyBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;flex:none;padding:3px 8px;font-size:11px}.NPLLIq_copyBtn:hover{background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_upgradeHint{color:var(--dsw-alias-label-secondary);margin-top:6px;font-size:11px}.NPLLIq_upgradeClose{width:20px;height:20px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;font-size:16px;line-height:1;position:absolute;top:4px;right:6px}.NPLLIq_upgradeClose:hover{background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary)}.NPLLIq_checkUpdatesRow{justify-content:space-between;align-items:center;gap:12px;display:flex}.NPLLIq_checkUpdatesLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}.NPLLIq_checkUpdatesLabel input{margin:0}.NPLLIq_checkBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-align:center;border-radius:4px;flex:none;min-width:72px;padding:4px 10px;font-size:12px}.NPLLIq_checkBtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_checkBtn:disabled{opacity:.6;cursor:default}.NPLLIq_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}.NPLLIq_logToolbar{flex:1;align-items:center;gap:6px;display:flex}.NPLLIq_logSearch{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);min-width:0;color:var(--dsw-alias-label-primary);border-radius:4px;flex:1;padding:3px 6px;font-size:11px}.NPLLIq_logSearch:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}.NPLLIq_logClearBtn{border:1px solid var(--dsw-alias-state-error-border,#b400004d);color:var(--dsw-alias-state-error-primary,#c00);cursor:pointer;white-space:nowrap;background:0 0;border-radius:4px;flex:none;padding:3px 8px;font-size:11px}.NPLLIq_logClearBtn:hover{background:var(--dsw-alias-state-error-background,#ff00000f)}.NPLLIq_logPager{border-top:1px solid var(--dsw-alias-divider-1,#00000014);background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);flex:none;justify-content:space-between;align-items:center;gap:8px;padding:6px 14px;font-size:11px;display:flex}.NPLLIq_logPagerInfo{white-space:nowrap}.NPLLIq_logPagerControls{align-items:center;gap:4px;display:flex}.NPLLIq_logPagerBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:24px;height:24px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;font-size:13px;line-height:1}.NPLLIq_logPagerBtn:disabled{opacity:.4;cursor:default}.NPLLIq_logPagerPages{text-align:center;min-width:50px;color:var(--dsw-alias-label-secondary)}.NPLLIq_logPageSizeSelect{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;padding:2px 4px;font-size:11px}.NPLLIq_dialogOverlay{z-index:100001;background:#0000004d;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.NPLLIq_logClearDialog{gap:0;width:320px;padding:16px}.NPLLIq_dialogBody{color:var(--dsw-alias-label-secondary);margin:6px 0 10px;font-size:12px;line-height:1.5}.NPLLIq_logClearDate{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:100%;color:var(--dsw-alias-label-primary);border-radius:6px;margin-bottom:12px;padding:6px 8px;font-size:13px}.NPLLIq_logClearDate:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}";
140
170
  const tagId = "@jxgame2020/dsh-token-quota/TokenQuotaPanel.module.css";
141
171
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
142
172
  const tag = document.createElement("style");
@@ -146,71 +176,85 @@ window.__ModuleLoader__.load({
146
176
  document.head.appendChild(tag);
147
177
  }
148
178
  var TokenQuotaPanel_module_css_default = {
149
- "monitorList": "NPLLIq_monitorList",
150
- "dialogClose": "NPLLIq_dialogClose",
151
- "controls": "NPLLIq_controls",
152
- "tabIcon": "NPLLIq_tabIcon",
153
- "logModelCol": "NPLLIq_logModelCol",
154
- "rowMeta": "NPLLIq_rowMeta",
155
- "fillOver": "NPLLIq_fillOver",
156
- "copyBtn": "NPLLIq_copyBtn",
157
- "checkBtn": "NPLLIq_checkBtn",
179
+ "logHeader": "NPLLIq_logHeader",
180
+ "title": "NPLLIq_title",
158
181
  "radioRow": "NPLLIq_radioRow",
159
- "headerText": "NPLLIq_headerText",
182
+ "logModelCol": "NPLLIq_logModelCol",
183
+ "logPagerInfo": "NPLLIq_logPagerInfo",
184
+ "upgradeClose": "NPLLIq_upgradeClose",
185
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
186
+ "subtitle": "NPLLIq_subtitle",
187
+ "logPagerControls": "NPLLIq_logPagerControls",
160
188
  "dialogBtn": "NPLLIq_dialogBtn",
161
- "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
162
- "currentBadge": "NPLLIq_currentBadge",
163
- "header": "NPLLIq_header",
189
+ "logPagerPages": "NPLLIq_logPagerPages",
190
+ "headerActions": "NPLLIq_headerActions",
164
191
  "monitorHint": "NPLLIq_monitorHint",
165
- "resetRow": "NPLLIq_resetRow",
166
- "subtitle": "NPLLIq_subtitle",
167
- "logTable": "NPLLIq_logTable",
168
- "selectBtn": "NPLLIq_selectBtn",
169
- "logHeader": "NPLLIq_logHeader",
192
+ "dialogTitle": "NPLLIq_dialogTitle",
193
+ "logDialog": "NPLLIq_logDialog",
194
+ "header": "NPLLIq_header",
170
195
  "dialog": "NPLLIq_dialog",
171
- "monitorRow": "NPLLIq_monitorRow",
172
196
  "tab": "NPLLIq_tab",
173
- "settingsBtn": "NPLLIq_settingsBtn",
174
- "upgradeBanner": "NPLLIq_upgradeBanner",
175
- "rowName": "NPLLIq_rowName",
197
+ "dialogHeader": "NPLLIq_dialogHeader",
198
+ "logToolbar": "NPLLIq_logToolbar",
176
199
  "dialogLabel": "NPLLIq_dialogLabel",
177
- "fillIdle": "NPLLIq_fillIdle",
178
- "dialogActions": "NPLLIq_dialogActions",
179
- "collapse": "NPLLIq_collapse",
180
- "panel": "NPLLIq_panel",
181
- "resetSelect": "NPLLIq_resetSelect",
182
- "fillWarn": "NPLLIq_fillWarn",
183
- "fullNotice": "NPLLIq_fullNotice",
184
- "monitorName": "NPLLIq_monitorName",
185
- "switch": "NPLLIq_switch",
186
- "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
187
- "versionNew": "NPLLIq_versionNew",
188
- "row": "NPLLIq_row",
189
- "input": "NPLLIq_input",
200
+ "logClearDate": "NPLLIq_logClearDate",
201
+ "upgradeBanner": "NPLLIq_upgradeBanner",
190
202
  "bar": "NPLLIq_bar",
191
- "noticeError": "NPLLIq_noticeError",
203
+ "row": "NPLLIq_row",
204
+ "copyBtn": "NPLLIq_copyBtn",
205
+ "dialogActions": "NPLLIq_dialogActions",
206
+ "logUsedCol": "NPLLIq_logUsedCol",
207
+ "monitorRow": "NPLLIq_monitorRow",
192
208
  "tabLabel": "NPLLIq_tabLabel",
193
- "dialogTitle": "NPLLIq_dialogTitle",
194
- "headerActions": "NPLLIq_headerActions",
195
- "upgradeCommands": "NPLLIq_upgradeCommands",
196
- "upgradeBannerText": "NPLLIq_upgradeBannerText",
197
- "upgradeHint": "NPLLIq_upgradeHint",
209
+ "monitorName": "NPLLIq_monitorName",
210
+ "logPageSizeSelect": "NPLLIq_logPageSizeSelect",
211
+ "dialogSection": "NPLLIq_dialogSection",
198
212
  "save": "NPLLIq_save",
213
+ "switch": "NPLLIq_switch",
214
+ "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
215
+ "tabIcon": "NPLLIq_tabIcon",
199
216
  "version": "NPLLIq_version",
217
+ "upgradeBannerText": "NPLLIq_upgradeBannerText",
218
+ "panel": "NPLLIq_panel",
219
+ "notice": "NPLLIq_notice",
220
+ "fillOver": "NPLLIq_fillOver",
221
+ "dialogOverlay": "NPLLIq_dialogOverlay",
222
+ "fullNotice": "NPLLIq_fullNotice",
223
+ "logDayCol": "NPLLIq_logDayCol",
200
224
  "rowHeader": "NPLLIq_rowHeader",
201
- "body": "NPLLIq_body",
202
- "upgradeCommand": "NPLLIq_upgradeCommand",
203
- "logDialog": "NPLLIq_logDialog",
204
- "title": "NPLLIq_title",
205
- "dialogHeader": "NPLLIq_dialogHeader",
206
- "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
225
+ "settingsBtn": "NPLLIq_settingsBtn",
207
226
  "gearBtn": "NPLLIq_gearBtn",
208
- "upgradeClose": "NPLLIq_upgradeClose",
227
+ "headerText": "NPLLIq_headerText",
228
+ "collapse": "NPLLIq_collapse",
229
+ "resetSelect": "NPLLIq_resetSelect",
230
+ "upgradeCommands": "NPLLIq_upgradeCommands",
231
+ "body": "NPLLIq_body",
232
+ "rowName": "NPLLIq_rowName",
233
+ "monitorList": "NPLLIq_monitorList",
209
234
  "logScroll": "NPLLIq_logScroll",
210
- "logUsedCol": "NPLLIq_logUsedCol",
211
- "notice": "NPLLIq_notice",
212
- "dialogSection": "NPLLIq_dialogSection",
213
- "logDayCol": "NPLLIq_logDayCol"
235
+ "input": "NPLLIq_input",
236
+ "resetRow": "NPLLIq_resetRow",
237
+ "logPagerBtn": "NPLLIq_logPagerBtn",
238
+ "logClearDialog": "NPLLIq_logClearDialog",
239
+ "logTable": "NPLLIq_logTable",
240
+ "upgradeHint": "NPLLIq_upgradeHint",
241
+ "rowMeta": "NPLLIq_rowMeta",
242
+ "fillIdle": "NPLLIq_fillIdle",
243
+ "logClearBtn": "NPLLIq_logClearBtn",
244
+ "logPager": "NPLLIq_logPager",
245
+ "versionNew": "NPLLIq_versionNew",
246
+ "dialogBody": "NPLLIq_dialogBody",
247
+ "noticeError": "NPLLIq_noticeError",
248
+ "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
249
+ "logSearch": "NPLLIq_logSearch",
250
+ "checkBtn": "NPLLIq_checkBtn",
251
+ "selectBtn": "NPLLIq_selectBtn",
252
+ "upgradeCommand": "NPLLIq_upgradeCommand",
253
+ "currentBadge": "NPLLIq_currentBadge",
254
+ "controls": "NPLLIq_controls",
255
+ "fillWarn": "NPLLIq_fillWarn",
256
+ "dialogClose": "NPLLIq_dialogClose",
257
+ "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel"
214
258
  };
215
259
  //#endregion
216
260
  //#region lib/types/client/TokenQuotaPanel.js
@@ -297,7 +341,7 @@ window.__ModuleLoader__.load({
297
341
  * @param props - composed slot props.
298
342
  * @returns the panel element tree.
299
343
  */
300
- function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, useStore, actions, useSessions }) {
344
+ function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, useStore, actions, useSessions }) {
301
345
  const [collapsed, setCollapsed] = (0, react.useState)(false);
302
346
  const [drafts, setDrafts] = (0, react.useState)({});
303
347
  const [editingKey, setEditingKey] = (0, react.useState)(null);
@@ -323,10 +367,17 @@ window.__ModuleLoader__.load({
323
367
  const upgrade = useStore((s) => s.upgrade);
324
368
  const upgradeDismissed = useStore((s) => s.upgradeDismissed);
325
369
  const checkingUpdates = useStore((s) => s.checkingUpdates);
370
+ const lastCheckResult = useStore((s) => s.lastCheckResult);
326
371
  const reset = useStore((s) => s.reset);
327
372
  const dialogOpen = useStore((s) => s.dialogOpen);
328
373
  const logOpen = useStore((s) => s.logOpen);
329
374
  const log = useStore((s) => s.log);
375
+ const logSearch = useStore((s) => s.logSearch);
376
+ const logPage = useStore((s) => s.logPage);
377
+ const logPageSize = useStore((s) => s.logPageSize);
378
+ const logClearOpen = useStore((s) => s.logClearOpen);
379
+ const logClearBefore = useStore((s) => s.logClearBefore);
380
+ const logClearing = useStore((s) => s.logClearing);
330
381
  const fullNotice = useStore((s) => s.fullNotice);
331
382
  const loading = useStore((s) => s.loading);
332
383
  const error = useStore((s) => s.error);
@@ -343,6 +394,22 @@ window.__ModuleLoader__.load({
343
394
  name: model.name
344
395
  }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
345
396
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
397
+ const filteredLogEntries = (0, react.useMemo)(() => {
398
+ if (log === null) return [];
399
+ const q = logSearch.trim().toLowerCase();
400
+ if (q === "") return log.entries;
401
+ return log.entries.filter((e) => e.key.toLowerCase().includes(q) || e.provider.toLowerCase().includes(q) || e.model.toLowerCase().includes(q));
402
+ }, [log, logSearch]);
403
+ const logTotalPages = Math.max(1, Math.ceil(filteredLogEntries.length / logPageSize));
404
+ const logCurrentPage = Math.min(logPage, logTotalPages - 1);
405
+ const logPageEntries = (0, react.useMemo)(() => {
406
+ const start = logCurrentPage * logPageSize;
407
+ return filteredLogEntries.slice(start, start + logPageSize);
408
+ }, [
409
+ filteredLogEntries,
410
+ logCurrentPage,
411
+ logPageSize
412
+ ]);
346
413
  /** Copy text to the clipboard (clipboard API with execCommand fallback). */
347
414
  const copyToClipboard = async (text) => {
348
415
  try {
@@ -433,13 +500,13 @@ window.__ModuleLoader__.load({
433
500
  title: t("upgradeAvailableHint"),
434
501
  children: [
435
502
  "v",
436
- "0.1.11",
503
+ "0.1.12",
437
504
  " → v",
438
505
  upgrade.latestVersion
439
506
  ]
440
507
  }) : (0, react_jsx_runtime.jsxs)("span", {
441
508
  className: TokenQuotaPanel_module_css_default.version,
442
- children: ["v", "0.1.11"]
509
+ children: ["v", "0.1.12"]
443
510
  })]
444
511
  }), (0, react_jsx_runtime.jsx)("div", {
445
512
  className: TokenQuotaPanel_module_css_default.subtitle,
@@ -760,27 +827,27 @@ window.__ModuleLoader__.load({
760
827
  })
761
828
  ]
762
829
  }),
763
- (0, react_jsx_runtime.jsxs)("div", {
830
+ (0, react_jsx_runtime.jsx)("div", {
764
831
  className: TokenQuotaPanel_module_css_default.dialogSection,
765
- children: [(0, react_jsx_runtime.jsxs)("label", {
766
- className: TokenQuotaPanel_module_css_default.radioRow,
767
- children: [(0, react_jsx_runtime.jsx)("input", {
768
- type: "checkbox",
769
- checked: checkUpdates,
770
- onChange: (event) => {
771
- setCheckUpdates(event.target.checked);
772
- }
773
- }), (0, react_jsx_runtime.jsx)("span", { children: t("checkUpdatesLabel") })]
774
- }), (0, react_jsx_runtime.jsx)("div", {
832
+ children: (0, react_jsx_runtime.jsxs)("div", {
775
833
  className: TokenQuotaPanel_module_css_default.checkUpdatesRow,
776
- children: (0, react_jsx_runtime.jsx)("button", {
834
+ children: [(0, react_jsx_runtime.jsxs)("label", {
835
+ className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
836
+ children: [(0, react_jsx_runtime.jsx)("input", {
837
+ type: "checkbox",
838
+ checked: checkUpdates,
839
+ onChange: (event) => {
840
+ setCheckUpdates(event.target.checked);
841
+ }
842
+ }), (0, react_jsx_runtime.jsx)("span", { children: t("checkUpdatesLabel") })]
843
+ }), (0, react_jsx_runtime.jsx)("button", {
777
844
  type: "button",
778
845
  className: TokenQuotaPanel_module_css_default.checkBtn,
779
846
  disabled: checkingUpdates,
780
847
  onClick: checkUpdatesNow,
781
- children: checkingUpdates ? t("checkingUpdates") : t("checkUpdatesNow")
782
- })
783
- })]
848
+ children: checkingUpdates ? t("checkingUpdates") : lastCheckResult === "up-to-date" ? t("checkUpToDate") : lastCheckResult === "error" ? t("checkFailed") : t("checkUpdatesNow")
849
+ })]
850
+ })
784
851
  })
785
852
  ]
786
853
  }),
@@ -798,22 +865,47 @@ window.__ModuleLoader__.load({
798
865
  onPointerDown: (event) => {
799
866
  beginDrag(event, logRef.current, setLogPos);
800
867
  },
801
- children: [(0, react_jsx_runtime.jsx)("div", {
802
- className: TokenQuotaPanel_module_css_default.dialogTitle,
803
- children: t("logsTitle")
804
- }), (0, react_jsx_runtime.jsx)("button", {
805
- type: "button",
806
- className: TokenQuotaPanel_module_css_default.dialogClose,
807
- title: t("close"),
808
- onClick: () => {
809
- actions.setLogOpen(false);
810
- },
811
- children: "×"
812
- })]
868
+ children: [
869
+ (0, react_jsx_runtime.jsx)("div", {
870
+ className: TokenQuotaPanel_module_css_default.dialogTitle,
871
+ children: t("logsTitle")
872
+ }),
873
+ (0, react_jsx_runtime.jsxs)("div", {
874
+ className: TokenQuotaPanel_module_css_default.logToolbar,
875
+ onPointerDown: (event) => {
876
+ event.stopPropagation();
877
+ },
878
+ children: [(0, react_jsx_runtime.jsx)("input", {
879
+ type: "search",
880
+ className: TokenQuotaPanel_module_css_default.logSearch,
881
+ placeholder: t("logSearchPlaceholder"),
882
+ value: logSearch,
883
+ onChange: (event) => {
884
+ actions.setLogSearch(event.target.value);
885
+ }
886
+ }), (0, react_jsx_runtime.jsx)("button", {
887
+ type: "button",
888
+ className: TokenQuotaPanel_module_css_default.logClearBtn,
889
+ onClick: () => {
890
+ actions.setLogClearOpen(true);
891
+ },
892
+ children: t("logClear")
893
+ })]
894
+ }),
895
+ (0, react_jsx_runtime.jsx)("button", {
896
+ type: "button",
897
+ className: TokenQuotaPanel_module_css_default.dialogClose,
898
+ title: t("close"),
899
+ onClick: () => {
900
+ actions.setLogOpen(false);
901
+ },
902
+ children: "×"
903
+ })
904
+ ]
813
905
  }),
814
- log !== null && log.entries.length === 0 && (0, react_jsx_runtime.jsx)("div", {
906
+ log !== null && filteredLogEntries.length === 0 && (0, react_jsx_runtime.jsx)("div", {
815
907
  className: TokenQuotaPanel_module_css_default.notice,
816
- children: t("logEmpty")
908
+ children: logSearch.trim() !== "" ? t("logNoMatch") : t("logEmpty")
817
909
  }),
818
910
  (0, react_jsx_runtime.jsx)("div", {
819
911
  className: TokenQuotaPanel_module_css_default.logScroll,
@@ -826,7 +918,7 @@ window.__ModuleLoader__.load({
826
918
  className: TokenQuotaPanel_module_css_default.logUsedCol,
827
919
  children: t("logUsed")
828
920
  })
829
- ] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: log?.entries.map((entry) => (0, react_jsx_runtime.jsxs)("tr", { children: [
921
+ ] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: logPageEntries.map((entry) => (0, react_jsx_runtime.jsxs)("tr", { children: [
830
922
  (0, react_jsx_runtime.jsx)("td", {
831
923
  className: TokenQuotaPanel_module_css_default.logDayCol,
832
924
  children: entry.day
@@ -842,8 +934,105 @@ window.__ModuleLoader__.load({
842
934
  })
843
935
  ] }, `${entry.day}/${entry.key}`)) })]
844
936
  })
937
+ }),
938
+ (0, react_jsx_runtime.jsxs)("div", {
939
+ className: TokenQuotaPanel_module_css_default.logPager,
940
+ children: [(0, react_jsx_runtime.jsx)("span", {
941
+ className: TokenQuotaPanel_module_css_default.logPagerInfo,
942
+ children: t("logPagerInfo").replace("{from}", String(logCurrentPage * logPageSize + 1)).replace("{to}", String(Math.min((logCurrentPage + 1) * logPageSize, filteredLogEntries.length))).replace("{total}", String(filteredLogEntries.length))
943
+ }), (0, react_jsx_runtime.jsxs)("div", {
944
+ className: TokenQuotaPanel_module_css_default.logPagerControls,
945
+ children: [
946
+ (0, react_jsx_runtime.jsx)("button", {
947
+ type: "button",
948
+ className: TokenQuotaPanel_module_css_default.logPagerBtn,
949
+ disabled: logCurrentPage <= 0,
950
+ onClick: () => {
951
+ actions.setLogPage(logCurrentPage - 1);
952
+ },
953
+ children: "‹"
954
+ }),
955
+ (0, react_jsx_runtime.jsxs)("span", {
956
+ className: TokenQuotaPanel_module_css_default.logPagerPages,
957
+ children: [
958
+ logCurrentPage + 1,
959
+ " / ",
960
+ logTotalPages
961
+ ]
962
+ }),
963
+ (0, react_jsx_runtime.jsx)("button", {
964
+ type: "button",
965
+ className: TokenQuotaPanel_module_css_default.logPagerBtn,
966
+ disabled: logCurrentPage >= logTotalPages - 1,
967
+ onClick: () => {
968
+ actions.setLogPage(logCurrentPage + 1);
969
+ },
970
+ children: "›"
971
+ }),
972
+ (0, react_jsx_runtime.jsx)("select", {
973
+ className: TokenQuotaPanel_module_css_default.logPageSizeSelect,
974
+ value: logPageSize,
975
+ onChange: (event) => {
976
+ actions.setLogPageSize(Number(event.target.value));
977
+ },
978
+ children: [
979
+ 10,
980
+ 15,
981
+ 20,
982
+ 50,
983
+ 100
984
+ ].map((size) => (0, react_jsx_runtime.jsxs)("option", {
985
+ value: size,
986
+ children: [size, t("logPerPage")]
987
+ }, size))
988
+ })
989
+ ]
990
+ })]
845
991
  })
846
992
  ]
993
+ }),
994
+ logClearOpen && (0, react_jsx_runtime.jsx)("div", {
995
+ className: TokenQuotaPanel_module_css_default.dialogOverlay,
996
+ children: (0, react_jsx_runtime.jsxs)("div", {
997
+ className: `${TokenQuotaPanel_module_css_default.dialog} ${TokenQuotaPanel_module_css_default.logClearDialog}`,
998
+ children: [
999
+ (0, react_jsx_runtime.jsx)("div", {
1000
+ className: TokenQuotaPanel_module_css_default.dialogTitle,
1001
+ children: t("logClearTitle")
1002
+ }),
1003
+ (0, react_jsx_runtime.jsx)("div", {
1004
+ className: TokenQuotaPanel_module_css_default.dialogBody,
1005
+ children: t("logClearHint")
1006
+ }),
1007
+ (0, react_jsx_runtime.jsx)("input", {
1008
+ type: "date",
1009
+ className: TokenQuotaPanel_module_css_default.logClearDate,
1010
+ value: logClearBefore,
1011
+ onChange: (event) => {
1012
+ actions.setLogClearBefore(event.target.value);
1013
+ }
1014
+ }),
1015
+ (0, react_jsx_runtime.jsxs)("div", {
1016
+ className: TokenQuotaPanel_module_css_default.dialogActions,
1017
+ children: [(0, react_jsx_runtime.jsx)("button", {
1018
+ type: "button",
1019
+ className: TokenQuotaPanel_module_css_default.checkBtn,
1020
+ disabled: logClearBefore === "" || logClearing,
1021
+ onClick: () => {
1022
+ clearLogBefore(logClearBefore);
1023
+ },
1024
+ children: logClearing ? t("logClearing") : t("logClearConfirm")
1025
+ }), (0, react_jsx_runtime.jsx)("button", {
1026
+ type: "button",
1027
+ className: TokenQuotaPanel_module_css_default.checkBtn,
1028
+ onClick: () => {
1029
+ actions.setLogClearOpen(false);
1030
+ },
1031
+ children: t("logClearCancel")
1032
+ })]
1033
+ })
1034
+ ]
1035
+ })
847
1036
  })
848
1037
  ]
849
1038
  });
@@ -893,6 +1082,16 @@ window.__ModuleLoader__.load({
893
1082
  logs: "日志",
894
1083
  logsTitle: "用量日志",
895
1084
  logEmpty: "暂无记录",
1085
+ logNoMatch: "无匹配记录",
1086
+ logSearchPlaceholder: "搜索模型…",
1087
+ logClear: "清除",
1088
+ logClearTitle: "清除历史日志",
1089
+ logClearHint: "将删除所选日期之前的所有记录(不含当日)",
1090
+ logClearConfirm: "确认清除",
1091
+ logClearCancel: "取消",
1092
+ logClearing: "清除中…",
1093
+ logPagerInfo: "第 {from}-{to} 条 / 共 {total} 条",
1094
+ logPerPage: "条/页",
896
1095
  logDay: "日期",
897
1096
  logModel: "模型",
898
1097
  logUsed: "用量",
@@ -906,7 +1105,9 @@ window.__ModuleLoader__.load({
906
1105
  upgradeHint: "升级后请重启 dsh web 并刷新浏览器页面",
907
1106
  checkUpdatesLabel: "自动检查更新",
908
1107
  checkUpdatesNow: "立即检查",
909
- checkingUpdates: "检查中…"
1108
+ checkingUpdates: "检查中…",
1109
+ checkUpToDate: "已是最新",
1110
+ checkFailed: "检查失败"
910
1111
  };
911
1112
  /** English copy. */
912
1113
  const en = {
@@ -945,6 +1146,16 @@ window.__ModuleLoader__.load({
945
1146
  logs: "Logs",
946
1147
  logsTitle: "Usage Log",
947
1148
  logEmpty: "No records",
1149
+ logNoMatch: "No matching records",
1150
+ logSearchPlaceholder: "Search model…",
1151
+ logClear: "Clear",
1152
+ logClearTitle: "Clear historical log",
1153
+ logClearHint: "This deletes all records before the selected date (the date itself is kept).",
1154
+ logClearConfirm: "Confirm",
1155
+ logClearCancel: "Cancel",
1156
+ logClearing: "Clearing…",
1157
+ logPagerInfo: "{from}-{to} of {total}",
1158
+ logPerPage: "/page",
948
1159
  logDay: "Day",
949
1160
  logModel: "Model",
950
1161
  logUsed: "Used",
@@ -958,7 +1169,9 @@ window.__ModuleLoader__.load({
958
1169
  upgradeHint: "After upgrading, restart dsh web and refresh the browser",
959
1170
  checkUpdatesLabel: "Check for updates automatically",
960
1171
  checkUpdatesNow: "Check now",
961
- checkingUpdates: "Checking…"
1172
+ checkingUpdates: "Checking…",
1173
+ checkUpToDate: "Up to date",
1174
+ checkFailed: "Check failed"
962
1175
  };
963
1176
  //#endregion
964
1177
  //#region lib/types/client/index.js
@@ -1163,21 +1376,28 @@ window.__ModuleLoader__.load({
1163
1376
  /** Ask the Host to check the registry right now, then refresh the snapshot. */
1164
1377
  const checkUpdatesNow = () => {
1165
1378
  bound?.setCheckingUpdates(true);
1379
+ bound?.setLastCheckResult("idle");
1166
1380
  fetch("/token-quota/check-updates", { method: "POST" }).then((response) => {
1167
1381
  if (!response.ok) {
1168
1382
  bound?.setError(`check-updates route: ${String(response.status)}`);
1383
+ bound?.setLastCheckResult("error");
1169
1384
  return;
1170
1385
  }
1171
1386
  return response.json();
1172
1387
  }, () => {
1173
1388
  bound?.setError("update check failed");
1389
+ bound?.setLastCheckResult("error");
1174
1390
  }).then((snapshot) => {
1175
1391
  if (snapshot !== void 0) {
1176
1392
  bound?.setSnapshot(snapshot);
1177
1393
  bound?.setUpgrade(snapshot.upgrade ?? null);
1178
1394
  actOnFull(snapshot);
1395
+ if (snapshot.upgrade === null) bound?.setLastCheckResult("up-to-date");
1179
1396
  }
1180
1397
  bound?.setCheckingUpdates(false);
1398
+ setTimeout(() => {
1399
+ bound?.setLastCheckResult("idle");
1400
+ }, 2500);
1181
1401
  });
1182
1402
  };
1183
1403
  const setReset = (reset) => {
@@ -1201,6 +1421,27 @@ window.__ModuleLoader__.load({
1201
1421
  bound?.setError("model switch failed");
1202
1422
  });
1203
1423
  };
1424
+ /** Ask the Host to clear log entries before a given date, then refresh. */
1425
+ const clearLogBefore = (before) => {
1426
+ bound?.setLogClearing(true);
1427
+ fetch("/token-quota/clear-log", {
1428
+ method: "POST",
1429
+ headers: { "content-type": "application/json" },
1430
+ body: JSON.stringify({ before })
1431
+ }).then((response) => {
1432
+ if (!response.ok) {
1433
+ bound?.setError(`clear-log route: ${String(response.status)}`);
1434
+ return;
1435
+ }
1436
+ return response.json();
1437
+ }, () => {
1438
+ bound?.setError("clear log failed");
1439
+ }).then((log) => {
1440
+ if (log !== void 0) bound?.setLog(log);
1441
+ bound?.setLogClearing(false);
1442
+ bound?.setLogClearOpen(false);
1443
+ });
1444
+ };
1204
1445
  const injected = (actions) => {
1205
1446
  bound = actions;
1206
1447
  return {
@@ -1211,7 +1452,8 @@ window.__ModuleLoader__.load({
1211
1452
  setOnFull,
1212
1453
  setReset,
1213
1454
  setCheckUpdates,
1214
- checkUpdatesNow
1455
+ checkUpdatesNow,
1456
+ clearLogBefore
1215
1457
  };
1216
1458
  };
1217
1459
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
package/lib/index.js CHANGED
@@ -243,6 +243,8 @@ var TokenQuotaService = class TokenQuotaService extends Service {
243
243
  disposeRouteLog;
244
244
  /** Disposer for the optional manual-check route (`POST /token-quota/check-updates`). */
245
245
  disposeRouteCheck;
246
+ /** Disposer for the optional clear-log route (`POST /token-quota/clear-log`). */
247
+ disposeRouteClear;
246
248
  /** Disposer for the webServer-arrival watcher when the service mounts later. */
247
249
  disposeRouteWatcher;
248
250
  /**
@@ -353,6 +355,41 @@ var TokenQuotaService = class TokenQuotaService extends Service {
353
355
  });
354
356
  }
355
357
  });
358
+ this.disposeRouteClear = server.register({
359
+ kind: "exact",
360
+ path: "/token-quota/clear-log",
361
+ handler: (req, res) => {
362
+ if (req.method !== "POST") {
363
+ res.writeHead(405);
364
+ res.end();
365
+ return;
366
+ }
367
+ const chunks = [];
368
+ req.on("data", (chunk) => {
369
+ chunks.push(Buffer.from(chunk));
370
+ });
371
+ req.on("end", () => {
372
+ let before = "";
373
+ try {
374
+ const raw = Buffer.concat(chunks).toString("utf8");
375
+ if (raw.length > 0) {
376
+ const payload = JSON.parse(raw);
377
+ before = typeof payload.before === "string" ? payload.before : "";
378
+ }
379
+ } catch {
380
+ res.writeHead(400, { "content-type": "application/json" });
381
+ res.end(JSON.stringify({ error: "bad payload" }));
382
+ return;
383
+ }
384
+ const updated = this.clearLogBefore(before);
385
+ res.writeHead(200, {
386
+ "content-type": "application/json",
387
+ "cache-control": "no-store"
388
+ });
389
+ res.end(JSON.stringify(updated));
390
+ });
391
+ }
392
+ });
356
393
  }
357
394
  /** Query the npm registry and rebuild the cached upgrade info (coalesced). */
358
395
  refreshUpgrade() {
@@ -367,7 +404,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
367
404
  return this.upgradeCheck;
368
405
  }
369
406
  async doRefreshUpgrade() {
370
- const current = "0.1.11";
407
+ const current = "0.1.12";
371
408
  let latest;
372
409
  try {
373
410
  const response = await fetch(REGISTRY_LATEST_URL, {
@@ -442,6 +479,19 @@ var TokenQuotaService = class TokenQuotaService extends Service {
442
479
  entries.sort((left, right) => right.day.localeCompare(left.day) || left.key.localeCompare(right.key));
443
480
  return { entries };
444
481
  }
482
+ /**
483
+ * Remove all historical cycles whose day portion strictly precedes `before`
484
+ * (format `YYYY-MM-DD`). The current cycle is never cleared. Returns the
485
+ * trimmed log so the client can refresh immediately.
486
+ */
487
+ clearLogBefore(before) {
488
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(before)) return this.readLog();
489
+ const kept = {};
490
+ for (const [cycle, usageByKey] of Object.entries(this.history)) if (cycle.slice(0, 10) >= before) kept[cycle] = usageByKey;
491
+ this.history = kept;
492
+ this.scheduleWrite();
493
+ return this.readLog();
494
+ }
445
495
  /** Read the current snapshot (also used by tests and inspection). */
446
496
  readSnapshot() {
447
497
  this.rollCycleIfNeeded();
@@ -644,6 +694,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
644
694
  if (this.disposeRoute !== void 0) this.disposeRoute();
645
695
  if (this.disposeRouteLog !== void 0) this.disposeRouteLog();
646
696
  if (this.disposeRouteCheck !== void 0) this.disposeRouteCheck();
697
+ if (this.disposeRouteClear !== void 0) this.disposeRouteClear();
647
698
  this.flush();
648
699
  }
649
700
  safeSplit(key) {
@@ -20,6 +20,8 @@ export interface TokenQuotaPanelInjected {
20
20
  setCheckUpdates: (enabled: boolean) => void;
21
21
  /** Ask the Host to check the registry right now, then refresh the snapshot. */
22
22
  checkUpdatesNow: () => void;
23
+ /** Ask the Host to clear log entries before a date, then refresh the log. */
24
+ clearLogBefore: (before: string) => void;
23
25
  }
24
26
  /** Full component props: runtime + store + locale + injected face. */
25
27
  export type TokenQuotaPanelComponentProps = PropsRuntime<'shell.overlay'> & PropsStore<ReturnType<typeof createTokenQuotaPanelStore>> & PropsLocale<'token-quota'> & TokenQuotaPanelInjected;
@@ -28,5 +30,5 @@ export type TokenQuotaPanelComponentProps = PropsRuntime<'shell.overlay'> & Prop
28
30
  * @param props - composed slot props.
29
31
  * @returns the panel element tree.
30
32
  */
31
- export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
33
+ export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
32
34
  //# sourceMappingURL=TokenQuotaPanel.d.ts.map
@@ -86,7 +86,7 @@ function loadPanelPos() {
86
86
  * @param props - composed slot props.
87
87
  * @returns the panel element tree.
88
88
  */
89
- export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, useStore, actions, useSessions, }) {
89
+ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, useStore, actions, useSessions, }) {
90
90
  const [collapsed, setCollapsed] = useState(false);
91
91
  const [drafts, setDrafts] = useState({});
92
92
  const [editingKey, setEditingKey] = useState(null);
@@ -117,10 +117,17 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
117
117
  const upgrade = useStore(s => s.upgrade);
118
118
  const upgradeDismissed = useStore(s => s.upgradeDismissed);
119
119
  const checkingUpdates = useStore(s => s.checkingUpdates);
120
+ const lastCheckResult = useStore(s => s.lastCheckResult);
120
121
  const reset = useStore(s => s.reset);
121
122
  const dialogOpen = useStore(s => s.dialogOpen);
122
123
  const logOpen = useStore(s => s.logOpen);
123
124
  const log = useStore(s => s.log);
125
+ const logSearch = useStore(s => s.logSearch);
126
+ const logPage = useStore(s => s.logPage);
127
+ const logPageSize = useStore(s => s.logPageSize);
128
+ const logClearOpen = useStore(s => s.logClearOpen);
129
+ const logClearBefore = useStore(s => s.logClearBefore);
130
+ const logClearing = useStore(s => s.logClearing);
124
131
  const fullNotice = useStore(s => s.fullNotice);
125
132
  const loading = useStore(s => s.loading);
126
133
  const error = useStore(s => s.error);
@@ -136,6 +143,23 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
136
143
  name: model.name,
137
144
  }))).sort((a, b) => a.key.localeCompare(b.key)), [groups]);
138
145
  const isMonitoredKey = (key) => monitored === null || monitored.includes(key);
146
+ // Log dialog: filter by search, then paginate.
147
+ const filteredLogEntries = useMemo(() => {
148
+ if (log === null)
149
+ return [];
150
+ const q = logSearch.trim().toLowerCase();
151
+ if (q === '')
152
+ return log.entries;
153
+ return log.entries.filter(e => e.key.toLowerCase().includes(q)
154
+ || e.provider.toLowerCase().includes(q)
155
+ || e.model.toLowerCase().includes(q));
156
+ }, [log, logSearch]);
157
+ const logTotalPages = Math.max(1, Math.ceil(filteredLogEntries.length / logPageSize));
158
+ const logCurrentPage = Math.min(logPage, logTotalPages - 1);
159
+ const logPageEntries = useMemo(() => {
160
+ const start = logCurrentPage * logPageSize;
161
+ return filteredLogEntries.slice(start, start + logPageSize);
162
+ }, [filteredLogEntries, logCurrentPage, logPageSize]);
139
163
  /** Copy text to the clipboard (clipboard API with execCommand fallback). */
140
164
  const copyToClipboard = async (text) => {
141
165
  try {
@@ -237,6 +261,15 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
237
261
  hour: reset?.hour ?? 0,
238
262
  minute: Number(event.target.value),
239
263
  });
240
- }, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.radioRow, children: [_jsx("input", { type: "checkbox", checked: checkUpdates, onChange: (event) => { setCheckUpdates(event.target.checked); } }), _jsx("span", { children: t('checkUpdatesLabel') })] }), _jsx("div", { className: css.checkUpdatesRow, children: _jsx("button", { type: "button", className: css.checkBtn, disabled: checkingUpdates, onClick: checkUpdatesNow, children: checkingUpdates ? t('checkingUpdates') : t('checkUpdatesNow') }) })] })] })), 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}`))) })] }) })] }))] }));
264
+ }, children: Array.from({ length: 12 }, (_, i) => i * 5).map(minute => (_jsxs("option", { value: minute, children: [String(minute).padStart(2, '0'), " \u5206"] }, minute))) })] })] }), _jsx("div", { className: css.dialogSection, children: _jsxs("div", { className: css.checkUpdatesRow, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: checkUpdates, onChange: (event) => { setCheckUpdates(event.target.checked); } }), _jsx("span", { children: t('checkUpdatesLabel') })] }), _jsx("button", { type: "button", className: css.checkBtn, disabled: checkingUpdates, onClick: checkUpdatesNow, children: checkingUpdates
265
+ ? t('checkingUpdates')
266
+ : lastCheckResult === 'up-to-date'
267
+ ? t('checkUpToDate')
268
+ : lastCheckResult === 'error'
269
+ ? t('checkFailed')
270
+ : t('checkUpdatesNow') })] }) })] })), 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') }), _jsxs("div", { className: css.logToolbar, onPointerDown: (event) => { event.stopPropagation(); }, children: [_jsx("input", { type: "search", className: css.logSearch, placeholder: t('logSearchPlaceholder'), value: logSearch, onChange: (event) => { actions.setLogSearch(event.target.value); } }), _jsx("button", { type: "button", className: css.logClearBtn, onClick: () => { actions.setLogClearOpen(true); }, children: t('logClear') })] }), _jsx("button", { type: "button", className: css.dialogClose, title: t('close'), onClick: () => { actions.setLogOpen(false); }, children: "\u00D7" })] }), log !== null && filteredLogEntries.length === 0 && (_jsx("div", { className: css.notice, children: logSearch.trim() !== '' ? t('logNoMatch') : 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: logPageEntries.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}`))) })] }) }), _jsxs("div", { className: css.logPager, children: [_jsx("span", { className: css.logPagerInfo, children: t('logPagerInfo')
271
+ .replace('{from}', String(logCurrentPage * logPageSize + 1))
272
+ .replace('{to}', String(Math.min((logCurrentPage + 1) * logPageSize, filteredLogEntries.length)))
273
+ .replace('{total}', String(filteredLogEntries.length)) }), _jsxs("div", { className: css.logPagerControls, children: [_jsx("button", { type: "button", className: css.logPagerBtn, disabled: logCurrentPage <= 0, onClick: () => { actions.setLogPage(logCurrentPage - 1); }, children: "\u2039" }), _jsxs("span", { className: css.logPagerPages, children: [logCurrentPage + 1, " / ", logTotalPages] }), _jsx("button", { type: "button", className: css.logPagerBtn, disabled: logCurrentPage >= logTotalPages - 1, onClick: () => { actions.setLogPage(logCurrentPage + 1); }, children: "\u203A" }), _jsx("select", { className: css.logPageSizeSelect, value: logPageSize, onChange: (event) => { actions.setLogPageSize(Number(event.target.value)); }, children: [10, 15, 20, 50, 100].map(size => (_jsxs("option", { value: size, children: [size, t('logPerPage')] }, size))) })] })] })] })), logClearOpen && (_jsx("div", { className: css.dialogOverlay, children: _jsxs("div", { className: `${css.dialog} ${css.logClearDialog}`, children: [_jsx("div", { className: css.dialogTitle, children: t('logClearTitle') }), _jsx("div", { className: css.dialogBody, children: t('logClearHint') }), _jsx("input", { type: "date", className: css.logClearDate, value: logClearBefore, onChange: (event) => { actions.setLogClearBefore(event.target.value); } }), _jsxs("div", { className: css.dialogActions, children: [_jsx("button", { type: "button", className: css.checkBtn, disabled: logClearBefore === '' || logClearing, onClick: () => { clearLogBefore(logClearBefore); }, children: logClearing ? t('logClearing') : t('logClearConfirm') }), _jsx("button", { type: "button", className: css.checkBtn, onClick: () => { actions.setLogClearOpen(false); }, children: t('logClearCancel') })] })] }) }))] }));
241
274
  }
242
275
  //# sourceMappingURL=TokenQuotaPanel.js.map
@@ -236,21 +236,30 @@ export function apply(ctx) {
236
236
  /** Ask the Host to check the registry right now, then refresh the snapshot. */
237
237
  const checkUpdatesNow = () => {
238
238
  bound?.setCheckingUpdates(true);
239
+ bound?.setLastCheckResult('idle');
239
240
  void fetch('/token-quota/check-updates', { method: 'POST' }).then((response) => {
240
241
  if (!response.ok) {
241
242
  bound?.setError(`check-updates route: ${String(response.status)}`);
243
+ bound?.setLastCheckResult('error');
242
244
  return undefined;
243
245
  }
244
246
  return response.json();
245
247
  }, () => {
246
248
  bound?.setError('update check failed');
249
+ bound?.setLastCheckResult('error');
247
250
  }).then((snapshot) => {
248
251
  if (snapshot !== undefined) {
249
252
  bound?.setSnapshot(snapshot);
250
253
  bound?.setUpgrade(snapshot.upgrade ?? null);
251
254
  actOnFull(snapshot);
255
+ if (snapshot.upgrade === null) {
256
+ bound?.setLastCheckResult('up-to-date');
257
+ }
252
258
  }
253
259
  bound?.setCheckingUpdates(false);
260
+ // Clear the transient result hint after a short delay so the button
261
+ // returns to its idle label.
262
+ setTimeout(() => { bound?.setLastCheckResult('idle'); }, 2500);
254
263
  });
255
264
  };
256
265
  const setReset = (reset) => {
@@ -276,11 +285,33 @@ export function apply(ctx) {
276
285
  bound?.setError('model switch failed');
277
286
  });
278
287
  };
288
+ /** Ask the Host to clear log entries before a given date, then refresh. */
289
+ const clearLogBefore = (before) => {
290
+ bound?.setLogClearing(true);
291
+ void fetch('/token-quota/clear-log', {
292
+ method: 'POST',
293
+ headers: { 'content-type': 'application/json' },
294
+ body: JSON.stringify({ before }),
295
+ }).then((response) => {
296
+ if (!response.ok) {
297
+ bound?.setError(`clear-log route: ${String(response.status)}`);
298
+ return undefined;
299
+ }
300
+ return response.json();
301
+ }, () => {
302
+ bound?.setError('clear log failed');
303
+ }).then((log) => {
304
+ if (log !== undefined)
305
+ bound?.setLog(log);
306
+ bound?.setLogClearing(false);
307
+ bound?.setLogClearOpen(false);
308
+ });
309
+ };
279
310
  const injected = (actions) => {
280
311
  bound = actions;
281
312
  return {
282
313
  load, setLimit, selectModel, setMonitored, setOnFull, setReset,
283
- setCheckUpdates, checkUpdatesNow,
314
+ setCheckUpdates, checkUpdatesNow, clearLogBefore,
284
315
  };
285
316
  };
286
317
  ctx.slots.inject('shell.overlay', () => ctx.slots.register({
@@ -41,6 +41,16 @@ export declare const zh: {
41
41
  logs: string;
42
42
  logsTitle: string;
43
43
  logEmpty: string;
44
+ logNoMatch: string;
45
+ logSearchPlaceholder: string;
46
+ logClear: string;
47
+ logClearTitle: string;
48
+ logClearHint: string;
49
+ logClearConfirm: string;
50
+ logClearCancel: string;
51
+ logClearing: string;
52
+ logPagerInfo: string;
53
+ logPerPage: string;
44
54
  logDay: string;
45
55
  logModel: string;
46
56
  logUsed: string;
@@ -55,6 +65,8 @@ export declare const zh: {
55
65
  checkUpdatesLabel: string;
56
66
  checkUpdatesNow: string;
57
67
  checkingUpdates: string;
68
+ checkUpToDate: string;
69
+ checkFailed: string;
58
70
  };
59
71
  /** Dictionary keys owned by the token-quota panel (a string-literal union). */
60
72
  export type TokenQuotaKey = keyof typeof zh;
@@ -95,6 +107,16 @@ export declare const en: {
95
107
  logs: string;
96
108
  logsTitle: string;
97
109
  logEmpty: string;
110
+ logNoMatch: string;
111
+ logSearchPlaceholder: string;
112
+ logClear: string;
113
+ logClearTitle: string;
114
+ logClearHint: string;
115
+ logClearConfirm: string;
116
+ logClearCancel: string;
117
+ logClearing: string;
118
+ logPagerInfo: string;
119
+ logPerPage: string;
98
120
  logDay: string;
99
121
  logModel: string;
100
122
  logUsed: string;
@@ -109,5 +131,7 @@ export declare const en: {
109
131
  checkUpdatesLabel: string;
110
132
  checkUpdatesNow: string;
111
133
  checkingUpdates: string;
134
+ checkUpToDate: string;
135
+ checkFailed: string;
112
136
  };
113
137
  //# sourceMappingURL=locales.d.ts.map
@@ -41,6 +41,16 @@ export const zh = {
41
41
  logs: '日志',
42
42
  logsTitle: '用量日志',
43
43
  logEmpty: '暂无记录',
44
+ logNoMatch: '无匹配记录',
45
+ logSearchPlaceholder: '搜索模型…',
46
+ logClear: '清除',
47
+ logClearTitle: '清除历史日志',
48
+ logClearHint: '将删除所选日期之前的所有记录(不含当日)',
49
+ logClearConfirm: '确认清除',
50
+ logClearCancel: '取消',
51
+ logClearing: '清除中…',
52
+ logPagerInfo: '第 {from}-{to} 条 / 共 {total} 条',
53
+ logPerPage: '条/页',
44
54
  logDay: '日期',
45
55
  logModel: '模型',
46
56
  logUsed: '用量',
@@ -55,6 +65,8 @@ export const zh = {
55
65
  checkUpdatesLabel: '自动检查更新',
56
66
  checkUpdatesNow: '立即检查',
57
67
  checkingUpdates: '检查中…',
68
+ checkUpToDate: '已是最新',
69
+ checkFailed: '检查失败',
58
70
  };
59
71
  /** English copy. */
60
72
  export const en = {
@@ -93,6 +105,16 @@ export const en = {
93
105
  logs: 'Logs',
94
106
  logsTitle: 'Usage Log',
95
107
  logEmpty: 'No records',
108
+ logNoMatch: 'No matching records',
109
+ logSearchPlaceholder: 'Search model…',
110
+ logClear: 'Clear',
111
+ logClearTitle: 'Clear historical log',
112
+ logClearHint: 'This deletes all records before the selected date (the date itself is kept).',
113
+ logClearConfirm: 'Confirm',
114
+ logClearCancel: 'Cancel',
115
+ logClearing: 'Clearing…',
116
+ logPagerInfo: '{from}-{to} of {total}',
117
+ logPerPage: '/page',
96
118
  logDay: 'Day',
97
119
  logModel: 'Model',
98
120
  logUsed: 'Used',
@@ -107,5 +129,7 @@ export const en = {
107
129
  checkUpdatesLabel: 'Check for updates automatically',
108
130
  checkUpdatesNow: 'Check now',
109
131
  checkingUpdates: 'Checking…',
132
+ checkUpToDate: 'Up to date',
133
+ checkFailed: 'Check failed',
110
134
  };
111
135
  //# sourceMappingURL=locales.js.map
@@ -54,6 +54,8 @@ export interface TokenQuotaPanelState {
54
54
  upgradeDismissed: boolean;
55
55
  /** Whether a manual update check is in flight. */
56
56
  checkingUpdates: boolean;
57
+ /** Result of the most recent manual check; 'idle' = nothing to show. */
58
+ lastCheckResult: 'idle' | 'up-to-date' | 'error';
57
59
  /** Whether the settings dialog is open. */
58
60
  dialogOpen: boolean;
59
61
  /** Full-quota notice shown for the `'stop'` strategy; null when none. */
@@ -64,6 +66,18 @@ export interface TokenQuotaPanelState {
64
66
  logOpen: boolean;
65
67
  /** Latest usage history; null before the first fetch. */
66
68
  log: TokenQuotaLog | null;
69
+ /** Filter string for the log table (matches provider/model key). */
70
+ logSearch: string;
71
+ /** Current page index (0-based) in the filtered log table. */
72
+ logPage: number;
73
+ /** Rows per page in the log table. */
74
+ logPageSize: number;
75
+ /** Whether the clear-log confirmation dialog is open. */
76
+ logClearOpen: boolean;
77
+ /** The `YYYY-MM-DD` date before which log entries will be cleared. */
78
+ logClearBefore: string;
79
+ /** Whether a clear-log request is in flight. */
80
+ logClearing: boolean;
67
81
  }
68
82
  /** Declared write surface. */
69
83
  export type TokenQuotaPanelActions = {
@@ -76,11 +90,18 @@ export type TokenQuotaPanelActions = {
76
90
  setUpgrade: (d: TokenQuotaPanelState, upgrade: TokenQuotaUpgrade | null) => void;
77
91
  setUpgradeDismissed: (d: TokenQuotaPanelState, dismissed: boolean) => void;
78
92
  setCheckingUpdates: (d: TokenQuotaPanelState, checking: boolean) => void;
93
+ setLastCheckResult: (d: TokenQuotaPanelState, result: 'idle' | 'up-to-date' | 'error') => void;
79
94
  setDialogOpen: (d: TokenQuotaPanelState, open: boolean) => void;
80
95
  setFullNotice: (d: TokenQuotaPanelState, notice: string | null) => void;
81
96
  setReset: (d: TokenQuotaPanelState, reset: TokenQuotaReset | null) => void;
82
97
  setLogOpen: (d: TokenQuotaPanelState, open: boolean) => void;
83
98
  setLog: (d: TokenQuotaPanelState, log: TokenQuotaLog | null) => void;
99
+ setLogSearch: (d: TokenQuotaPanelState, search: string) => void;
100
+ setLogPage: (d: TokenQuotaPanelState, page: number) => void;
101
+ setLogPageSize: (d: TokenQuotaPanelState, size: number) => void;
102
+ setLogClearOpen: (d: TokenQuotaPanelState, open: boolean) => void;
103
+ setLogClearBefore: (d: TokenQuotaPanelState, before: string) => void;
104
+ setLogClearing: (d: TokenQuotaPanelState, clearing: boolean) => void;
84
105
  };
85
106
  /**
86
107
  * Declares the panel state and write surface.
@@ -30,11 +30,18 @@ export function createTokenQuotaPanelStore() {
30
30
  upgrade: null,
31
31
  upgradeDismissed: false,
32
32
  checkingUpdates: false,
33
+ lastCheckResult: 'idle',
33
34
  dialogOpen: false,
34
35
  fullNotice: null,
35
36
  reset: null,
36
37
  logOpen: false,
37
38
  log: null,
39
+ logSearch: '',
40
+ logPage: 0,
41
+ logPageSize: 15,
42
+ logClearOpen: false,
43
+ logClearBefore: '',
44
+ logClearing: false,
38
45
  }),
39
46
  actions: {
40
47
  setSnapshot: (d, snapshot) => { d.snapshot = snapshot; },
@@ -53,11 +60,18 @@ export function createTokenQuotaPanelStore() {
53
60
  },
54
61
  setUpgradeDismissed: (d, dismissed) => { d.upgradeDismissed = dismissed; },
55
62
  setCheckingUpdates: (d, checking) => { d.checkingUpdates = checking; },
63
+ setLastCheckResult: (d, result) => { d.lastCheckResult = result; },
56
64
  setDialogOpen: (d, open) => { d.dialogOpen = open; },
57
65
  setFullNotice: (d, notice) => { d.fullNotice = notice; },
58
66
  setReset: (d, reset) => { d.reset = reset; },
59
67
  setLogOpen: (d, open) => { d.logOpen = open; },
60
68
  setLog: (d, log) => { d.log = log; },
69
+ setLogSearch: (d, search) => { d.logSearch = search; d.logPage = 0; },
70
+ setLogPage: (d, page) => { d.logPage = page; },
71
+ setLogPageSize: (d, size) => { d.logPageSize = size; d.logPage = 0; },
72
+ setLogClearOpen: (d, open) => { d.logClearOpen = open; },
73
+ setLogClearBefore: (d, before) => { d.logClearBefore = before; },
74
+ setLogClearing: (d, clearing) => { d.logClearing = clearing; },
61
75
  },
62
76
  });
63
77
  }
@@ -77,6 +77,8 @@ export declare class TokenQuotaService extends Service {
77
77
  private disposeRouteLog;
78
78
  /** Disposer for the optional manual-check route (`POST /token-quota/check-updates`). */
79
79
  private disposeRouteCheck;
80
+ /** Disposer for the optional clear-log route (`POST /token-quota/clear-log`). */
81
+ private disposeRouteClear;
80
82
  /** Disposer for the webServer-arrival watcher when the service mounts later. */
81
83
  private disposeRouteWatcher;
82
84
  /**
@@ -93,6 +95,12 @@ export declare class TokenQuotaService extends Service {
93
95
  private syncUpdateChecking;
94
96
  /** Read the full per-cycle usage history (log dialog data). */
95
97
  readLog(): TokenQuotaLog;
98
+ /**
99
+ * Remove all historical cycles whose day portion strictly precedes `before`
100
+ * (format `YYYY-MM-DD`). The current cycle is never cleared. Returns the
101
+ * trimmed log so the client can refresh immediately.
102
+ */
103
+ clearLogBefore(before: string): TokenQuotaLog;
96
104
  /** Read the current snapshot (also used by tests and inspection). */
97
105
  readSnapshot(): TokenQuotaSnapshot;
98
106
  /** Today's used tokens for one model key, or `0`. */
@@ -243,6 +243,8 @@ export class TokenQuotaService extends Service {
243
243
  disposeRouteLog;
244
244
  /** Disposer for the optional manual-check route (`POST /token-quota/check-updates`). */
245
245
  disposeRouteCheck;
246
+ /** Disposer for the optional clear-log route (`POST /token-quota/clear-log`). */
247
+ disposeRouteClear;
246
248
  /** Disposer for the webServer-arrival watcher when the service mounts later. */
247
249
  disposeRouteWatcher;
248
250
  /**
@@ -376,6 +378,37 @@ export class TokenQuotaService extends Service {
376
378
  });
377
379
  },
378
380
  });
381
+ this.disposeRouteClear = server.register({
382
+ kind: 'exact',
383
+ path: '/token-quota/clear-log',
384
+ handler: (req, res) => {
385
+ if (req.method !== 'POST') {
386
+ res.writeHead(405);
387
+ res.end();
388
+ return;
389
+ }
390
+ const chunks = [];
391
+ req.on('data', (chunk) => { chunks.push(Buffer.from(chunk)); });
392
+ req.on('end', () => {
393
+ let before = '';
394
+ try {
395
+ const raw = Buffer.concat(chunks).toString('utf8');
396
+ if (raw.length > 0) {
397
+ const payload = JSON.parse(raw);
398
+ before = typeof payload.before === 'string' ? payload.before : '';
399
+ }
400
+ }
401
+ catch {
402
+ res.writeHead(400, { 'content-type': 'application/json' });
403
+ res.end(JSON.stringify({ error: 'bad payload' }));
404
+ return;
405
+ }
406
+ const updated = this.clearLogBefore(before);
407
+ res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
408
+ res.end(JSON.stringify(updated));
409
+ });
410
+ },
411
+ });
379
412
  }
380
413
  /** Query the npm registry and rebuild the cached upgrade info (coalesced). */
381
414
  refreshUpgrade() {
@@ -470,6 +503,25 @@ export class TokenQuotaService extends Service {
470
503
  entries.sort((left, right) => right.day.localeCompare(left.day) || left.key.localeCompare(right.key));
471
504
  return { entries };
472
505
  }
506
+ /**
507
+ * Remove all historical cycles whose day portion strictly precedes `before`
508
+ * (format `YYYY-MM-DD`). The current cycle is never cleared. Returns the
509
+ * trimmed log so the client can refresh immediately.
510
+ */
511
+ clearLogBefore(before) {
512
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(before))
513
+ return this.readLog();
514
+ const kept = {};
515
+ for (const [cycle, usageByKey] of Object.entries(this.history)) {
516
+ const dayPart = cycle.slice(0, 10);
517
+ if (dayPart >= before) {
518
+ kept[cycle] = usageByKey;
519
+ }
520
+ }
521
+ this.history = kept;
522
+ this.scheduleWrite();
523
+ return this.readLog();
524
+ }
473
525
  /** Read the current snapshot (also used by tests and inspection). */
474
526
  readSnapshot() {
475
527
  this.rollCycleIfNeeded();
@@ -728,6 +780,8 @@ export class TokenQuotaService extends Service {
728
780
  this.disposeRouteLog();
729
781
  if (this.disposeRouteCheck !== undefined)
730
782
  this.disposeRouteCheck();
783
+ if (this.disposeRouteClear !== undefined)
784
+ this.disposeRouteClear();
731
785
  this.flush();
732
786
  }
733
787
  safeSplit(key) {
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.11",
4
+ "version": "0.1.12",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },