@jxgame2020/dsh-token-quota 0.1.29 → 0.1.31

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.31" ? 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_settingsDialog{height:min(560px,80vh)}.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");
@@ -201,92 +201,96 @@ window.__ModuleLoader__.load({
201
201
  document.head.appendChild(tag);
202
202
  }
203
203
  var TokenQuotaPanel_module_css_default = {
204
- "controls": "NPLLIq_controls",
205
- "logModelCol": "NPLLIq_logModelCol",
204
+ "body": "NPLLIq_body",
205
+ "bar": "NPLLIq_bar",
206
+ "rowName": "NPLLIq_rowName",
207
+ "rowHeader": "NPLLIq_rowHeader",
208
+ "gearBtn": "NPLLIq_gearBtn",
209
+ "radioRow": "NPLLIq_radioRow",
206
210
  "resetRow": "NPLLIq_resetRow",
211
+ "logModelCol": "NPLLIq_logModelCol",
212
+ "logClearDialog": "NPLLIq_logClearDialog",
213
+ "upgradeClose": "NPLLIq_upgradeClose",
214
+ "subtitle": "NPLLIq_subtitle",
215
+ "fillWarn": "NPLLIq_fillWarn",
207
216
  "noticeError": "NPLLIq_noticeError",
208
- "selectBtn": "NPLLIq_selectBtn",
209
- "monitorList": "NPLLIq_monitorList",
210
217
  "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
- "gearBtn": "NPLLIq_gearBtn",
220
- "panelDimStrong": "NPLLIq_panelDimStrong",
221
- "logClearDate": "NPLLIq_logClearDate",
218
+ "logDialog": "NPLLIq_logDialog",
219
+ "dialogLabel": "NPLLIq_dialogLabel",
222
220
  "settingsBtn": "NPLLIq_settingsBtn",
223
- "copyBtn": "NPLLIq_copyBtn",
221
+ "dialogHeader": "NPLLIq_dialogHeader",
222
+ "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
223
+ "copyBtnDone": "NPLLIq_copyBtnDone",
224
+ "upgradeBanner": "NPLLIq_upgradeBanner",
225
+ "logScroll": "NPLLIq_logScroll",
226
+ "logDayCol": "NPLLIq_logDayCol",
224
227
  "logPager": "NPLLIq_logPager",
225
- "tab": "NPLLIq_tab",
226
- "tabIcon": "NPLLIq_tabIcon",
227
- "dialog": "NPLLIq_dialog",
228
228
  "upgradeCommands": "NPLLIq_upgradeCommands",
229
- "rowMeta": "NPLLIq_rowMeta",
230
- "version": "NPLLIq_version",
229
+ "fillOver": "NPLLIq_fillOver",
231
230
  "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
232
- "dialogTitle": "NPLLIq_dialogTitle",
233
231
  "headerText": "NPLLIq_headerText",
234
- "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
- "row": "NPLLIq_row",
245
- "logToolbar": "NPLLIq_logToolbar",
246
- "logSearch": "NPLLIq_logSearch",
247
232
  "switch": "NPLLIq_switch",
248
- "checkBtn": "NPLLIq_checkBtn",
249
- "dialogSection": "NPLLIq_dialogSection",
250
- "bar": "NPLLIq_bar",
251
- "logPagerPages": "NPLLIq_logPagerPages",
233
+ "dialogHint": "NPLLIq_dialogHint",
234
+ "dialogOverlay": "NPLLIq_dialogOverlay",
235
+ "version": "NPLLIq_version",
252
236
  "input": "NPLLIq_input",
253
- "rowHeader": "NPLLIq_rowHeader",
254
- "copyBtnDone": "NPLLIq_copyBtnDone",
255
- "dialogClose": "NPLLIq_dialogClose",
256
- "logScroll": "NPLLIq_logScroll",
257
- "versionNew": "NPLLIq_versionNew",
258
- "header": "NPLLIq_header",
259
- "upgradeCommand": "NPLLIq_upgradeCommand",
260
237
  "logPagerControls": "NPLLIq_logPagerControls",
261
- "currentBadge": "NPLLIq_currentBadge",
262
- "tabLabel": "NPLLIq_tabLabel",
238
+ "settingsDialog": "NPLLIq_settingsDialog",
263
239
  "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",
240
+ "panelDim": "NPLLIq_panelDim",
241
+ "settingsBtnActive": "NPLLIq_settingsBtnActive",
242
+ "versionNew": "NPLLIq_versionNew",
243
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
244
+ "dialogTabActive": "NPLLIq_dialogTabActive",
245
+ "copyBtn": "NPLLIq_copyBtn",
246
+ "headerActions": "NPLLIq_headerActions",
247
+ "logSearch": "NPLLIq_logSearch",
271
248
  "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
272
- "logUsedCol": "NPLLIq_logUsedCol",
249
+ "dialogTab": "NPLLIq_dialogTab",
250
+ "upgradeHint": "NPLLIq_upgradeHint",
273
251
  "collapse": "NPLLIq_collapse",
274
- "dialogOverlay": "NPLLIq_dialogOverlay",
275
- "radioRow": "NPLLIq_radioRow",
276
- "dialogHeader": "NPLLIq_dialogHeader",
252
+ "dialogTabs": "NPLLIq_dialogTabs",
277
253
  "title": "NPLLIq_title",
278
- "logTable": "NPLLIq_logTable",
254
+ "controls": "NPLLIq_controls",
255
+ "logClearBtn": "NPLLIq_logClearBtn",
256
+ "header": "NPLLIq_header",
257
+ "logPagerInfo": "NPLLIq_logPagerInfo",
258
+ "logPagerPages": "NPLLIq_logPagerPages",
259
+ "dialogClose": "NPLLIq_dialogClose",
260
+ "logHeader": "NPLLIq_logHeader",
261
+ "notice": "NPLLIq_notice",
262
+ "dialogBody": "NPLLIq_dialogBody",
263
+ "dialog": "NPLLIq_dialog",
264
+ "panelDimStrong": "NPLLIq_panelDimStrong",
265
+ "logClearDate": "NPLLIq_logClearDate",
279
266
  "save": "NPLLIq_save",
267
+ "logUsedCol": "NPLLIq_logUsedCol",
268
+ "monitorName": "NPLLIq_monitorName",
269
+ "monitorRow": "NPLLIq_monitorRow",
270
+ "dialogBtn": "NPLLIq_dialogBtn",
271
+ "dialogActions": "NPLLIq_dialogActions",
272
+ "upgradeBannerText": "NPLLIq_upgradeBannerText",
273
+ "selectBtn": "NPLLIq_selectBtn",
274
+ "logPagerBtn": "NPLLIq_logPagerBtn",
275
+ "upgradeCommand": "NPLLIq_upgradeCommand",
276
+ "row": "NPLLIq_row",
277
+ "currentBadge": "NPLLIq_currentBadge",
278
+ "monitorList": "NPLLIq_monitorList",
279
+ "tabLabel": "NPLLIq_tabLabel",
280
+ "fillIdle": "NPLLIq_fillIdle",
280
281
  "checkErrorHint": "NPLLIq_checkErrorHint",
281
- "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
282
- "dialogLabel": "NPLLIq_dialogLabel",
283
- "logPagerInfo": "NPLLIq_logPagerInfo",
284
- "panelDim": "NPLLIq_panelDim",
285
- "fillWarn": "NPLLIq_fillWarn",
282
+ "dialogTitle": "NPLLIq_dialogTitle",
286
283
  "monitorHint": "NPLLIq_monitorHint",
287
- "body": "NPLLIq_body",
288
- "headerActions": "NPLLIq_headerActions",
289
- "monitorName": "NPLLIq_monitorName"
284
+ "tabIcon": "NPLLIq_tabIcon",
285
+ "panel": "NPLLIq_panel",
286
+ "resetSelect": "NPLLIq_resetSelect",
287
+ "checkBtn": "NPLLIq_checkBtn",
288
+ "logTable": "NPLLIq_logTable",
289
+ "tab": "NPLLIq_tab",
290
+ "rowMeta": "NPLLIq_rowMeta",
291
+ "logToolbar": "NPLLIq_logToolbar",
292
+ "fullNotice": "NPLLIq_fullNotice",
293
+ "dialogSection": "NPLLIq_dialogSection"
290
294
  };
291
295
  //#endregion
292
296
  //#region lib/types/client/TokenQuotaPanel.js
@@ -379,6 +383,7 @@ window.__ModuleLoader__.load({
379
383
  const [editingKey, setEditingKey] = (0, react.useState)(null);
380
384
  const [hovered, setHovered] = (0, react.useState)(false);
381
385
  const [inputActive, setInputActive] = (0, react.useState)(false);
386
+ const [settingsTab, setSettingsTab] = (0, react.useState)("quota");
382
387
  const [copiedCmd, setCopiedCmd] = (0, react.useState)(null);
383
388
  const copyTimerRef = (0, react.useRef)(null);
384
389
  const [upgradeFlash, setUpgradeFlash] = (0, react.useState)(false);
@@ -592,13 +597,13 @@ window.__ModuleLoader__.load({
592
597
  title: t("upgradeAvailableHint"),
593
598
  children: [
594
599
  "v",
595
- "0.1.29",
600
+ "0.1.31",
596
601
  " → v",
597
602
  upgrade.latestVersion
598
603
  ]
599
604
  }) : (0, react_jsx_runtime.jsxs)("span", {
600
605
  className: TokenQuotaPanel_module_css_default.version,
601
- children: ["v", "0.1.29"]
606
+ children: ["v", "0.1.31"]
602
607
  })]
603
608
  }), (0, react_jsx_runtime.jsx)("div", {
604
609
  className: TokenQuotaPanel_module_css_default.subtitle,
@@ -787,7 +792,7 @@ window.__ModuleLoader__.load({
787
792
  }),
788
793
  dialogOpen && (0, react_jsx_runtime.jsxs)("div", {
789
794
  ref: settingsRef,
790
- className: TokenQuotaPanel_module_css_default.dialog,
795
+ className: `${TokenQuotaPanel_module_css_default.dialog} ${TokenQuotaPanel_module_css_default.settingsDialog}`,
791
796
  style: settingsPos !== null ? {
792
797
  left: settingsPos.x,
793
798
  top: settingsPos.y,
@@ -816,6 +821,42 @@ window.__ModuleLoader__.load({
816
821
  })]
817
822
  }),
818
823
  (0, react_jsx_runtime.jsxs)("div", {
824
+ className: TokenQuotaPanel_module_css_default.dialogTabs,
825
+ role: "tablist",
826
+ children: [
827
+ (0, react_jsx_runtime.jsx)("button", {
828
+ type: "button",
829
+ role: "tab",
830
+ "aria-selected": settingsTab === "quota",
831
+ className: `${TokenQuotaPanel_module_css_default.dialogTab}${settingsTab === "quota" ? ` ${TokenQuotaPanel_module_css_default.dialogTabActive}` : ""}`,
832
+ onClick: () => {
833
+ setSettingsTab("quota");
834
+ },
835
+ children: t("settingsTabQuota")
836
+ }),
837
+ (0, react_jsx_runtime.jsx)("button", {
838
+ type: "button",
839
+ role: "tab",
840
+ "aria-selected": settingsTab === "music",
841
+ className: `${TokenQuotaPanel_module_css_default.dialogTab}${settingsTab === "music" ? ` ${TokenQuotaPanel_module_css_default.dialogTabActive}` : ""}`,
842
+ onClick: () => {
843
+ setSettingsTab("music");
844
+ },
845
+ children: t("settingsTabMusic")
846
+ }),
847
+ (0, react_jsx_runtime.jsx)("button", {
848
+ type: "button",
849
+ role: "tab",
850
+ "aria-selected": settingsTab === "other",
851
+ className: `${TokenQuotaPanel_module_css_default.dialogTab}${settingsTab === "other" ? ` ${TokenQuotaPanel_module_css_default.dialogTabActive}` : ""}`,
852
+ onClick: () => {
853
+ setSettingsTab("other");
854
+ },
855
+ children: t("settingsTabOther")
856
+ })
857
+ ]
858
+ }),
859
+ settingsTab === "quota" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
819
860
  className: TokenQuotaPanel_module_css_default.dialogSection,
820
861
  children: [
821
862
  (0, react_jsx_runtime.jsx)("div", {
@@ -848,8 +889,7 @@ window.__ModuleLoader__.load({
848
889
  }, model.key))
849
890
  })
850
891
  ]
851
- }),
852
- (0, react_jsx_runtime.jsxs)("div", {
892
+ }), (0, react_jsx_runtime.jsxs)("div", {
853
893
  className: TokenQuotaPanel_module_css_default.dialogSection,
854
894
  children: [(0, react_jsx_runtime.jsx)("div", {
855
895
  className: TokenQuotaPanel_module_css_default.dialogLabel,
@@ -865,8 +905,8 @@ window.__ModuleLoader__.load({
865
905
  }
866
906
  }), (0, react_jsx_runtime.jsx)("span", { children: t(action.labelKey) })]
867
907
  }, action.value))]
868
- }),
869
- (0, react_jsx_runtime.jsxs)("div", {
908
+ })] }),
909
+ settingsTab === "other" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
870
910
  className: TokenQuotaPanel_module_css_default.dialogSection,
871
911
  children: [
872
912
  (0, react_jsx_runtime.jsx)("div", {
@@ -928,8 +968,7 @@ window.__ModuleLoader__.load({
928
968
  ]
929
969
  })
930
970
  ]
931
- }),
932
- (0, react_jsx_runtime.jsxs)("div", {
971
+ }), (0, react_jsx_runtime.jsxs)("div", {
933
972
  className: TokenQuotaPanel_module_css_default.dialogSection,
934
973
  children: [(0, react_jsx_runtime.jsxs)("label", {
935
974
  className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
@@ -944,79 +983,78 @@ window.__ModuleLoader__.load({
944
983
  className: TokenQuotaPanel_module_css_default.dialogHint,
945
984
  children: t("dimWhenIdleHint")
946
985
  })]
947
- }),
948
- (0, react_jsx_runtime.jsxs)("div", {
986
+ })] }),
987
+ settingsTab === "music" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
988
+ className: TokenQuotaPanel_module_css_default.dialogSection,
989
+ children: [(0, react_jsx_runtime.jsxs)("label", {
990
+ className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
991
+ children: [(0, react_jsx_runtime.jsx)("input", {
992
+ type: "checkbox",
993
+ checked: musicEnabled,
994
+ onChange: (event) => {
995
+ setMusicEnabled(event.target.checked);
996
+ }
997
+ }), (0, react_jsx_runtime.jsx)("span", { children: t("musicLabel") })]
998
+ }), (0, react_jsx_runtime.jsx)("div", {
999
+ className: TokenQuotaPanel_module_css_default.dialogHint,
1000
+ children: t("musicHint")
1001
+ })]
1002
+ }), musicEnabled && (0, react_jsx_runtime.jsxs)("div", {
949
1003
  className: TokenQuotaPanel_module_css_default.dialogSection,
950
1004
  children: [
1005
+ (0, react_jsx_runtime.jsxs)("label", {
1006
+ className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
1007
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("musicVolume") }), (0, react_jsx_runtime.jsx)("input", {
1008
+ type: "range",
1009
+ min: 0,
1010
+ max: 1,
1011
+ step: .05,
1012
+ value: musicVolume,
1013
+ onChange: (event) => {
1014
+ setMusicVolume(Number(event.target.value));
1015
+ }
1016
+ })]
1017
+ }),
1018
+ (0, react_jsx_runtime.jsxs)("label", {
1019
+ className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
1020
+ children: [(0, react_jsx_runtime.jsx)("span", { children: t("musicStyle") }), (0, react_jsx_runtime.jsxs)("select", {
1021
+ value: musicStyle,
1022
+ onChange: (event) => {
1023
+ setMusicStyle(event.target.value);
1024
+ },
1025
+ children: [
1026
+ (0, react_jsx_runtime.jsx)("option", {
1027
+ value: "major",
1028
+ children: t("musicStyleMajor")
1029
+ }),
1030
+ (0, react_jsx_runtime.jsx)("option", {
1031
+ value: "minor",
1032
+ children: t("musicStyleMinor")
1033
+ }),
1034
+ (0, react_jsx_runtime.jsx)("option", {
1035
+ value: "pentatonic",
1036
+ children: t("musicStylePentatonic")
1037
+ })
1038
+ ]
1039
+ })]
1040
+ }),
951
1041
  (0, react_jsx_runtime.jsxs)("label", {
952
1042
  className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
953
1043
  children: [(0, react_jsx_runtime.jsx)("input", {
954
1044
  type: "checkbox",
955
- checked: musicEnabled,
1045
+ checked: musicOnlyCurrentSession,
956
1046
  onChange: (event) => {
957
- setMusicEnabled(event.target.checked);
1047
+ setMusicOnlyCurrentSession(event.target.checked);
958
1048
  }
959
- }), (0, react_jsx_runtime.jsx)("span", { children: t("musicLabel") })]
1049
+ }), (0, react_jsx_runtime.jsx)("span", { children: t("musicOnlyCurrentSessionLabel") })]
960
1050
  }),
961
1051
  (0, react_jsx_runtime.jsx)("div", {
962
1052
  className: TokenQuotaPanel_module_css_default.dialogHint,
963
- children: t("musicHint")
964
- }),
965
- musicEnabled && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
966
- (0, react_jsx_runtime.jsxs)("label", {
967
- className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
968
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("musicVolume") }), (0, react_jsx_runtime.jsx)("input", {
969
- type: "range",
970
- min: 0,
971
- max: 1,
972
- step: .05,
973
- value: musicVolume,
974
- onChange: (event) => {
975
- setMusicVolume(Number(event.target.value));
976
- }
977
- })]
978
- }),
979
- (0, react_jsx_runtime.jsxs)("label", {
980
- className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
981
- children: [(0, react_jsx_runtime.jsx)("span", { children: t("musicStyle") }), (0, react_jsx_runtime.jsxs)("select", {
982
- value: musicStyle,
983
- onChange: (event) => {
984
- setMusicStyle(event.target.value);
985
- },
986
- children: [
987
- (0, react_jsx_runtime.jsx)("option", {
988
- value: "major",
989
- children: t("musicStyleMajor")
990
- }),
991
- (0, react_jsx_runtime.jsx)("option", {
992
- value: "minor",
993
- children: t("musicStyleMinor")
994
- }),
995
- (0, react_jsx_runtime.jsx)("option", {
996
- value: "pentatonic",
997
- children: t("musicStylePentatonic")
998
- })
999
- ]
1000
- })]
1001
- }),
1002
- (0, react_jsx_runtime.jsxs)("label", {
1003
- className: TokenQuotaPanel_module_css_default.checkUpdatesLabel,
1004
- children: [(0, react_jsx_runtime.jsx)("input", {
1005
- type: "checkbox",
1006
- checked: musicOnlyCurrentSession,
1007
- onChange: (event) => {
1008
- setMusicOnlyCurrentSession(event.target.checked);
1009
- }
1010
- }), (0, react_jsx_runtime.jsx)("span", { children: t("musicOnlyCurrentSessionLabel") })]
1011
- }),
1012
- (0, react_jsx_runtime.jsx)("div", {
1013
- className: TokenQuotaPanel_module_css_default.dialogHint,
1014
- children: t("musicOnlyCurrentSessionHint")
1015
- })
1016
- ] })
1053
+ children: t("musicOnlyCurrentSessionHint")
1054
+ })
1017
1055
  ]
1018
- }),
1019
- (0, react_jsx_runtime.jsxs)("div", {
1056
+ })] }),
1057
+ settingsTab === "other" && (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: (0, react_jsx_runtime.jsxs)("div", {
1020
1058
  className: TokenQuotaPanel_module_css_default.dialogSection,
1021
1059
  children: [(0, react_jsx_runtime.jsxs)("div", {
1022
1060
  className: TokenQuotaPanel_module_css_default.checkUpdatesRow,
@@ -1041,7 +1079,7 @@ window.__ModuleLoader__.load({
1041
1079
  role: "alert",
1042
1080
  children: upgradeError
1043
1081
  })]
1044
- })
1082
+ }) })
1045
1083
  ]
1046
1084
  }),
1047
1085
  logOpen && (0, react_jsx_runtime.jsxs)("div", {
@@ -1512,6 +1550,10 @@ window.__ModuleLoader__.load({
1512
1550
  get started() {
1513
1551
  return this.audio !== null;
1514
1552
  }
1553
+ /** True when the master switch is on (mirrors the settings document). */
1554
+ get isEnabled() {
1555
+ return this.enabled;
1556
+ }
1515
1557
  /**
1516
1558
  * Start the audio context and probe for a MIDI output (once). Must be
1517
1559
  * called inside a user gesture so the AudioContext can resume.
@@ -1584,7 +1626,7 @@ window.__ModuleLoader__.load({
1584
1626
  break;
1585
1627
  case "request/header":
1586
1628
  if (!this.playing) this.startPiece();
1587
- else this.pluck(base + 24, .3);
1629
+ else this.pluck(this.tone(5, 1), .3, .9);
1588
1630
  break;
1589
1631
  case "step/start": {
1590
1632
  const prog = PROGRESSION[this.style];
@@ -1596,31 +1638,51 @@ window.__ModuleLoader__.load({
1596
1638
  break;
1597
1639
  }
1598
1640
  case "tool/call":
1599
- this.pluck(base + 26, .45, .5);
1600
- this.pluck(base - 12, .3, .35);
1641
+ this.pluck(this.tone(4, 1), .38, .9);
1642
+ setTimeout(() => this.pluck(this.tone(5, 1), .34, .9), 90);
1643
+ this.pluck(this.tonicRoot - 12, .22, .9);
1601
1644
  break;
1602
1645
  case "tool/result":
1603
1646
  if (action.error) {
1604
- this.pluck(base + 13, .4, .6);
1605
- setTimeout(() => this.pluck(base + 12, .4, .9), 90);
1647
+ const t = this.tone(2, 1);
1648
+ this.pluck(t - 1, .4, .7);
1649
+ setTimeout(() => this.pluck(t - 3, .4, 1), 110);
1606
1650
  } else {
1607
- this.pluck(base + 9, .35, .6);
1608
- setTimeout(() => this.pluck(base + 11, .35, .9), 70);
1651
+ this.pluck(this.tone(3, 1), .34, 1);
1652
+ setTimeout(() => this.pluck(this.tone(1, 1), .32, 1), 90);
1653
+ setTimeout(() => this.pluck(this.tone(0, 1), .34, 1.2), 180);
1609
1654
  }
1610
1655
  break;
1611
1656
  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);
1657
+ this.pluck(this.tone(4, 1), .36, .8);
1658
+ setTimeout(() => this.pluck(this.tone(2, 1), .36, .9), 110);
1659
+ setTimeout(() => this.pluck(this.tone(0), .36, 1.1), 220);
1615
1660
  break;
1616
1661
  case "assistant/message":
1617
- this.breath(action.interrupted ? base + 6 : base + 12, 1.6);
1662
+ this.pluck(this.tone(2, 1), .3, 1);
1663
+ setTimeout(() => this.breath(this.tone(0, action.interrupted ? 0 : 1), 1.4), 140);
1618
1664
  break;
1619
1665
  case "turn/end":
1620
1666
  this.stopPiece(true);
1621
1667
  break;
1622
1668
  }
1623
1669
  }
1670
+ /** Current tonic root in MIDI note numbers. */
1671
+ get tonicRoot() {
1672
+ return TokenQuotaMusic.BASE_MIDI + (PROGRESSION[this.style][this.tonicIndex] ?? 0);
1673
+ }
1674
+ /**
1675
+ * A scale tone relative to the current tonic — degrees wrap up octaves
1676
+ * (deg 5 on a 5-note scale = the octave). Event figures use this so every
1677
+ * accent stays inside the key and melts into the piece instead of
1678
+ * clashing with it.
1679
+ */
1680
+ tone(deg, oct = 0) {
1681
+ const scale = SCALES[this.style];
1682
+ const len = scale.length;
1683
+ const d = (deg % len + len) % len;
1684
+ return this.tonicRoot + 12 * (Math.floor(deg / len) + oct) + (scale[d] ?? 0);
1685
+ }
1624
1686
  /** Start a new piece: intro run, then the phrase chain. */
1625
1687
  startPiece() {
1626
1688
  if (!this.audio || this.playing) return;
@@ -1852,7 +1914,10 @@ window.__ModuleLoader__.load({
1852
1914
  expand: "展开",
1853
1915
  expandHint: "展开每日 Token 限额面板",
1854
1916
  settings: "设置",
1855
- settingsTitle: "限额设置",
1917
+ settingsTitle: "设置",
1918
+ settingsTabQuota: "限额设置",
1919
+ settingsTabMusic: "音乐设置",
1920
+ settingsTabOther: "其它",
1856
1921
  monitorLabel: "监控模型",
1857
1922
  monitorHint: "未勾选的模型不显示、不计入用量、不受限额限制",
1858
1923
  monitorAll: "全部",
@@ -1930,7 +1995,10 @@ window.__ModuleLoader__.load({
1930
1995
  expand: "Expand",
1931
1996
  expandHint: "Expand the daily token quota panel",
1932
1997
  settings: "Settings",
1933
- settingsTitle: "Quota Settings",
1998
+ settingsTitle: "Settings",
1999
+ settingsTabQuota: "Quota",
2000
+ settingsTabMusic: "Music",
2001
+ settingsTabOther: "Other",
1934
2002
  monitorLabel: "Monitored models",
1935
2003
  monitorHint: "Unchecked models are hidden, not metered, and never capped",
1936
2004
  monitorAll: "All",
@@ -2049,6 +2117,24 @@ window.__ModuleLoader__.load({
2049
2117
  ctx.effect(() => () => {
2050
2118
  sse.close();
2051
2119
  }, "token-quota-ui: music SSE");
2120
+ const autoStartMusic = () => {
2121
+ if (!music.isEnabled || music.started) {
2122
+ if (music.started) {
2123
+ window.removeEventListener("pointerdown", autoStartMusic);
2124
+ window.removeEventListener("keydown", autoStartMusic);
2125
+ }
2126
+ return;
2127
+ }
2128
+ music.ensureStarted();
2129
+ window.removeEventListener("pointerdown", autoStartMusic);
2130
+ window.removeEventListener("keydown", autoStartMusic);
2131
+ };
2132
+ window.addEventListener("pointerdown", autoStartMusic);
2133
+ window.addEventListener("keydown", autoStartMusic);
2134
+ ctx.effect(() => () => {
2135
+ window.removeEventListener("pointerdown", autoStartMusic);
2136
+ window.removeEventListener("keydown", autoStartMusic);
2137
+ }, "token-quota-ui: music autostart gestures");
2052
2138
  /** Build a writable music-settings clone with defaults applied. */
2053
2139
  const musicDefaults = (raw) => ({
2054
2140
  enabled: raw?.enabled ?? false,
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.31";
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 vs. misc.
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} ${css.settingsDialog}`, 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') }), _jsx("button", { type: "button", role: "tab", "aria-selected": settingsTab === 'other', className: `${css.dialogTab}${settingsTab === 'other' ? ` ${css.dialogTabActive}` : ''}`, onClick: () => { setSettingsTab('other'); }, children: t('settingsTabOther') })] }), 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)))] })] })), settingsTab === 'other' && (_jsxs(_Fragment, { children: [_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(_Fragment, { children: [_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("div", { className: css.dialogSection, 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 === 'other' && (_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') })] })] }) }))] }));
@@ -66,6 +66,29 @@ export function apply(ctx) {
66
66
  music.onAction(action);
67
67
  });
68
68
  ctx.effect(() => () => { sse.close(); }, 'token-quota-ui: music SSE');
69
+ // Browser autoplay policy: an AudioContext only starts inside a user
70
+ // gesture. When the switch was left ON and the page reloads, the setting
71
+ // is enabled but no gesture has happened yet — so the engine stays silent
72
+ // until the user toggles it off/on. Arm a one-shot gesture listener that
73
+ // starts the engine on the first click / keypress instead.
74
+ const autoStartMusic = () => {
75
+ if (!music.isEnabled || music.started) {
76
+ if (music.started) {
77
+ window.removeEventListener('pointerdown', autoStartMusic);
78
+ window.removeEventListener('keydown', autoStartMusic);
79
+ }
80
+ return;
81
+ }
82
+ void music.ensureStarted();
83
+ window.removeEventListener('pointerdown', autoStartMusic);
84
+ window.removeEventListener('keydown', autoStartMusic);
85
+ };
86
+ window.addEventListener('pointerdown', autoStartMusic);
87
+ window.addEventListener('keydown', autoStartMusic);
88
+ ctx.effect(() => () => {
89
+ window.removeEventListener('pointerdown', autoStartMusic);
90
+ window.removeEventListener('keydown', autoStartMusic);
91
+ }, 'token-quota-ui: music autostart gestures');
69
92
  /** Build a writable music-settings clone with defaults applied. */
70
93
  const musicDefaults = (raw) => ({
71
94
  enabled: raw?.enabled ?? false,
@@ -25,6 +25,9 @@ export declare const zh: {
25
25
  expandHint: string;
26
26
  settings: string;
27
27
  settingsTitle: string;
28
+ settingsTabQuota: string;
29
+ settingsTabMusic: string;
30
+ settingsTabOther: string;
28
31
  monitorLabel: string;
29
32
  monitorHint: string;
30
33
  monitorAll: string;
@@ -105,6 +108,9 @@ export declare const en: {
105
108
  expandHint: string;
106
109
  settings: string;
107
110
  settingsTitle: string;
111
+ settingsTabQuota: string;
112
+ settingsTabMusic: string;
113
+ settingsTabOther: string;
108
114
  monitorLabel: string;
109
115
  monitorHint: string;
110
116
  monitorAll: string;
@@ -24,7 +24,10 @@ export const zh = {
24
24
  expand: '展开',
25
25
  expandHint: '展开每日 Token 限额面板',
26
26
  settings: '设置',
27
- settingsTitle: '限额设置',
27
+ settingsTitle: '设置',
28
+ settingsTabQuota: '限额设置',
29
+ settingsTabMusic: '音乐设置',
30
+ settingsTabOther: '其它',
28
31
  monitorLabel: '监控模型',
29
32
  monitorHint: '未勾选的模型不显示、不计入用量、不受限额限制',
30
33
  monitorAll: '全部',
@@ -102,7 +105,10 @@ export const en = {
102
105
  expand: 'Expand',
103
106
  expandHint: 'Expand the daily token quota panel',
104
107
  settings: 'Settings',
105
- settingsTitle: 'Quota Settings',
108
+ settingsTitle: 'Settings',
109
+ settingsTabQuota: 'Quota',
110
+ settingsTabMusic: 'Music',
111
+ settingsTabOther: 'Other',
106
112
  monitorLabel: 'Monitored models',
107
113
  monitorHint: 'Unchecked models are hidden, not metered, and never capped',
108
114
  monitorAll: 'All',
@@ -56,6 +56,8 @@ export declare class TokenQuotaMusic {
56
56
  private static readonly STREAM_TIMEOUT_MS;
57
57
  /** True once the audio context has been started (needs a user gesture). */
58
58
  get started(): boolean;
59
+ /** True when the master switch is on (mirrors the settings document). */
60
+ get isEnabled(): boolean;
59
61
  /**
60
62
  * Start the audio context and probe for a MIDI output (once). Must be
61
63
  * called inside a user gesture so the AudioContext can resume.
@@ -70,6 +72,15 @@ export declare class TokenQuotaMusic {
70
72
  stop(): void;
71
73
  /** Handle one incoming host action and steer the piece. */
72
74
  onAction(action: TokenQuotaMusicAction): void;
75
+ /** Current tonic root in MIDI note numbers. */
76
+ private get tonicRoot();
77
+ /**
78
+ * A scale tone relative to the current tonic — degrees wrap up octaves
79
+ * (deg 5 on a 5-note scale = the octave). Event figures use this so every
80
+ * accent stays inside the key and melts into the piece instead of
81
+ * clashing with it.
82
+ */
83
+ private tone;
73
84
  /** Start a new piece: intro run, then the phrase chain. */
74
85
  private startPiece;
75
86
  /** Play one phrase, then chain into the next after a one-beat rest. */
@@ -98,6 +98,10 @@ export class TokenQuotaMusic {
98
98
  get started() {
99
99
  return this.audio !== null;
100
100
  }
101
+ /** True when the master switch is on (mirrors the settings document). */
102
+ get isEnabled() {
103
+ return this.enabled;
104
+ }
101
105
  /**
102
106
  * Start the audio context and probe for a MIDI output (once). Must be
103
107
  * called inside a user gesture so the AudioContext can resume.
@@ -194,7 +198,7 @@ export class TokenQuotaMusic {
194
198
  if (!this.playing)
195
199
  this.startPiece();
196
200
  else
197
- this.pluck(base + 24, 0.3);
201
+ this.pluck(this.tone(5, 1), 0.3, 0.9);
198
202
  break;
199
203
  }
200
204
  case 'step/start': {
@@ -207,40 +211,67 @@ export class TokenQuotaMusic {
207
211
  }
208
212
  break;
209
213
  }
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);
214
+ case 'tool/call': {
215
+ // Rising two-note figure drawn from the current scale (so it blends
216
+ // with the piece, not pokes out of it) over a soft low root for body.
217
+ this.pluck(this.tone(4, 1), 0.38, 0.9);
218
+ setTimeout(() => this.pluck(this.tone(5, 1), 0.34, 0.9), 90);
219
+ this.pluck(this.tonicRoot - 12, 0.22, 0.9);
214
220
  break;
221
+ }
215
222
  case 'tool/result': {
216
223
  if (action.error) {
217
- this.pluck(base + 13, 0.4, 0.6);
218
- setTimeout(() => this.pluck(base + 12, 0.4, 0.9), 90);
224
+ // Deliberately sour (an error should feel off): a half-step below a
225
+ // scale tone, then sinking further.
226
+ const t = this.tone(2, 1);
227
+ this.pluck(t - 1, 0.4, 0.7);
228
+ setTimeout(() => this.pluck(t - 3, 0.4, 1.0), 110);
219
229
  }
220
230
  else {
221
- this.pluck(base + 9, 0.35, 0.6);
222
- setTimeout(() => this.pluck(base + 11, 0.35, 0.9), 70);
231
+ // Gentle in-key resolve landing on the tonic octave.
232
+ this.pluck(this.tone(3, 1), 0.34, 1.0);
233
+ setTimeout(() => this.pluck(this.tone(1, 1), 0.32, 1.0), 90);
234
+ setTimeout(() => this.pluck(this.tone(0, 1), 0.34, 1.2), 180);
223
235
  }
224
236
  break;
225
237
  }
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);
238
+ case 'assistant/attempt': {
239
+ // Failed attempt: falling in-key tension figure.
240
+ this.pluck(this.tone(4, 1), 0.36, 0.8);
241
+ setTimeout(() => this.pluck(this.tone(2, 1), 0.36, 0.9), 110);
242
+ setTimeout(() => this.pluck(this.tone(0), 0.36, 1.1), 220);
231
243
  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);
244
+ }
245
+ case 'assistant/message': {
246
+ // Sentence end: a short cadence figure, then a breath on the tonic
247
+ // octave (in key — the old fixed C clashed with the mode rotation).
248
+ // The bed keeps playing until the turn fully ends (turn/end).
249
+ this.pluck(this.tone(2, 1), 0.3, 1.0);
250
+ setTimeout(() => this.breath(this.tone(0, action.interrupted ? 0 : 1), 1.4), 140);
237
251
  break;
252
+ }
238
253
  case 'turn/end':
239
254
  this.stopPiece(true);
240
255
  break;
241
256
  }
242
257
  }
243
258
  // ── piece lifecycle ───────────────────────────────────────────────────
259
+ /** Current tonic root in MIDI note numbers. */
260
+ get tonicRoot() {
261
+ return TokenQuotaMusic.BASE_MIDI + (PROGRESSION[this.style][this.tonicIndex] ?? 0);
262
+ }
263
+ /**
264
+ * A scale tone relative to the current tonic — degrees wrap up octaves
265
+ * (deg 5 on a 5-note scale = the octave). Event figures use this so every
266
+ * accent stays inside the key and melts into the piece instead of
267
+ * clashing with it.
268
+ */
269
+ tone(deg, oct = 0) {
270
+ const scale = SCALES[this.style];
271
+ const len = scale.length;
272
+ const d = ((deg % len) + len) % len;
273
+ return this.tonicRoot + 12 * (Math.floor(deg / len) + oct) + (scale[d] ?? 0);
274
+ }
244
275
  /** Start a new piece: intro run, then the phrase chain. */
245
276
  startPiece() {
246
277
  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.31",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },