@jxgame2020/dsh-token-quota 0.1.29 → 0.1.30

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
@@ -95,7 +95,7 @@ window.__ModuleLoader__.load({
95
95
  d.musicOnlyCurrentSession = only;
96
96
  },
97
97
  setUpgrade: (d, upgrade) => {
98
- const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.29" ? upgrade : null;
98
+ const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.30" ? upgrade : null;
99
99
  if (d.upgrade === null || d.upgrade.latestVersion !== normalized?.latestVersion) d.upgradeDismissed = false;
100
100
  d.upgrade = normalized;
101
101
  },
@@ -191,7 +191,7 @@ window.__ModuleLoader__.load({
191
191
  }
192
192
  //#endregion
193
193
  //#region \0dsh-css:/Users/jxgame/Desktop/works/deepseek-harness-package/dsh-token-quota/packages/token-quota/src/client/TokenQuotaPanel.module.css.mjs
194
- const css = ".NPLLIq_panel{box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);pointer-events:auto;z-index:40;opacity:1;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);transition:opacity .18s;display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.NPLLIq_panelDim{opacity:.55}.NPLLIq_panelDimStrong{opacity:.3}.NPLLIq_header{border-bottom:1px solid var(--dsw-alias-border-l1);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:flex-start;gap:8px;padding:10px 12px;display:flex}.NPLLIq_header:active{cursor:grabbing}.NPLLIq_headerText{flex-direction:column;gap:2px;min-width:0;display:flex}.NPLLIq_title{color:var(--dsw-alias-label-primary);align-items:baseline;gap:6px;font-size:13px;font-weight:600;display:flex}.NPLLIq_version{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:10px;font-weight:500}.NPLLIq_versionNew{color:var(--dsw-alias-status-warning-foreground,#b56900);background:var(--dsw-alias-status-warning-background,#fff6d7);cursor:default;border-radius:4px;padding:1px 6px}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_settingsBtnActive{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2,#0000000f);box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary);font-weight:700}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_upgradeBanner{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-divider-1,#0000001a);border-radius:8px;margin:6px 12px;padding:10px 30px 10px 10px;font-size:12px;position:relative}@keyframes NPLLIq_upgradeBannerFlash{0%,to{border-color:var(--dsw-alias-divider-1,#0000001a);box-shadow:0 0 #f0a02000}50%{border-color:var(--dsw-alias-status-warning-foreground,#b56900);box-shadow:0 0 0 5px #f0a02073}}.NPLLIq_upgradeBannerFlash{border-color:var(--dsw-alias-status-warning-foreground,#b56900);animation:.6s ease-in-out 4 NPLLIq_upgradeBannerFlash}.NPLLIq_upgradeBannerText{margin-bottom:6px;font-weight:600}.NPLLIq_upgradeCommands{flex-direction:column;gap:4px;display:flex}.NPLLIq_upgradeCommandRow{align-items:center;gap:6px;display:flex}.NPLLIq_upgradeCommand{background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:nowrap;border-radius:4px;flex:1;min-width:0;padding:4px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow-x:auto}.NPLLIq_copyBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;flex:none;padding:3px 8px;font-size:11px;transition:transform 80ms,background .12s,color .12s}.NPLLIq_copyBtn:hover{background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_copyBtn:active{transform:scale(.92)}.NPLLIq_copyBtnDone{background:var(--dsw-alias-status-success-background,#14963c1f);border-color:var(--dsw-alias-status-success-foreground,#0a7d32);color:var(--dsw-alias-status-success-foreground,#0a7d32);font-weight:600}.NPLLIq_upgradeHint{color:var(--dsw-alias-label-secondary);margin-top:6px;font-size:11px}.NPLLIq_upgradeClose{width:20px;height:20px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;font-size:16px;line-height:1;position:absolute;top:4px;right:6px}.NPLLIq_upgradeClose:hover{background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary)}.NPLLIq_checkUpdatesRow{justify-content:space-between;align-items:center;gap:12px;display:flex}.NPLLIq_checkUpdatesLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}.NPLLIq_checkUpdatesLabel input{margin:0}.NPLLIq_checkUpdatesLabel input[type=range]{min-width:90px;accent-color:var(--dsw-alias-brand-primary);flex:1}.NPLLIq_checkUpdatesLabel select{background:var(--dsw-alias-bg-layer-2,#0000000a);min-width:90px;color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-divider-1,#00000026);border-radius:4px;flex:1;padding:2px 4px;font-size:12px}.NPLLIq_checkBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-align:center;border-radius:4px;flex:none;min-width:72px;padding:4px 10px;font-size:12px}.NPLLIq_checkBtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_checkBtn:disabled{opacity:.6;cursor:default}.NPLLIq_checkErrorHint{color:var(--dsw-alias-state-error-primary,#c00);background:var(--dsw-alias-state-error-background,#ff00000f);border:1px solid var(--dsw-alias-state-error-border,#b4000040);word-break:break-all;border-radius:6px;margin-top:8px;padding:6px 8px;font-size:11px;line-height:1.4}.NPLLIq_dialog{z-index:100;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;gap:12px;width: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_dialogHint{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:11px;line-height:1.4}.NPLLIq_monitorList{flex-direction:column;gap:2px;max-height:180px;display:flex;overflow-y:auto}.NPLLIq_monitorRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:2px 4px;font-size:12px;display:flex}.NPLLIq_monitorRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_monitorName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.NPLLIq_radioRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:3px 4px;font-size:12px;display:flex}.NPLLIq_radioRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_dialogActions{justify-content:flex-end;gap:6px;display:flex}.NPLLIq_dialogBtn,.NPLLIq_dialogBtnPrimary{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;border-radius:6px;padding:4px 12px;font-size:11px}.NPLLIq_dialogBtn{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.NPLLIq_dialogBtnPrimary{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay)}.NPLLIq_dialogHeader{z-index:2;background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:center;gap:8px;margin:-14px -14px 0;padding:14px 14px 8px;display:flex;position:sticky;top:-14px}.NPLLIq_dialogHeader:active{cursor:grabbing}.NPLLIq_dialogClose{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;font-size:18px;line-height:1;display:flex}.NPLLIq_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_resetRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.NPLLIq_resetSelect{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:3px 6px;font-size:12px}.NPLLIq_logTable{border-collapse:collapse;width:100%;font-size:11px}.NPLLIq_logTable th{text-align:left;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);padding:4px 6px;font-weight:500}.NPLLIq_logTable td{color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l1);font-variant-numeric:tabular-nums;padding:4px 6px}.NPLLIq_logDayCol{white-space:nowrap;color:var(--dsw-alias-label-secondary)}.NPLLIq_logModelCol{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.NPLLIq_logUsedCol{text-align:right}.NPLLIq_logDialog{gap:0;width:380px;max-height:min(72vh,480px);padding:0}.NPLLIq_logHeader{background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;flex:none;justify-content:space-between;align-items:center;gap:8px;padding:14px 14px 8px;display:flex}.NPLLIq_logHeader:active{cursor:grabbing}.NPLLIq_logScroll{flex:1;min-height:0;padding:0 14px 14px;overflow:auto}.NPLLIq_logToolbar{flex:1;align-items:center;gap:6px;display:flex}.NPLLIq_logSearch{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);min-width:0;color:var(--dsw-alias-label-primary);border-radius:4px;flex:1;padding:3px 6px;font-size:11px}.NPLLIq_logSearch:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}.NPLLIq_logClearBtn{border:1px solid var(--dsw-alias-state-error-border,#b400004d);color:var(--dsw-alias-state-error-primary,#c00);cursor:pointer;white-space:nowrap;background:0 0;border-radius:4px;flex:none;padding:3px 8px;font-size:11px}.NPLLIq_logClearBtn:hover{background:var(--dsw-alias-state-error-background,#ff00000f)}.NPLLIq_logPager{border-top:1px solid var(--dsw-alias-divider-1,#00000014);background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);flex:none;justify-content:space-between;align-items:center;gap:8px;padding:6px 14px;font-size:11px;display:flex}.NPLLIq_logPagerInfo{white-space:nowrap}.NPLLIq_logPagerControls{align-items:center;gap:4px;display:flex}.NPLLIq_logPagerBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:24px;height:24px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;font-size:13px;line-height:1}.NPLLIq_logPagerBtn:disabled{opacity:.4;cursor:default}.NPLLIq_logPagerPages{text-align:center;min-width:50px;color:var(--dsw-alias-label-secondary)}.NPLLIq_logPageSizeSelect{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;padding:2px 4px;font-size:11px}.NPLLIq_dialogOverlay{z-index:100001;background:#0000004d;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.NPLLIq_logClearDialog{gap:0;width:320px;padding:16px}.NPLLIq_dialogBody{color:var(--dsw-alias-label-secondary);margin:6px 0 10px;font-size:12px;line-height:1.5}.NPLLIq_logClearDate{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:100%;color:var(--dsw-alias-label-primary);border-radius:6px;margin-bottom:12px;padding:6px 8px;font-size:13px}.NPLLIq_logClearDate:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}";
194
+ const css = ".NPLLIq_panel{box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);pointer-events:auto;z-index:40;opacity:1;border-radius:12px;flex-direction:column;width:320px;max-height:min(72vh,560px);transition:opacity .18s;display:flex;position:fixed;top:56px;right:12px;overflow:hidden;box-shadow:0 8px 28px #0000002e}.NPLLIq_panelDim{opacity:.55}.NPLLIq_panelDimStrong{opacity:.3}.NPLLIq_header{border-bottom:1px solid var(--dsw-alias-border-l1);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:flex-start;gap:8px;padding:10px 12px;display:flex}.NPLLIq_header:active{cursor:grabbing}.NPLLIq_headerText{flex-direction:column;gap:2px;min-width:0;display:flex}.NPLLIq_title{color:var(--dsw-alias-label-primary);align-items:baseline;gap:6px;font-size:13px;font-weight:600;display:flex}.NPLLIq_version{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:10px;font-weight:500}.NPLLIq_versionNew{color:var(--dsw-alias-status-warning-foreground,#b56900);background:var(--dsw-alias-status-warning-background,#fff6d7);cursor:default;border-radius:4px;padding:1px 6px}.NPLLIq_subtitle{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_collapse{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_body{padding:4px 0;overflow-y:auto}.NPLLIq_row{border-bottom:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:6px;padding:8px 12px;display:flex}.NPLLIq_rowHeader{justify-content:space-between;align-items:center;gap:8px;display:flex}.NPLLIq_rowName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:6px;font-size:12px;font-weight:500;display:inline-flex;overflow:hidden}.NPLLIq_currentBadge{color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-primary);border-radius:8px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_rowMeta{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.NPLLIq_bar{background:var(--dsw-alias-bg-layer-1);border-radius:3px;height:6px;overflow:hidden}.NPLLIq_fillIdle{background:var(--dsw-alias-brand-primary);height:100%;transition:width .2s}.NPLLIq_fillWarn{background:var(--dsw-alias-state-warn-primary);height:100%;transition:width .2s}.NPLLIq_fillOver{background:var(--dsw-alias-state-error-primary);height:100%;transition:width .2s}.NPLLIq_controls{align-items:center;gap:6px;display:flex}.NPLLIq_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);border-radius:6px;flex:1;padding:4px 8px;font-size:11px}.NPLLIq_input::placeholder{color:var(--dsw-alias-label-secondary)}.NPLLIq_save,.NPLLIq_switch{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px;font-size:11px}.NPLLIq_save:hover,.NPLLIq_switch:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.NPLLIq_switch:disabled{opacity:.5;cursor:default}.NPLLIq_notice,.NPLLIq_noticeError{color:var(--dsw-alias-label-secondary);padding:10px 12px;font-size:12px}.NPLLIq_noticeError{color:var(--dsw-alias-state-error-primary)}.NPLLIq_tab{background:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay);pointer-events:auto;cursor:pointer;z-index:90;border-radius:10px;align-items:center;gap:6px;padding:9px 14px;font-size:12px;font-weight:600;display:inline-flex;position:fixed;top:56px;right:12px;box-shadow:0 6px 20px #00000040}.NPLLIq_tab:hover{filter:brightness(1.08)}.NPLLIq_tabIcon{font-size:12px;line-height:1}.NPLLIq_tabLabel{white-space:nowrap}.NPLLIq_headerActions{flex:none;align-items:center;gap:4px;display:flex}.NPLLIq_settingsBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:none;padding:2px 6px;font-size:11px}.NPLLIq_settingsBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_settingsBtnActive{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2,#0000000f);box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary);font-weight:700}.NPLLIq_selectBtn{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-brand-primary);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:1px 6px;font-size:10px}.NPLLIq_selectBtn:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_gearBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;margin-left:6px;padding:0 2px;font-size:12px;line-height:1}.NPLLIq_gearBtn:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_fullNotice{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;margin:6px 12px;padding:8px 10px;font-size:12px}.NPLLIq_upgradeBanner{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-divider-1,#0000001a);border-radius:8px;margin:6px 12px;padding:10px 30px 10px 10px;font-size:12px;position:relative}@keyframes NPLLIq_upgradeBannerFlash{0%,to{border-color:var(--dsw-alias-divider-1,#0000001a);box-shadow:0 0 #f0a02000}50%{border-color:var(--dsw-alias-status-warning-foreground,#b56900);box-shadow:0 0 0 5px #f0a02073}}.NPLLIq_upgradeBannerFlash{border-color:var(--dsw-alias-status-warning-foreground,#b56900);animation:.6s ease-in-out 4 NPLLIq_upgradeBannerFlash}.NPLLIq_upgradeBannerText{margin-bottom:6px;font-weight:600}.NPLLIq_upgradeCommands{flex-direction:column;gap:4px;display:flex}.NPLLIq_upgradeCommandRow{align-items:center;gap:6px;display:flex}.NPLLIq_upgradeCommand{background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:nowrap;border-radius:4px;flex:1;min-width:0;padding:4px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow-x:auto}.NPLLIq_copyBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;flex:none;padding:3px 8px;font-size:11px;transition:transform 80ms,background .12s,color .12s}.NPLLIq_copyBtn:hover{background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_copyBtn:active{transform:scale(.92)}.NPLLIq_copyBtnDone{background:var(--dsw-alias-status-success-background,#14963c1f);border-color:var(--dsw-alias-status-success-foreground,#0a7d32);color:var(--dsw-alias-status-success-foreground,#0a7d32);font-weight:600}.NPLLIq_upgradeHint{color:var(--dsw-alias-label-secondary);margin-top:6px;font-size:11px}.NPLLIq_upgradeClose{width:20px;height:20px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:4px;font-size:16px;line-height:1;position:absolute;top:4px;right:6px}.NPLLIq_upgradeClose:hover{background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary)}.NPLLIq_checkUpdatesRow{justify-content:space-between;align-items:center;gap:12px;display:flex}.NPLLIq_checkUpdatesLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}.NPLLIq_checkUpdatesLabel input{margin:0}.NPLLIq_checkUpdatesLabel input[type=range]{min-width:90px;accent-color:var(--dsw-alias-brand-primary);flex:1}.NPLLIq_checkUpdatesLabel select{background:var(--dsw-alias-bg-layer-2,#0000000a);min-width:90px;color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-divider-1,#00000026);border-radius:4px;flex:1;padding:2px 4px;font-size:12px}.NPLLIq_checkBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-align:center;border-radius:4px;flex:none;min-width:72px;padding:4px 10px;font-size:12px}.NPLLIq_checkBtn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3,#00000014)}.NPLLIq_checkBtn:disabled{opacity:.6;cursor:default}.NPLLIq_checkErrorHint{color:var(--dsw-alias-state-error-primary,#c00);background:var(--dsw-alias-state-error-background,#ff00000f);border:1px solid var(--dsw-alias-state-error-border,#b4000040);word-break:break-all;border-radius:6px;margin-top:8px;padding:6px 8px;font-size:11px;line-height:1.4}.NPLLIq_dialog{z-index:100;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;gap:12px;width:430px;max-width:94vw;max-height:80vh;padding:14px;display:flex;position:fixed;top:50%;left:50%;overflow-y:auto;transform:translate(-50%,-50%);box-shadow:0 8px 28px #00000040}.NPLLIq_dialogTabs{border-bottom:1px solid var(--dsw-alias-divider-1,#00000014);gap:2px;margin:-6px -14px 0;padding:0 14px;display:flex}.NPLLIq_dialogTab{appearance:none;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px 8px;font-size:12px}.NPLLIq_dialogTab:hover{color:var(--dsw-alias-label-primary)}.NPLLIq_dialogTabActive{color:var(--dsw-alias-brand-primary);border-bottom-color:var(--dsw-alias-brand-primary);font-weight:600}.NPLLIq_dialogTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.NPLLIq_dialogSection{flex-direction:column;gap:6px;display:flex}.NPLLIq_dialogLabel{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500}.NPLLIq_monitorHint{color:var(--dsw-alias-label-secondary);font-size:11px}.NPLLIq_dialogHint{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:11px;line-height:1.4}.NPLLIq_monitorList{flex-direction:column;gap:2px;max-height:180px;display:flex;overflow-y:auto}.NPLLIq_monitorRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:2px 4px;font-size:12px;display:flex}.NPLLIq_monitorRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_monitorName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.NPLLIq_radioRow{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:3px 4px;font-size:12px;display:flex}.NPLLIq_radioRow:hover{background:var(--dsw-alias-bg-layer-1)}.NPLLIq_dialogActions{justify-content:flex-end;gap:6px;display:flex}.NPLLIq_dialogBtn,.NPLLIq_dialogBtnPrimary{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;border-radius:6px;padding:4px 12px;font-size:11px}.NPLLIq_dialogBtn{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.NPLLIq_dialogBtnPrimary{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-overlay)}.NPLLIq_dialogHeader{z-index:2;background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;justify-content:space-between;align-items:center;gap:8px;margin:-14px -14px 0;padding:14px 14px 8px;display:flex;position:sticky;top:-14px}.NPLLIq_dialogHeader:active{cursor:grabbing}.NPLLIq_dialogClose{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;font-size:18px;line-height:1;display:flex}.NPLLIq_dialogClose:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.NPLLIq_resetRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.NPLLIq_resetSelect{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:3px 6px;font-size:12px}.NPLLIq_logTable{border-collapse:collapse;width:100%;font-size:11px}.NPLLIq_logTable th{text-align:left;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);padding:4px 6px;font-weight:500}.NPLLIq_logTable td{color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l1);font-variant-numeric:tabular-nums;padding:4px 6px}.NPLLIq_logDayCol{white-space:nowrap;color:var(--dsw-alias-label-secondary)}.NPLLIq_logModelCol{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.NPLLIq_logUsedCol{text-align:right}.NPLLIq_logDialog{gap:0;width:380px;max-height:min(72vh,480px);padding:0}.NPLLIq_logHeader{background:var(--dsw-alias-bg-overlay);cursor:grab;user-select:none;touch-action:none;flex:none;justify-content:space-between;align-items:center;gap:8px;padding:14px 14px 8px;display:flex}.NPLLIq_logHeader:active{cursor:grabbing}.NPLLIq_logScroll{flex:1;min-height:0;padding:0 14px 14px;overflow:auto}.NPLLIq_logToolbar{flex:1;align-items:center;gap:6px;display:flex}.NPLLIq_logSearch{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);min-width:0;color:var(--dsw-alias-label-primary);border-radius:4px;flex:1;padding:3px 6px;font-size:11px}.NPLLIq_logSearch:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}.NPLLIq_logClearBtn{border:1px solid var(--dsw-alias-state-error-border,#b400004d);color:var(--dsw-alias-state-error-primary,#c00);cursor:pointer;white-space:nowrap;background:0 0;border-radius:4px;flex:none;padding:3px 8px;font-size:11px}.NPLLIq_logClearBtn:hover{background:var(--dsw-alias-state-error-background,#ff00000f)}.NPLLIq_logPager{border-top:1px solid var(--dsw-alias-divider-1,#00000014);background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);flex:none;justify-content:space-between;align-items:center;gap:8px;padding:6px 14px;font-size:11px;display:flex}.NPLLIq_logPagerInfo{white-space:nowrap}.NPLLIq_logPagerControls{align-items:center;gap:4px;display:flex}.NPLLIq_logPagerBtn{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:24px;height:24px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;font-size:13px;line-height:1}.NPLLIq_logPagerBtn:disabled{opacity:.4;cursor:default}.NPLLIq_logPagerPages{text-align:center;min-width:50px;color:var(--dsw-alias-label-secondary)}.NPLLIq_logPageSizeSelect{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:4px;padding:2px 4px;font-size:11px}.NPLLIq_dialogOverlay{z-index:100001;background:#0000004d;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.NPLLIq_logClearDialog{gap:0;width:320px;padding:16px}.NPLLIq_dialogBody{color:var(--dsw-alias-label-secondary);margin:6px 0 10px;font-size:12px;line-height:1.5}.NPLLIq_logClearDate{border:1px solid var(--dsw-alias-divider-1,#00000026);background:var(--dsw-alias-bg-layer-1,#0000000a);width:100%;color:var(--dsw-alias-label-primary);border-radius:6px;margin-bottom:12px;padding:6px 8px;font-size:13px}.NPLLIq_logClearDate:focus{border-color:var(--dsw-alias-accent-1,#4a9eff);outline:none}";
195
195
  const tagId = "@jxgame2020/dsh-token-quota/TokenQuotaPanel.module.css";
196
196
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
197
197
  const tag = document.createElement("style");
@@ -202,91 +202,94 @@ window.__ModuleLoader__.load({
202
202
  }
203
203
  var TokenQuotaPanel_module_css_default = {
204
204
  "controls": "NPLLIq_controls",
205
+ "logHeader": "NPLLIq_logHeader",
205
206
  "logModelCol": "NPLLIq_logModelCol",
206
- "resetRow": "NPLLIq_resetRow",
207
- "noticeError": "NPLLIq_noticeError",
208
- "selectBtn": "NPLLIq_selectBtn",
209
- "monitorList": "NPLLIq_monitorList",
210
- "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
211
- "dialogBtn": "NPLLIq_dialogBtn",
212
- "logDayCol": "NPLLIq_logDayCol",
213
- "fillIdle": "NPLLIq_fillIdle",
214
- "logPagerBtn": "NPLLIq_logPagerBtn",
215
- "panel": "NPLLIq_panel",
216
- "subtitle": "NPLLIq_subtitle",
217
- "notice": "NPLLIq_notice",
218
- "settingsBtnActive": "NPLLIq_settingsBtnActive",
219
207
  "gearBtn": "NPLLIq_gearBtn",
220
- "panelDimStrong": "NPLLIq_panelDimStrong",
221
- "logClearDate": "NPLLIq_logClearDate",
208
+ "monitorHint": "NPLLIq_monitorHint",
209
+ "bar": "NPLLIq_bar",
210
+ "resetSelect": "NPLLIq_resetSelect",
211
+ "dialogTab": "NPLLIq_dialogTab",
212
+ "dialogTabActive": "NPLLIq_dialogTabActive",
213
+ "logPagerInfo": "NPLLIq_logPagerInfo",
214
+ "radioRow": "NPLLIq_radioRow",
215
+ "header": "NPLLIq_header",
216
+ "rowHeader": "NPLLIq_rowHeader",
217
+ "input": "NPLLIq_input",
218
+ "settingsBtnActive": "NPLLIq_settingsBtnActive",
222
219
  "settingsBtn": "NPLLIq_settingsBtn",
223
- "copyBtn": "NPLLIq_copyBtn",
220
+ "subtitle": "NPLLIq_subtitle",
221
+ "copyBtnDone": "NPLLIq_copyBtnDone",
222
+ "checkBtn": "NPLLIq_checkBtn",
223
+ "dialogTitle": "NPLLIq_dialogTitle",
224
+ "monitorRow": "NPLLIq_monitorRow",
225
+ "logScroll": "NPLLIq_logScroll",
226
+ "dialogTabs": "NPLLIq_dialogTabs",
227
+ "logPageSizeSelect": "NPLLIq_logPageSizeSelect",
228
+ "checkErrorHint": "NPLLIq_checkErrorHint",
229
+ "logClearDate": "NPLLIq_logClearDate",
230
+ "dialogBtn": "NPLLIq_dialogBtn",
231
+ "upgradeCommand": "NPLLIq_upgradeCommand",
232
+ "logDayCol": "NPLLIq_logDayCol",
233
+ "resetRow": "NPLLIq_resetRow",
224
234
  "logPager": "NPLLIq_logPager",
225
- "tab": "NPLLIq_tab",
226
- "tabIcon": "NPLLIq_tabIcon",
227
- "dialog": "NPLLIq_dialog",
228
- "upgradeCommands": "NPLLIq_upgradeCommands",
229
- "rowMeta": "NPLLIq_rowMeta",
235
+ "collapse": "NPLLIq_collapse",
236
+ "upgradeHint": "NPLLIq_upgradeHint",
237
+ "logClearBtn": "NPLLIq_logClearBtn",
230
238
  "version": "NPLLIq_version",
231
- "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
232
- "dialogTitle": "NPLLIq_dialogTitle",
239
+ "upgradeCommands": "NPLLIq_upgradeCommands",
240
+ "notice": "NPLLIq_notice",
241
+ "upgradeBannerText": "NPLLIq_upgradeBannerText",
242
+ "monitorList": "NPLLIq_monitorList",
243
+ "logClearDialog": "NPLLIq_logClearDialog",
233
244
  "headerText": "NPLLIq_headerText",
245
+ "fullNotice": "NPLLIq_fullNotice",
234
246
  "dialogActions": "NPLLIq_dialogActions",
235
- "logDialog": "NPLLIq_logDialog",
236
- "fillOver": "NPLLIq_fillOver",
237
- "monitorRow": "NPLLIq_monitorRow",
238
- "upgradeBanner": "NPLLIq_upgradeBanner",
239
- "dialogHint": "NPLLIq_dialogHint",
240
- "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
241
- "logClearDialog": "NPLLIq_logClearDialog",
242
- "logHeader": "NPLLIq_logHeader",
243
- "upgradeHint": "NPLLIq_upgradeHint",
244
247
  "row": "NPLLIq_row",
248
+ "dialogHeader": "NPLLIq_dialogHeader",
249
+ "title": "NPLLIq_title",
250
+ "selectBtn": "NPLLIq_selectBtn",
251
+ "tabIcon": "NPLLIq_tabIcon",
252
+ "fillWarn": "NPLLIq_fillWarn",
253
+ "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
254
+ "dialogLabel": "NPLLIq_dialogLabel",
255
+ "panelDimStrong": "NPLLIq_panelDimStrong",
256
+ "dialogClose": "NPLLIq_dialogClose",
257
+ "switch": "NPLLIq_switch",
245
258
  "logToolbar": "NPLLIq_logToolbar",
259
+ "dialog": "NPLLIq_dialog",
260
+ "tabLabel": "NPLLIq_tabLabel",
261
+ "currentBadge": "NPLLIq_currentBadge",
262
+ "copyBtn": "NPLLIq_copyBtn",
263
+ "logDialog": "NPLLIq_logDialog",
246
264
  "logSearch": "NPLLIq_logSearch",
247
- "switch": "NPLLIq_switch",
248
- "checkBtn": "NPLLIq_checkBtn",
249
- "dialogSection": "NPLLIq_dialogSection",
250
- "bar": "NPLLIq_bar",
265
+ "dialogOverlay": "NPLLIq_dialogOverlay",
266
+ "noticeError": "NPLLIq_noticeError",
267
+ "headerActions": "NPLLIq_headerActions",
268
+ "panelDim": "NPLLIq_panelDim",
269
+ "tab": "NPLLIq_tab",
270
+ "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
271
+ "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
272
+ "dialogHint": "NPLLIq_dialogHint",
251
273
  "logPagerPages": "NPLLIq_logPagerPages",
252
- "input": "NPLLIq_input",
253
- "rowHeader": "NPLLIq_rowHeader",
254
- "copyBtnDone": "NPLLIq_copyBtnDone",
255
- "dialogClose": "NPLLIq_dialogClose",
256
- "logScroll": "NPLLIq_logScroll",
257
274
  "versionNew": "NPLLIq_versionNew",
258
- "header": "NPLLIq_header",
259
- "upgradeCommand": "NPLLIq_upgradeCommand",
260
- "logPagerControls": "NPLLIq_logPagerControls",
261
- "currentBadge": "NPLLIq_currentBadge",
262
- "tabLabel": "NPLLIq_tabLabel",
263
- "logPageSizeSelect": "NPLLIq_logPageSizeSelect",
264
- "upgradeBannerText": "NPLLIq_upgradeBannerText",
265
- "logClearBtn": "NPLLIq_logClearBtn",
266
- "fullNotice": "NPLLIq_fullNotice",
267
- "upgradeClose": "NPLLIq_upgradeClose",
268
- "dialogBody": "NPLLIq_dialogBody",
269
- "resetSelect": "NPLLIq_resetSelect",
270
- "rowName": "NPLLIq_rowName",
271
- "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
275
+ "body": "NPLLIq_body",
276
+ "rowMeta": "NPLLIq_rowMeta",
277
+ "dialogSection": "NPLLIq_dialogSection",
272
278
  "logUsedCol": "NPLLIq_logUsedCol",
273
- "collapse": "NPLLIq_collapse",
274
- "dialogOverlay": "NPLLIq_dialogOverlay",
275
- "radioRow": "NPLLIq_radioRow",
276
- "dialogHeader": "NPLLIq_dialogHeader",
277
- "title": "NPLLIq_title",
278
- "logTable": "NPLLIq_logTable",
279
+ "logPagerControls": "NPLLIq_logPagerControls",
280
+ "fillIdle": "NPLLIq_fillIdle",
279
281
  "save": "NPLLIq_save",
280
- "checkErrorHint": "NPLLIq_checkErrorHint",
282
+ "upgradeBanner": "NPLLIq_upgradeBanner",
283
+ "logPagerBtn": "NPLLIq_logPagerBtn",
284
+ "monitorName": "NPLLIq_monitorName",
285
+ "rowName": "NPLLIq_rowName",
281
286
  "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
282
- "dialogLabel": "NPLLIq_dialogLabel",
283
- "logPagerInfo": "NPLLIq_logPagerInfo",
284
- "panelDim": "NPLLIq_panelDim",
285
- "fillWarn": "NPLLIq_fillWarn",
286
- "monitorHint": "NPLLIq_monitorHint",
287
- "body": "NPLLIq_body",
288
- "headerActions": "NPLLIq_headerActions",
289
- "monitorName": "NPLLIq_monitorName"
287
+ "panel": "NPLLIq_panel",
288
+ "dialogBody": "NPLLIq_dialogBody",
289
+ "fillOver": "NPLLIq_fillOver",
290
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
291
+ "logTable": "NPLLIq_logTable",
292
+ "upgradeClose": "NPLLIq_upgradeClose"
290
293
  };
291
294
  //#endregion
292
295
  //#region lib/types/client/TokenQuotaPanel.js
@@ -379,6 +382,7 @@ window.__ModuleLoader__.load({
379
382
  const [editingKey, setEditingKey] = (0, react.useState)(null);
380
383
  const [hovered, setHovered] = (0, react.useState)(false);
381
384
  const [inputActive, setInputActive] = (0, react.useState)(false);
385
+ const [settingsTab, setSettingsTab] = (0, react.useState)("quota");
382
386
  const [copiedCmd, setCopiedCmd] = (0, react.useState)(null);
383
387
  const copyTimerRef = (0, react.useRef)(null);
384
388
  const [upgradeFlash, setUpgradeFlash] = (0, react.useState)(false);
@@ -592,13 +596,13 @@ window.__ModuleLoader__.load({
592
596
  title: t("upgradeAvailableHint"),
593
597
  children: [
594
598
  "v",
595
- "0.1.29",
599
+ "0.1.30",
596
600
  " → v",
597
601
  upgrade.latestVersion
598
602
  ]
599
603
  }) : (0, react_jsx_runtime.jsxs)("span", {
600
604
  className: TokenQuotaPanel_module_css_default.version,
601
- children: ["v", "0.1.29"]
605
+ children: ["v", "0.1.30"]
602
606
  })]
603
607
  }), (0, react_jsx_runtime.jsx)("div", {
604
608
  className: TokenQuotaPanel_module_css_default.subtitle,
@@ -816,136 +820,161 @@ window.__ModuleLoader__.load({
816
820
  })]
817
821
  }),
818
822
  (0, react_jsx_runtime.jsxs)("div", {
819
- className: TokenQuotaPanel_module_css_default.dialogSection,
820
- children: [
821
- (0, react_jsx_runtime.jsx)("div", {
822
- className: TokenQuotaPanel_module_css_default.dialogLabel,
823
- children: t("monitorLabel")
824
- }),
825
- (0, react_jsx_runtime.jsx)("div", {
826
- className: TokenQuotaPanel_module_css_default.monitorHint,
827
- children: t("monitorHint")
828
- }),
829
- (0, react_jsx_runtime.jsx)("div", {
830
- className: TokenQuotaPanel_module_css_default.monitorList,
831
- children: allModels.map((model) => (0, react_jsx_runtime.jsxs)("label", {
832
- className: TokenQuotaPanel_module_css_default.monitorRow,
833
- children: [(0, react_jsx_runtime.jsx)("input", {
834
- type: "checkbox",
835
- checked: monitored === null || monitored.includes(model.key),
836
- onChange: (event) => {
837
- const base = monitored === null ? allModels.map((m) => m.key) : monitored;
838
- const next = new Set(base);
839
- if (event.target.checked) next.add(model.key);
840
- else next.delete(model.key);
841
- setMonitored(next.size === allModels.length ? null : [...next]);
842
- }
843
- }), (0, react_jsx_runtime.jsx)("span", {
844
- className: TokenQuotaPanel_module_css_default.monitorName,
845
- title: model.key,
846
- children: model.name
847
- })]
848
- }, model.key))
849
- })
850
- ]
851
- }),
852
- (0, react_jsx_runtime.jsxs)("div", {
853
- className: TokenQuotaPanel_module_css_default.dialogSection,
854
- children: [(0, react_jsx_runtime.jsx)("div", {
855
- className: TokenQuotaPanel_module_css_default.dialogLabel,
856
- children: t("fullActionLabel")
857
- }), FULL_ACTIONS.map((action) => (0, react_jsx_runtime.jsxs)("label", {
858
- className: TokenQuotaPanel_module_css_default.radioRow,
859
- children: [(0, react_jsx_runtime.jsx)("input", {
860
- type: "radio",
861
- name: "token-quota-onfull",
862
- checked: onFull === action.value,
863
- onChange: () => {
864
- setOnFull(action.value);
865
- }
866
- }), (0, react_jsx_runtime.jsx)("span", { children: t(action.labelKey) })]
867
- }, action.value))]
868
- }),
869
- (0, react_jsx_runtime.jsxs)("div", {
870
- className: TokenQuotaPanel_module_css_default.dialogSection,
871
- children: [
872
- (0, react_jsx_runtime.jsx)("div", {
873
- className: TokenQuotaPanel_module_css_default.dialogLabel,
874
- children: t("resetLabel")
875
- }),
876
- (0, react_jsx_runtime.jsx)("div", {
877
- className: TokenQuotaPanel_module_css_default.monitorHint,
878
- children: t("resetHint")
879
- }),
880
- (0, react_jsx_runtime.jsxs)("div", {
881
- className: TokenQuotaPanel_module_css_default.resetRow,
882
- children: [
883
- (0, react_jsx_runtime.jsx)("select", {
884
- className: TokenQuotaPanel_module_css_default.resetSelect,
885
- value: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
886
- onChange: (event) => {
887
- setReset({
888
- offsetHours: Number(event.target.value),
889
- hour: reset?.hour ?? 0,
890
- minute: reset?.minute ?? 0
891
- });
892
- },
893
- children: Array.from({ length: 27 }, (_, i) => i - 12).map((offset) => (0, react_jsx_runtime.jsxs)("option", {
894
- value: offset,
895
- children: ["UTC", offset >= 0 ? `+${offset}` : offset]
896
- }, offset))
897
- }),
898
- (0, react_jsx_runtime.jsx)("select", {
899
- className: TokenQuotaPanel_module_css_default.resetSelect,
900
- value: reset?.hour ?? 0,
901
- onChange: (event) => {
902
- setReset({
903
- offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
904
- hour: Number(event.target.value),
905
- minute: reset?.minute ?? 0
906
- });
907
- },
908
- children: Array.from({ length: 24 }, (_, i) => i).map((hour) => (0, react_jsx_runtime.jsxs)("option", {
909
- value: hour,
910
- children: [String(hour).padStart(2, "0"), " 时"]
911
- }, hour))
912
- }),
913
- (0, react_jsx_runtime.jsx)("select", {
914
- className: TokenQuotaPanel_module_css_default.resetSelect,
915
- value: reset?.minute ?? 0,
916
- onChange: (event) => {
917
- setReset({
918
- offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
919
- hour: reset?.hour ?? 0,
920
- minute: Number(event.target.value)
921
- });
922
- },
923
- children: Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => (0, react_jsx_runtime.jsxs)("option", {
924
- value: minute,
925
- children: [String(minute).padStart(2, "0"), " 分"]
926
- }, minute))
927
- })
928
- ]
929
- })
930
- ]
931
- }),
932
- (0, react_jsx_runtime.jsxs)("div", {
933
- className: TokenQuotaPanel_module_css_default.dialogSection,
934
- children: [(0, react_jsx_runtime.jsxs)("label", {
935
- className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
936
- children: [(0, react_jsx_runtime.jsx)("input", {
937
- type: "checkbox",
938
- checked: dimWhenIdle,
939
- onChange: (event) => {
940
- setDimWhenIdle(event.target.checked);
941
- }
942
- }), (0, react_jsx_runtime.jsx)("span", { children: t("dimWhenIdleLabel") })]
943
- }), (0, react_jsx_runtime.jsx)("div", {
944
- className: TokenQuotaPanel_module_css_default.dialogHint,
945
- children: t("dimWhenIdleHint")
823
+ className: TokenQuotaPanel_module_css_default.dialogTabs,
824
+ role: "tablist",
825
+ children: [(0, react_jsx_runtime.jsx)("button", {
826
+ type: "button",
827
+ role: "tab",
828
+ "aria-selected": settingsTab === "quota",
829
+ className: `${TokenQuotaPanel_module_css_default.dialogTab}${settingsTab === "quota" ? ` ${TokenQuotaPanel_module_css_default.dialogTabActive}` : ""}`,
830
+ onClick: () => {
831
+ setSettingsTab("quota");
832
+ },
833
+ children: t("settingsTabQuota")
834
+ }), (0, react_jsx_runtime.jsx)("button", {
835
+ type: "button",
836
+ role: "tab",
837
+ "aria-selected": settingsTab === "music",
838
+ className: `${TokenQuotaPanel_module_css_default.dialogTab}${settingsTab === "music" ? ` ${TokenQuotaPanel_module_css_default.dialogTabActive}` : ""}`,
839
+ onClick: () => {
840
+ setSettingsTab("music");
841
+ },
842
+ children: t("settingsTabMusic")
946
843
  })]
947
844
  }),
948
- (0, react_jsx_runtime.jsxs)("div", {
845
+ settingsTab === "quota" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
846
+ (0, react_jsx_runtime.jsxs)("div", {
847
+ className: TokenQuotaPanel_module_css_default.dialogSection,
848
+ children: [
849
+ (0, react_jsx_runtime.jsx)("div", {
850
+ className: TokenQuotaPanel_module_css_default.dialogLabel,
851
+ children: t("monitorLabel")
852
+ }),
853
+ (0, react_jsx_runtime.jsx)("div", {
854
+ className: TokenQuotaPanel_module_css_default.monitorHint,
855
+ children: t("monitorHint")
856
+ }),
857
+ (0, react_jsx_runtime.jsx)("div", {
858
+ className: TokenQuotaPanel_module_css_default.monitorList,
859
+ children: allModels.map((model) => (0, react_jsx_runtime.jsxs)("label", {
860
+ className: TokenQuotaPanel_module_css_default.monitorRow,
861
+ children: [(0, react_jsx_runtime.jsx)("input", {
862
+ type: "checkbox",
863
+ checked: monitored === null || monitored.includes(model.key),
864
+ onChange: (event) => {
865
+ const base = monitored === null ? allModels.map((m) => m.key) : monitored;
866
+ const next = new Set(base);
867
+ if (event.target.checked) next.add(model.key);
868
+ else next.delete(model.key);
869
+ setMonitored(next.size === allModels.length ? null : [...next]);
870
+ }
871
+ }), (0, react_jsx_runtime.jsx)("span", {
872
+ className: TokenQuotaPanel_module_css_default.monitorName,
873
+ title: model.key,
874
+ children: model.name
875
+ })]
876
+ }, model.key))
877
+ })
878
+ ]
879
+ }),
880
+ (0, react_jsx_runtime.jsxs)("div", {
881
+ className: TokenQuotaPanel_module_css_default.dialogSection,
882
+ children: [(0, react_jsx_runtime.jsx)("div", {
883
+ className: TokenQuotaPanel_module_css_default.dialogLabel,
884
+ children: t("fullActionLabel")
885
+ }), FULL_ACTIONS.map((action) => (0, react_jsx_runtime.jsxs)("label", {
886
+ className: TokenQuotaPanel_module_css_default.radioRow,
887
+ children: [(0, react_jsx_runtime.jsx)("input", {
888
+ type: "radio",
889
+ name: "token-quota-onfull",
890
+ checked: onFull === action.value,
891
+ onChange: () => {
892
+ setOnFull(action.value);
893
+ }
894
+ }), (0, react_jsx_runtime.jsx)("span", { children: t(action.labelKey) })]
895
+ }, action.value))]
896
+ }),
897
+ (0, react_jsx_runtime.jsxs)("div", {
898
+ className: TokenQuotaPanel_module_css_default.dialogSection,
899
+ children: [
900
+ (0, react_jsx_runtime.jsx)("div", {
901
+ className: TokenQuotaPanel_module_css_default.dialogLabel,
902
+ children: t("resetLabel")
903
+ }),
904
+ (0, react_jsx_runtime.jsx)("div", {
905
+ className: TokenQuotaPanel_module_css_default.monitorHint,
906
+ children: t("resetHint")
907
+ }),
908
+ (0, react_jsx_runtime.jsxs)("div", {
909
+ className: TokenQuotaPanel_module_css_default.resetRow,
910
+ children: [
911
+ (0, react_jsx_runtime.jsx)("select", {
912
+ className: TokenQuotaPanel_module_css_default.resetSelect,
913
+ value: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
914
+ onChange: (event) => {
915
+ setReset({
916
+ offsetHours: Number(event.target.value),
917
+ hour: reset?.hour ?? 0,
918
+ minute: reset?.minute ?? 0
919
+ });
920
+ },
921
+ children: Array.from({ length: 27 }, (_, i) => i - 12).map((offset) => (0, react_jsx_runtime.jsxs)("option", {
922
+ value: offset,
923
+ children: ["UTC", offset >= 0 ? `+${offset}` : offset]
924
+ }, offset))
925
+ }),
926
+ (0, react_jsx_runtime.jsx)("select", {
927
+ className: TokenQuotaPanel_module_css_default.resetSelect,
928
+ value: reset?.hour ?? 0,
929
+ onChange: (event) => {
930
+ setReset({
931
+ offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
932
+ hour: Number(event.target.value),
933
+ minute: reset?.minute ?? 0
934
+ });
935
+ },
936
+ children: Array.from({ length: 24 }, (_, i) => i).map((hour) => (0, react_jsx_runtime.jsxs)("option", {
937
+ value: hour,
938
+ children: [String(hour).padStart(2, "0"), " 时"]
939
+ }, hour))
940
+ }),
941
+ (0, react_jsx_runtime.jsx)("select", {
942
+ className: TokenQuotaPanel_module_css_default.resetSelect,
943
+ value: reset?.minute ?? 0,
944
+ onChange: (event) => {
945
+ setReset({
946
+ offsetHours: reset?.offsetHours ?? -(/* @__PURE__ */ new Date()).getTimezoneOffset() / 60,
947
+ hour: reset?.hour ?? 0,
948
+ minute: Number(event.target.value)
949
+ });
950
+ },
951
+ children: Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => (0, react_jsx_runtime.jsxs)("option", {
952
+ value: minute,
953
+ children: [String(minute).padStart(2, "0"), " 分"]
954
+ }, minute))
955
+ })
956
+ ]
957
+ })
958
+ ]
959
+ }),
960
+ (0, react_jsx_runtime.jsxs)("div", {
961
+ className: TokenQuotaPanel_module_css_default.dialogSection,
962
+ children: [(0, react_jsx_runtime.jsxs)("label", {
963
+ className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
964
+ children: [(0, react_jsx_runtime.jsx)("input", {
965
+ type: "checkbox",
966
+ checked: dimWhenIdle,
967
+ onChange: (event) => {
968
+ setDimWhenIdle(event.target.checked);
969
+ }
970
+ }), (0, react_jsx_runtime.jsx)("span", { children: t("dimWhenIdleLabel") })]
971
+ }), (0, react_jsx_runtime.jsx)("div", {
972
+ className: TokenQuotaPanel_module_css_default.dialogHint,
973
+ children: t("dimWhenIdleHint")
974
+ })]
975
+ })
976
+ ] }),
977
+ settingsTab === "music" && (0, react_jsx_runtime.jsxs)("div", {
949
978
  className: TokenQuotaPanel_module_css_default.dialogSection,
950
979
  children: [
951
980
  (0, react_jsx_runtime.jsxs)("label", {
@@ -1016,7 +1045,7 @@ window.__ModuleLoader__.load({
1016
1045
  ] })
1017
1046
  ]
1018
1047
  }),
1019
- (0, react_jsx_runtime.jsxs)("div", {
1048
+ settingsTab === "quota" && (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: (0, react_jsx_runtime.jsxs)("div", {
1020
1049
  className: TokenQuotaPanel_module_css_default.dialogSection,
1021
1050
  children: [(0, react_jsx_runtime.jsxs)("div", {
1022
1051
  className: TokenQuotaPanel_module_css_default.checkUpdatesRow,
@@ -1041,7 +1070,7 @@ window.__ModuleLoader__.load({
1041
1070
  role: "alert",
1042
1071
  children: upgradeError
1043
1072
  })]
1044
- })
1073
+ }) })
1045
1074
  ]
1046
1075
  }),
1047
1076
  logOpen && (0, react_jsx_runtime.jsxs)("div", {
@@ -1584,7 +1613,7 @@ window.__ModuleLoader__.load({
1584
1613
  break;
1585
1614
  case "request/header":
1586
1615
  if (!this.playing) this.startPiece();
1587
- else this.pluck(base + 24, .3);
1616
+ else this.pluck(this.tone(5, 1), .3, .9);
1588
1617
  break;
1589
1618
  case "step/start": {
1590
1619
  const prog = PROGRESSION[this.style];
@@ -1596,31 +1625,51 @@ window.__ModuleLoader__.load({
1596
1625
  break;
1597
1626
  }
1598
1627
  case "tool/call":
1599
- this.pluck(base + 26, .45, .5);
1600
- this.pluck(base - 12, .3, .35);
1628
+ this.pluck(this.tone(4, 1), .38, .9);
1629
+ setTimeout(() => this.pluck(this.tone(5, 1), .34, .9), 90);
1630
+ this.pluck(this.tonicRoot - 12, .22, .9);
1601
1631
  break;
1602
1632
  case "tool/result":
1603
1633
  if (action.error) {
1604
- this.pluck(base + 13, .4, .6);
1605
- setTimeout(() => this.pluck(base + 12, .4, .9), 90);
1634
+ const t = this.tone(2, 1);
1635
+ this.pluck(t - 1, .4, .7);
1636
+ setTimeout(() => this.pluck(t - 3, .4, 1), 110);
1606
1637
  } else {
1607
- this.pluck(base + 9, .35, .6);
1608
- setTimeout(() => this.pluck(base + 11, .35, .9), 70);
1638
+ this.pluck(this.tone(3, 1), .34, 1);
1639
+ setTimeout(() => this.pluck(this.tone(1, 1), .32, 1), 90);
1640
+ setTimeout(() => this.pluck(this.tone(0, 1), .34, 1.2), 180);
1609
1641
  }
1610
1642
  break;
1611
1643
  case "assistant/attempt":
1612
- this.pluck(base + 8, .4, .8);
1613
- setTimeout(() => this.pluck(base + 7, .4, .8), 110);
1614
- setTimeout(() => this.pluck(base + 5, .4, .8), 220);
1644
+ this.pluck(this.tone(4, 1), .36, .8);
1645
+ setTimeout(() => this.pluck(this.tone(2, 1), .36, .9), 110);
1646
+ setTimeout(() => this.pluck(this.tone(0), .36, 1.1), 220);
1615
1647
  break;
1616
1648
  case "assistant/message":
1617
- this.breath(action.interrupted ? base + 6 : base + 12, 1.6);
1649
+ this.pluck(this.tone(2, 1), .3, 1);
1650
+ setTimeout(() => this.breath(this.tone(0, action.interrupted ? 0 : 1), 1.4), 140);
1618
1651
  break;
1619
1652
  case "turn/end":
1620
1653
  this.stopPiece(true);
1621
1654
  break;
1622
1655
  }
1623
1656
  }
1657
+ /** Current tonic root in MIDI note numbers. */
1658
+ get tonicRoot() {
1659
+ return TokenQuotaMusic.BASE_MIDI + (PROGRESSION[this.style][this.tonicIndex] ?? 0);
1660
+ }
1661
+ /**
1662
+ * A scale tone relative to the current tonic — degrees wrap up octaves
1663
+ * (deg 5 on a 5-note scale = the octave). Event figures use this so every
1664
+ * accent stays inside the key and melts into the piece instead of
1665
+ * clashing with it.
1666
+ */
1667
+ tone(deg, oct = 0) {
1668
+ const scale = SCALES[this.style];
1669
+ const len = scale.length;
1670
+ const d = (deg % len + len) % len;
1671
+ return this.tonicRoot + 12 * (Math.floor(deg / len) + oct) + (scale[d] ?? 0);
1672
+ }
1624
1673
  /** Start a new piece: intro run, then the phrase chain. */
1625
1674
  startPiece() {
1626
1675
  if (!this.audio || this.playing) return;
@@ -1852,7 +1901,9 @@ window.__ModuleLoader__.load({
1852
1901
  expand: "展开",
1853
1902
  expandHint: "展开每日 Token 限额面板",
1854
1903
  settings: "设置",
1855
- settingsTitle: "限额设置",
1904
+ settingsTitle: "设置",
1905
+ settingsTabQuota: "限额设置",
1906
+ settingsTabMusic: "音乐设置",
1856
1907
  monitorLabel: "监控模型",
1857
1908
  monitorHint: "未勾选的模型不显示、不计入用量、不受限额限制",
1858
1909
  monitorAll: "全部",
@@ -1930,7 +1981,9 @@ window.__ModuleLoader__.load({
1930
1981
  expand: "Expand",
1931
1982
  expandHint: "Expand the daily token quota panel",
1932
1983
  settings: "Settings",
1933
- settingsTitle: "Quota Settings",
1984
+ settingsTitle: "Settings",
1985
+ settingsTabQuota: "Quota",
1986
+ settingsTabMusic: "Music",
1934
1987
  monitorLabel: "Monitored models",
1935
1988
  monitorHint: "Unchecked models are hidden, not metered, and never capped",
1936
1989
  monitorAll: "All",
package/lib/index.js CHANGED
@@ -515,7 +515,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
515
515
  return this.upgradeCheck;
516
516
  }
517
517
  async doRefreshUpgrade() {
518
- const current = "0.1.29";
518
+ const current = "0.1.30";
519
519
  let latest;
520
520
  try {
521
521
  const response = await fetch(REGISTRY_LATEST_URL, {
@@ -95,6 +95,8 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
95
95
  // so the chat content behind it stays readable; hovering restores it.
96
96
  const [hovered, setHovered] = useState(false);
97
97
  const [inputActive, setInputActive] = useState(false);
98
+ // Active settings-dialog tab: quota limits vs. live-music options.
99
+ const [settingsTab, setSettingsTab] = useState('quota');
98
100
  // Copy-button feedback: which command was just copied (label flips to
99
101
  // 「已复制」for a moment so the click is perceivable).
100
102
  const [copiedCmd, setCopiedCmd] = useState(null);
@@ -311,40 +313,40 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
311
313
  ? `${formatTokens(row.used)} / ${formatTokens(row.limit)}`
312
314
  : `${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')
313
315
  commitLimit(row); } }), _jsx("button", { type: "button", className: css.save, onClick: () => { commitLimit(row); }, children: t('save') })] }))] }, row.key));
314
- })] }), 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) => {
315
- const base = monitored === null ? allModels.map(m => m.key) : monitored;
316
- const next = new Set(base);
317
- if (event.target.checked)
318
- next.add(model.key);
319
- else
320
- next.delete(model.key);
321
- // Auto-save on every change; all selected stores as null.
322
- setMonitored(next.size === allModels.length ? null : [...next]);
323
- } }), _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) => {
324
- setReset({
325
- offsetHours: Number(event.target.value),
326
- hour: reset?.hour ?? 0,
327
- minute: reset?.minute ?? 0,
328
- });
329
- }, 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) => {
330
- setReset({
331
- offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
332
- hour: Number(event.target.value),
333
- minute: reset?.minute ?? 0,
334
- });
335
- }, 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) => {
336
- setReset({
337
- offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
338
- hour: reset?.hour ?? 0,
339
- minute: Number(event.target.value),
340
- });
341
- }, 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.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: dimWhenIdle, onChange: (event) => { setDimWhenIdle(event.target.checked); } }), _jsx("span", { children: t('dimWhenIdleLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('dimWhenIdleHint') })] }), _jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: musicEnabled, onChange: (event) => { setMusicEnabled(event.target.checked); } }), _jsx("span", { children: t('musicLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('musicHint') }), musicEnabled && (_jsxs(_Fragment, { children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicVolume') }), _jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: musicVolume, onChange: (event) => { setMusicVolume(Number(event.target.value)); } })] }), _jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicStyle') }), _jsxs("select", { value: musicStyle, onChange: (event) => { setMusicStyle(event.target.value); }, children: [_jsx("option", { value: "major", children: t('musicStyleMajor') }), _jsx("option", { value: "minor", children: t('musicStyleMinor') }), _jsx("option", { value: "pentatonic", children: t('musicStylePentatonic') })] })] }), _jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: musicOnlyCurrentSession, onChange: (event) => { setMusicOnlyCurrentSession(event.target.checked); } }), _jsx("span", { children: t('musicOnlyCurrentSessionLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('musicOnlyCurrentSessionHint') })] }))] }), _jsxs("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
342
- ? t('checkingUpdates')
343
- : lastCheckResult === 'up-to-date'
344
- ? t('checkUpToDate')
345
- : lastCheckResult === 'error'
346
- ? t('checkFailed')
347
- : t('checkUpdatesNow') })] }), lastCheckResult === 'error' && upgradeError !== null && (_jsx("div", { className: css.checkErrorHint, role: "alert", children: upgradeError }))] })] })), 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')
316
+ })] }), 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.dialogTabs, role: "tablist", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": settingsTab === 'quota', className: `${css.dialogTab}${settingsTab === 'quota' ? ` ${css.dialogTabActive}` : ''}`, onClick: () => { setSettingsTab('quota'); }, children: t('settingsTabQuota') }), _jsx("button", { type: "button", role: "tab", "aria-selected": settingsTab === 'music', className: `${css.dialogTab}${settingsTab === 'music' ? ` ${css.dialogTabActive}` : ''}`, onClick: () => { setSettingsTab('music'); }, children: t('settingsTabMusic') })] }), settingsTab === 'quota' && (_jsxs(_Fragment, { children: [_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) => {
317
+ const base = monitored === null ? allModels.map(m => m.key) : monitored;
318
+ const next = new Set(base);
319
+ if (event.target.checked)
320
+ next.add(model.key);
321
+ else
322
+ next.delete(model.key);
323
+ // Auto-save on every change; all selected stores as null.
324
+ setMonitored(next.size === allModels.length ? null : [...next]);
325
+ } }), _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) => {
326
+ setReset({
327
+ offsetHours: Number(event.target.value),
328
+ hour: reset?.hour ?? 0,
329
+ minute: reset?.minute ?? 0,
330
+ });
331
+ }, 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) => {
332
+ setReset({
333
+ offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
334
+ hour: Number(event.target.value),
335
+ minute: reset?.minute ?? 0,
336
+ });
337
+ }, 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) => {
338
+ setReset({
339
+ offsetHours: reset?.offsetHours ?? -new Date().getTimezoneOffset() / 60,
340
+ hour: reset?.hour ?? 0,
341
+ minute: Number(event.target.value),
342
+ });
343
+ }, 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.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: dimWhenIdle, onChange: (event) => { setDimWhenIdle(event.target.checked); } }), _jsx("span", { children: t('dimWhenIdleLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('dimWhenIdleHint') })] })] })), settingsTab === 'music' && (_jsxs("div", { className: css.dialogSection, children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: musicEnabled, onChange: (event) => { setMusicEnabled(event.target.checked); } }), _jsx("span", { children: t('musicLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('musicHint') }), musicEnabled && (_jsxs(_Fragment, { children: [_jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicVolume') }), _jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: musicVolume, onChange: (event) => { setMusicVolume(Number(event.target.value)); } })] }), _jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("span", { children: t('musicStyle') }), _jsxs("select", { value: musicStyle, onChange: (event) => { setMusicStyle(event.target.value); }, children: [_jsx("option", { value: "major", children: t('musicStyleMajor') }), _jsx("option", { value: "minor", children: t('musicStyleMinor') }), _jsx("option", { value: "pentatonic", children: t('musicStylePentatonic') })] })] }), _jsxs("label", { className: css.checkUpdatesLabel, children: [_jsx("input", { type: "checkbox", checked: musicOnlyCurrentSession, onChange: (event) => { setMusicOnlyCurrentSession(event.target.checked); } }), _jsx("span", { children: t('musicOnlyCurrentSessionLabel') })] }), _jsx("div", { className: css.dialogHint, children: t('musicOnlyCurrentSessionHint') })] }))] })), settingsTab === 'quota' && (_jsx(_Fragment, { children: _jsxs("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
344
+ ? t('checkingUpdates')
345
+ : lastCheckResult === 'up-to-date'
346
+ ? t('checkUpToDate')
347
+ : lastCheckResult === 'error'
348
+ ? t('checkFailed')
349
+ : t('checkUpdatesNow') })] }), lastCheckResult === 'error' && upgradeError !== null && (_jsx("div", { className: css.checkErrorHint, role: "alert", children: upgradeError }))] }) }))] })), 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')
348
350
  .replace('{from}', String(logCurrentPage * logPageSize + 1))
349
351
  .replace('{to}', String(Math.min((logCurrentPage + 1) * logPageSize, filteredLogEntries.length)))
350
352
  .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') })] })] }) }))] }));
@@ -25,6 +25,8 @@ export declare const zh: {
25
25
  expandHint: string;
26
26
  settings: string;
27
27
  settingsTitle: string;
28
+ settingsTabQuota: string;
29
+ settingsTabMusic: string;
28
30
  monitorLabel: string;
29
31
  monitorHint: string;
30
32
  monitorAll: string;
@@ -105,6 +107,8 @@ export declare const en: {
105
107
  expandHint: string;
106
108
  settings: string;
107
109
  settingsTitle: string;
110
+ settingsTabQuota: string;
111
+ settingsTabMusic: string;
108
112
  monitorLabel: string;
109
113
  monitorHint: string;
110
114
  monitorAll: string;
@@ -24,7 +24,9 @@ export const zh = {
24
24
  expand: '展开',
25
25
  expandHint: '展开每日 Token 限额面板',
26
26
  settings: '设置',
27
- settingsTitle: '限额设置',
27
+ settingsTitle: '设置',
28
+ settingsTabQuota: '限额设置',
29
+ settingsTabMusic: '音乐设置',
28
30
  monitorLabel: '监控模型',
29
31
  monitorHint: '未勾选的模型不显示、不计入用量、不受限额限制',
30
32
  monitorAll: '全部',
@@ -102,7 +104,9 @@ export const en = {
102
104
  expand: 'Expand',
103
105
  expandHint: 'Expand the daily token quota panel',
104
106
  settings: 'Settings',
105
- settingsTitle: 'Quota Settings',
107
+ settingsTitle: 'Settings',
108
+ settingsTabQuota: 'Quota',
109
+ settingsTabMusic: 'Music',
106
110
  monitorLabel: 'Monitored models',
107
111
  monitorHint: 'Unchecked models are hidden, not metered, and never capped',
108
112
  monitorAll: 'All',
@@ -70,6 +70,15 @@ export declare class TokenQuotaMusic {
70
70
  stop(): void;
71
71
  /** Handle one incoming host action and steer the piece. */
72
72
  onAction(action: TokenQuotaMusicAction): void;
73
+ /** Current tonic root in MIDI note numbers. */
74
+ private get tonicRoot();
75
+ /**
76
+ * A scale tone relative to the current tonic — degrees wrap up octaves
77
+ * (deg 5 on a 5-note scale = the octave). Event figures use this so every
78
+ * accent stays inside the key and melts into the piece instead of
79
+ * clashing with it.
80
+ */
81
+ private tone;
73
82
  /** Start a new piece: intro run, then the phrase chain. */
74
83
  private startPiece;
75
84
  /** Play one phrase, then chain into the next after a one-beat rest. */
@@ -194,7 +194,7 @@ export class TokenQuotaMusic {
194
194
  if (!this.playing)
195
195
  this.startPiece();
196
196
  else
197
- this.pluck(base + 24, 0.3);
197
+ this.pluck(this.tone(5, 1), 0.3, 0.9);
198
198
  break;
199
199
  }
200
200
  case 'step/start': {
@@ -207,40 +207,67 @@ export class TokenQuotaMusic {
207
207
  }
208
208
  break;
209
209
  }
210
- case 'tool/call':
211
- // High plink + low tap — distinct from the plucked melody.
212
- this.pluck(base + 26, 0.45, 0.5);
213
- this.pluck(base - 12, 0.3, 0.35);
210
+ case 'tool/call': {
211
+ // Rising two-note figure drawn from the current scale (so it blends
212
+ // with the piece, not pokes out of it) over a soft low root for body.
213
+ this.pluck(this.tone(4, 1), 0.38, 0.9);
214
+ setTimeout(() => this.pluck(this.tone(5, 1), 0.34, 0.9), 90);
215
+ this.pluck(this.tonicRoot - 12, 0.22, 0.9);
214
216
  break;
217
+ }
215
218
  case 'tool/result': {
216
219
  if (action.error) {
217
- this.pluck(base + 13, 0.4, 0.6);
218
- setTimeout(() => this.pluck(base + 12, 0.4, 0.9), 90);
220
+ // Deliberately sour (an error should feel off): a half-step below a
221
+ // scale tone, then sinking further.
222
+ const t = this.tone(2, 1);
223
+ this.pluck(t - 1, 0.4, 0.7);
224
+ setTimeout(() => this.pluck(t - 3, 0.4, 1.0), 110);
219
225
  }
220
226
  else {
221
- this.pluck(base + 9, 0.35, 0.6);
222
- setTimeout(() => this.pluck(base + 11, 0.35, 0.9), 70);
227
+ // Gentle in-key resolve landing on the tonic octave.
228
+ this.pluck(this.tone(3, 1), 0.34, 1.0);
229
+ setTimeout(() => this.pluck(this.tone(1, 1), 0.32, 1.0), 90);
230
+ setTimeout(() => this.pluck(this.tone(0, 1), 0.34, 1.2), 180);
223
231
  }
224
232
  break;
225
233
  }
226
- case 'assistant/attempt':
227
- // Failed attempt: falling tension figure.
228
- this.pluck(base + 8, 0.4, 0.8);
229
- setTimeout(() => this.pluck(base + 7, 0.4, 0.8), 110);
230
- setTimeout(() => this.pluck(base + 5, 0.4, 0.8), 220);
234
+ case 'assistant/attempt': {
235
+ // Failed attempt: falling in-key tension figure.
236
+ this.pluck(this.tone(4, 1), 0.36, 0.8);
237
+ setTimeout(() => this.pluck(this.tone(2, 1), 0.36, 0.9), 110);
238
+ setTimeout(() => this.pluck(this.tone(0), 0.36, 1.1), 220);
231
239
  break;
232
- case 'assistant/message':
233
- // Sentence end: a soft breath cadence, but the bed keeps playing
234
- // until the turn fully ends (turn/end), so thinking + streaming stay
235
- // audible the whole way through.
236
- this.breath(action.interrupted ? base + 6 : base + 12, 1.6);
240
+ }
241
+ case 'assistant/message': {
242
+ // Sentence end: a short cadence figure, then a breath on the tonic
243
+ // octave (in key — the old fixed C clashed with the mode rotation).
244
+ // The bed keeps playing until the turn fully ends (turn/end).
245
+ this.pluck(this.tone(2, 1), 0.3, 1.0);
246
+ setTimeout(() => this.breath(this.tone(0, action.interrupted ? 0 : 1), 1.4), 140);
237
247
  break;
248
+ }
238
249
  case 'turn/end':
239
250
  this.stopPiece(true);
240
251
  break;
241
252
  }
242
253
  }
243
254
  // ── piece lifecycle ───────────────────────────────────────────────────
255
+ /** Current tonic root in MIDI note numbers. */
256
+ get tonicRoot() {
257
+ return TokenQuotaMusic.BASE_MIDI + (PROGRESSION[this.style][this.tonicIndex] ?? 0);
258
+ }
259
+ /**
260
+ * A scale tone relative to the current tonic — degrees wrap up octaves
261
+ * (deg 5 on a 5-note scale = the octave). Event figures use this so every
262
+ * accent stays inside the key and melts into the piece instead of
263
+ * clashing with it.
264
+ */
265
+ tone(deg, oct = 0) {
266
+ const scale = SCALES[this.style];
267
+ const len = scale.length;
268
+ const d = ((deg % len) + len) % len;
269
+ return this.tonicRoot + 12 * (Math.floor(deg / len) + oct) + (scale[d] ?? 0);
270
+ }
244
271
  /** Start a new piece: intro run, then the phrase chain. */
245
272
  startPiece() {
246
273
  if (!this.audio || this.playing)
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.29",
4
+ "version": "0.1.30",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },