@jxgame2020/dsh-token-quota 0.1.32 → 0.1.34
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 +946 -536
- package/lib/index.js +28 -2
- package/lib/types/client/TokenQuotaPanel.d.ts +4 -2
- package/lib/types/client/TokenQuotaPanel.js +235 -35
- package/lib/types/client/index.js +31 -1
- package/lib/types/client/locales.d.ts +22 -0
- package/lib/types/client/locales.js +22 -0
- package/lib/types/client/store.d.ts +11 -1
- package/lib/types/client/store.js +3 -0
- package/lib/types/index.js +31 -2
- package/lib/types/types.d.ts +34 -0
- package/lib/types/types.js +5 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -215,6 +215,17 @@ const TOKEN_QUOTA_SETTINGS_SCHEMA = z.object({
|
|
|
215
215
|
checkUpdates: z.boolean().default(true),
|
|
216
216
|
dimWhenIdle: z.boolean().default(false),
|
|
217
217
|
order: z.array(z.string()).default([]),
|
|
218
|
+
notes: z.array(z.object({
|
|
219
|
+
id: z.string(),
|
|
220
|
+
title: z.string().default(""),
|
|
221
|
+
text: z.string().default(""),
|
|
222
|
+
x: z.number().default(80),
|
|
223
|
+
y: z.number().default(80),
|
|
224
|
+
width: z.number().default(220),
|
|
225
|
+
height: z.number().default(220),
|
|
226
|
+
collapsed: z.boolean().default(false),
|
|
227
|
+
visible: z.boolean().default(true)
|
|
228
|
+
})).default([]),
|
|
218
229
|
reset: z.any().default(null),
|
|
219
230
|
music: z.object({
|
|
220
231
|
enabled: z.boolean().default(TOKEN_QUOTA_DEFAULT_MUSIC.enabled),
|
|
@@ -283,7 +294,8 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
283
294
|
checkUpdates: true,
|
|
284
295
|
dimWhenIdle: false,
|
|
285
296
|
music: { ...TOKEN_QUOTA_DEFAULT_MUSIC },
|
|
286
|
-
order: []
|
|
297
|
+
order: [],
|
|
298
|
+
notes: []
|
|
287
299
|
});
|
|
288
300
|
/** Whether update checks are enabled (mirrors the settings document). */
|
|
289
301
|
checkUpdates = true;
|
|
@@ -337,6 +349,20 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
337
349
|
checkUpdates: doc.checkUpdates ?? true,
|
|
338
350
|
dimWhenIdle: doc.dimWhenIdle ?? false,
|
|
339
351
|
order: Array.isArray(doc.order) ? doc.order : [],
|
|
352
|
+
notes: Array.isArray(doc.notes) ? doc.notes.slice(0, 5).flatMap((note) => {
|
|
353
|
+
if (typeof note.id !== "string") return [];
|
|
354
|
+
return [{
|
|
355
|
+
id: note.id,
|
|
356
|
+
title: note.title ?? "",
|
|
357
|
+
text: note.text ?? "",
|
|
358
|
+
x: note.x ?? 80,
|
|
359
|
+
y: note.y ?? 80,
|
|
360
|
+
width: note.width ?? 220,
|
|
361
|
+
height: note.height ?? 220,
|
|
362
|
+
collapsed: note.collapsed ?? false,
|
|
363
|
+
visible: note.visible ?? true
|
|
364
|
+
}];
|
|
365
|
+
}) : [],
|
|
340
366
|
reset: doc.reset ?? void 0,
|
|
341
367
|
music: {
|
|
342
368
|
enabled: doc.music?.enabled ?? TOKEN_QUOTA_DEFAULT_MUSIC.enabled,
|
|
@@ -518,7 +544,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
518
544
|
return this.upgradeCheck;
|
|
519
545
|
}
|
|
520
546
|
async doRefreshUpgrade() {
|
|
521
|
-
const current = "0.1.
|
|
547
|
+
const current = "0.1.34";
|
|
522
548
|
let latest;
|
|
523
549
|
try {
|
|
524
550
|
const response = await fetch(REGISTRY_LATEST_URL, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PropsLocale, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
|
-
import type { TokenQuotaFullAction, TokenQuotaMusicStyle, TokenQuotaReset } from '@jxgame2020/dsh-token-quota/types';
|
|
3
|
+
import type { TokenQuotaFullAction, TokenQuotaMusicStyle, TokenQuotaNote, TokenQuotaReset } from '@jxgame2020/dsh-token-quota/types';
|
|
4
4
|
import type { createTokenQuotaPanelStore } from './store.ts';
|
|
5
5
|
/** Injected business face: data loading and mutations wired in `apply`. */
|
|
6
6
|
export interface TokenQuotaPanelInjected {
|
|
@@ -22,6 +22,8 @@ export interface TokenQuotaPanelInjected {
|
|
|
22
22
|
setDimWhenIdle: (enabled: boolean) => void;
|
|
23
23
|
/** Persist the drag-to-reorder display order of model rows. */
|
|
24
24
|
setModelOrder: (order: string[]) => void;
|
|
25
|
+
/** Persist the floating scratchpad notes (content, title, placement). */
|
|
26
|
+
saveNotes: (notes: TokenQuotaNote[]) => void;
|
|
25
27
|
/** Toggle the live music output (requires a user gesture to start audio). */
|
|
26
28
|
setMusicEnabled: (enabled: boolean) => void;
|
|
27
29
|
/** Change the live-music master volume 0..1. */
|
|
@@ -42,5 +44,5 @@ export type TokenQuotaPanelComponentProps = PropsRuntime<'shell.overlay'> & Prop
|
|
|
42
44
|
* @param props - composed slot props.
|
|
43
45
|
* @returns the panel element tree.
|
|
44
46
|
*/
|
|
45
|
-
export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setModelOrder, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
|
|
47
|
+
export declare function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setModelOrder, saveNotes, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }: TokenQuotaPanelComponentProps): import("react").JSX.Element;
|
|
46
48
|
//# sourceMappingURL=TokenQuotaPanel.d.ts.map
|
|
@@ -10,6 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
10
10
|
* callbacks.
|
|
11
11
|
*/
|
|
12
12
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
13
|
+
import { TOKEN_QUOTA_MAX_NOTES } from '@jxgame2020/dsh-token-quota/types';
|
|
13
14
|
import { mergeModelRows, reorderKeys } from "./store.js";
|
|
14
15
|
import css from './TokenQuotaPanel.module.css';
|
|
15
16
|
/** Compact a token count for display. */
|
|
@@ -63,6 +64,46 @@ function beginDrag(event, el, apply) {
|
|
|
63
64
|
window.addEventListener('pointerup', onUp);
|
|
64
65
|
window.addEventListener('pointercancel', onUp);
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Pointer-driven resize of a note window from its bottom-right grip. Sizes are
|
|
69
|
+
* absolute pixels derived from the element's rect at grab time, so the window
|
|
70
|
+
* keeps its top-left anchor while the pointer moves. A floor keeps a window
|
|
71
|
+
* from collapsing to nothing.
|
|
72
|
+
*/
|
|
73
|
+
function beginResize(event, el, apply) {
|
|
74
|
+
if (el === null)
|
|
75
|
+
return;
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
event.stopPropagation();
|
|
78
|
+
const startX = event.clientX;
|
|
79
|
+
const startY = event.clientY;
|
|
80
|
+
const rect = el.getBoundingClientRect();
|
|
81
|
+
const onMove = (ev) => {
|
|
82
|
+
apply({
|
|
83
|
+
width: Math.max(NOTE_MIN_WIDTH, Math.round(rect.width + (ev.clientX - startX))),
|
|
84
|
+
height: Math.max(NOTE_MIN_HEIGHT, Math.round(rect.height + (ev.clientY - startY))),
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const onUp = () => {
|
|
88
|
+
window.removeEventListener('pointermove', onMove);
|
|
89
|
+
window.removeEventListener('pointerup', onUp);
|
|
90
|
+
window.removeEventListener('pointercancel', onUp);
|
|
91
|
+
};
|
|
92
|
+
window.addEventListener('pointermove', onMove);
|
|
93
|
+
window.addEventListener('pointerup', onUp);
|
|
94
|
+
window.addEventListener('pointercancel', onUp);
|
|
95
|
+
}
|
|
96
|
+
/** First visible character of a note title, for its header chip. */
|
|
97
|
+
function noteInitial(title) {
|
|
98
|
+
const trimmed = title.trim();
|
|
99
|
+
if (trimmed === '')
|
|
100
|
+
return '·';
|
|
101
|
+
return Array.from(trimmed)[0] ?? '·';
|
|
102
|
+
}
|
|
103
|
+
/** Default geometry of a new note; later notes cascade so they don't stack. */
|
|
104
|
+
const NOTE_DEFAULT_SIZE = 220;
|
|
105
|
+
const NOTE_MIN_WIDTH = 150;
|
|
106
|
+
const NOTE_MIN_HEIGHT = 70;
|
|
66
107
|
/** Persisted panel placement so a dragged position survives reloads. */
|
|
67
108
|
const PANEL_POS_KEY = 'dsh-token-quota:panel-pos';
|
|
68
109
|
function loadPanelPos() {
|
|
@@ -86,7 +127,7 @@ function loadPanelPos() {
|
|
|
86
127
|
* @param props - composed slot props.
|
|
87
128
|
* @returns the panel element tree.
|
|
88
129
|
*/
|
|
89
|
-
export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setModelOrder, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }) {
|
|
130
|
+
export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored, setOnFull, setReset, setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle, setModelOrder, saveNotes, setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession, useStore, actions, useSessions, }) {
|
|
90
131
|
const [collapsed, setCollapsed] = useState(false);
|
|
91
132
|
const [drafts, setDrafts] = useState({});
|
|
92
133
|
const [editingKey, setEditingKey] = useState(null);
|
|
@@ -101,6 +142,144 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
101
142
|
// the key currently hovered as the drop target.
|
|
102
143
|
const [dragKey, setDragKey] = useState(null);
|
|
103
144
|
const [dropKey, setDropKey] = useState(null);
|
|
145
|
+
// ---- Floating scratchpad notes -------------------------------------------
|
|
146
|
+
// The notes live in local state while the panel is mounted: every keystroke
|
|
147
|
+
// updates this copy (so typing is never laggy) and a debounced write pushes
|
|
148
|
+
// it to the Host. Seeding happens exactly once, when the Host's document
|
|
149
|
+
// first arrives, so a background settings poll cannot clobber the text.
|
|
150
|
+
const storedNotes = useStore(s => s.notes);
|
|
151
|
+
const storedNotesLoaded = useStore(s => s.notesLoaded);
|
|
152
|
+
const [notes, setNotes] = useState([]);
|
|
153
|
+
const notesSeeded = useRef(false);
|
|
154
|
+
const notesTimer = useRef(null);
|
|
155
|
+
// Latest value awaiting a debounced write, so an unmount can flush it.
|
|
156
|
+
const notesPending = useRef(null);
|
|
157
|
+
// Context menu opened by right-clicking a note chip.
|
|
158
|
+
const [noteMenu, setNoteMenu] = useState(null);
|
|
159
|
+
// Note window that was touched last, painted above its siblings.
|
|
160
|
+
const [activeNote, setActiveNote] = useState(null);
|
|
161
|
+
// Note whose title is being edited (id) plus the in-progress draft. The
|
|
162
|
+
// title is a plain label until the pencil is pressed, so the title bar
|
|
163
|
+
// stays fully draggable the rest of the time.
|
|
164
|
+
const [editingTitle, setEditingTitle] = useState(null);
|
|
165
|
+
const [titleDraft, setTitleDraft] = useState('');
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (!storedNotesLoaded || notesSeeded.current)
|
|
168
|
+
return;
|
|
169
|
+
notesSeeded.current = true;
|
|
170
|
+
setNotes(storedNotes);
|
|
171
|
+
}, [storedNotesLoaded, storedNotes]);
|
|
172
|
+
// Flush a pending debounced write when the panel goes away, so keystrokes
|
|
173
|
+
// typed in the last fraction of a second are not lost.
|
|
174
|
+
useEffect(() => () => {
|
|
175
|
+
if (notesTimer.current !== null)
|
|
176
|
+
window.clearTimeout(notesTimer.current);
|
|
177
|
+
const pending = notesPending.current;
|
|
178
|
+
if (pending !== null) {
|
|
179
|
+
notesPending.current = null;
|
|
180
|
+
saveNotes(pending);
|
|
181
|
+
}
|
|
182
|
+
}, []);
|
|
183
|
+
/**
|
|
184
|
+
* Apply a note change locally and persist it. Structural changes (create,
|
|
185
|
+
* close, collapse, drop) write straight through; free-form edits (typing,
|
|
186
|
+
* dragging, resizing) are debounced so a keystroke doesn't hit the Host.
|
|
187
|
+
*/
|
|
188
|
+
const commitNotes = (next, immediate = true) => {
|
|
189
|
+
setNotes(next);
|
|
190
|
+
if (notesTimer.current !== null)
|
|
191
|
+
window.clearTimeout(notesTimer.current);
|
|
192
|
+
if (immediate) {
|
|
193
|
+
notesPending.current = null;
|
|
194
|
+
saveNotes(next);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
notesPending.current = next;
|
|
198
|
+
notesTimer.current = window.setTimeout(() => {
|
|
199
|
+
notesTimer.current = null;
|
|
200
|
+
const pending = notesPending.current;
|
|
201
|
+
notesPending.current = null;
|
|
202
|
+
if (pending !== null)
|
|
203
|
+
saveNotes(pending);
|
|
204
|
+
}, 400);
|
|
205
|
+
};
|
|
206
|
+
const patchNote = (id, patch, immediate = true) => {
|
|
207
|
+
commitNotes(notes.map(note => (note.id === id ? { ...note, ...patch } : note)), immediate);
|
|
208
|
+
};
|
|
209
|
+
/** Create a note, auto-titled 新建N with the lowest unused number. */
|
|
210
|
+
const createNote = () => {
|
|
211
|
+
if (notes.length >= TOKEN_QUOTA_MAX_NOTES)
|
|
212
|
+
return;
|
|
213
|
+
let index = 1;
|
|
214
|
+
const used = new Set(notes.map(note => /^新建(\d+)$/.exec(note.title.trim())?.[1]).filter(Boolean));
|
|
215
|
+
while (used.has(String(index)))
|
|
216
|
+
index += 1;
|
|
217
|
+
const step = notes.length * 28;
|
|
218
|
+
commitNotes([...notes, {
|
|
219
|
+
id: `note-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`,
|
|
220
|
+
title: `新建${index}`,
|
|
221
|
+
text: '',
|
|
222
|
+
x: 120 + step,
|
|
223
|
+
y: 140 + step,
|
|
224
|
+
width: NOTE_DEFAULT_SIZE,
|
|
225
|
+
height: NOTE_DEFAULT_SIZE,
|
|
226
|
+
collapsed: false,
|
|
227
|
+
visible: true,
|
|
228
|
+
}]);
|
|
229
|
+
};
|
|
230
|
+
/** Commit the title draft and leave edit mode. */
|
|
231
|
+
const commitTitle = (id) => {
|
|
232
|
+
patchNote(id, { title: titleDraft }, true);
|
|
233
|
+
setEditingTitle(null);
|
|
234
|
+
};
|
|
235
|
+
const closeNote = (id) => { patchNote(id, { visible: false }); };
|
|
236
|
+
const removeNote = (id) => {
|
|
237
|
+
commitNotes(notes.filter(note => note.id !== id));
|
|
238
|
+
setNoteMenu(null);
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Title-bar drag with a click/drag threshold: moving the pointer past a few
|
|
242
|
+
* pixels drags the window, while a plain click focuses the title input for
|
|
243
|
+
* editing. A title that is already being edited keeps native text behaviour
|
|
244
|
+
* (selection, caret placement) instead of turning into a window drag.
|
|
245
|
+
*/
|
|
246
|
+
const beginNoteBarDrag = (event, id) => {
|
|
247
|
+
const win = event.currentTarget.parentElement;
|
|
248
|
+
if (win === null)
|
|
249
|
+
return;
|
|
250
|
+
if (event.target.closest('button') !== null)
|
|
251
|
+
return;
|
|
252
|
+
// While the title is being edited the input keeps native text behaviour.
|
|
253
|
+
if (event.target.closest('input') !== null)
|
|
254
|
+
return;
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
const startX = event.clientX;
|
|
257
|
+
const startY = event.clientY;
|
|
258
|
+
const rect = win.getBoundingClientRect();
|
|
259
|
+
let dragging = false;
|
|
260
|
+
const onMove = (ev) => {
|
|
261
|
+
if (!dragging) {
|
|
262
|
+
if (Math.abs(ev.clientX - startX) < 3 && Math.abs(ev.clientY - startY) < 3)
|
|
263
|
+
return;
|
|
264
|
+
dragging = true;
|
|
265
|
+
const active = document.activeElement;
|
|
266
|
+
if (active instanceof HTMLElement)
|
|
267
|
+
active.blur();
|
|
268
|
+
}
|
|
269
|
+
patchNote(id, {
|
|
270
|
+
x: Math.round(rect.left + (ev.clientX - startX)),
|
|
271
|
+
y: Math.round(rect.top + (ev.clientY - startY)),
|
|
272
|
+
}, false);
|
|
273
|
+
};
|
|
274
|
+
const onUp = () => {
|
|
275
|
+
window.removeEventListener('pointermove', onMove);
|
|
276
|
+
window.removeEventListener('pointerup', onUp);
|
|
277
|
+
window.removeEventListener('pointercancel', onUp);
|
|
278
|
+
};
|
|
279
|
+
window.addEventListener('pointermove', onMove);
|
|
280
|
+
window.addEventListener('pointerup', onUp);
|
|
281
|
+
window.addEventListener('pointercancel', onUp);
|
|
282
|
+
};
|
|
104
283
|
// Copy-button feedback: which command was just copied (label flips to
|
|
105
284
|
// 「已复制」for a moment so the click is perceivable).
|
|
106
285
|
const [copiedCmd, setCopiedCmd] = useState(null);
|
|
@@ -318,39 +497,45 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
318
497
|
const panelDimClass = dimWhenIdle
|
|
319
498
|
? (hovered || upgradeFlash ? '' : inputActive ? css.panelDimStrong : css.panelDim)
|
|
320
499
|
: '';
|
|
321
|
-
return (_jsxs("div", { ref: panelRef, className: `${css.panel}${panelDimClass !== '' ? ` ${panelDimClass}` : ''}`, style: panelPos !== null ? { left: panelPos.x, top: panelPos.y, right: 'auto' } : undefined, onPointerEnter: () => { setHovered(true); }, onPointerLeave: () => { setHovered(false); }, children: [_jsxs("div", { className: css.header, onPointerDown: (event) => { beginDrag(event, panelRef.current, applyPanelPos); }, children: [_jsxs("div", { className: css.headerText, children: [_jsxs("div", { className: css.title, children: [t('title'), __TOKEN_QUOTA_VERSION__ !== undefined && __TOKEN_QUOTA_VERSION__ !== '' && (upgrade !== null
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
500
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: panelRef, className: `${css.panel}${panelDimClass !== '' ? ` ${panelDimClass}` : ''}`, style: panelPos !== null ? { left: panelPos.x, top: panelPos.y, right: 'auto' } : undefined, onPointerEnter: () => { setHovered(true); }, onPointerLeave: () => { setHovered(false); }, children: [_jsxs("div", { className: css.header, onPointerDown: (event) => { beginDrag(event, panelRef.current, applyPanelPos); }, children: [_jsxs("div", { className: css.headerText, children: [_jsxs("div", { className: css.title, children: [t('title'), __TOKEN_QUOTA_VERSION__ !== undefined && __TOKEN_QUOTA_VERSION__ !== '' && (upgrade !== null
|
|
501
|
+
? (_jsxs("span", { className: `${css.version} ${css.versionNew}`, title: t('upgradeAvailableHint'), children: ["v", __TOKEN_QUOTA_VERSION__, " \u2192 v", upgrade.latestVersion] }))
|
|
502
|
+
: (_jsxs("span", { className: css.version, children: ["v", __TOKEN_QUOTA_VERSION__] })))] }), _jsxs("div", { className: css.notesBar, onPointerDown: (event) => { event.stopPropagation(); }, children: [notes.map((note) => {
|
|
503
|
+
const open = note.visible && !note.collapsed;
|
|
504
|
+
return (_jsx("button", { type: "button", className: `${css.noteChip}${open ? ` ${css.noteChipOpen}` : ''}`, title: note.title.trim() === '' ? t('noteUntitled') : note.title, onClick: () => { patchNote(note.id, { visible: true, collapsed: false }); }, onContextMenu: (event) => {
|
|
505
|
+
event.preventDefault();
|
|
506
|
+
setNoteMenu({ id: note.id, x: event.clientX, y: event.clientY });
|
|
507
|
+
}, children: noteInitial(note.title) }, note.id));
|
|
508
|
+
}), _jsxs("button", { type: "button", className: css.noteNew, disabled: notes.length >= TOKEN_QUOTA_MAX_NOTES, title: notes.length >= TOKEN_QUOTA_MAX_NOTES ? t('noteMaxHint') : t('noteNewHint'), onClick: createNote, children: [_jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [_jsx("path", { d: "M4 1.6h4.6L12.4 5.4v9H4z", fill: "none", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round" }), _jsx("path", { d: "M8.4 1.6v3.9h4", fill: "none", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round" })] }), _jsx("span", { className: css.noteNewPlus, children: "+" })] })] })] }), _jsxs("div", { className: css.headerActions, onPointerDown: (event) => { event.stopPropagation(); }, children: [_jsx("button", { type: "button", "aria-pressed": musicEnabled, className: `${css.settingsBtn}${musicEnabled ? ` ${css.settingsBtnActive}` : ''}`, title: `${t('musicLabel')}:${musicEnabled ? t('musicOn') : t('musicOff')}`, onClick: () => { setMusicEnabled(!musicEnabled); }, children: "\u266A" }), _jsx("button", { type: "button", className: css.settingsBtn, onClick: () => { actions.setLogOpen(true); }, children: t('logs') }), _jsx("button", { type: "button", className: css.settingsBtn, onClick: openSettings, children: t('settings') }), _jsx("button", { type: "button", className: css.collapse, onClick: () => { setCollapsed(true); }, children: t('collapse') })] })] }), _jsxs("div", { className: css.body, children: [loading && _jsx("div", { className: css.notice, children: t('loading') }), error !== null && _jsx("div", { className: css.noticeError, children: error }), fullNotice !== null && (_jsx("div", { className: css.fullNotice, role: "alert", children: fullNotice })), upgrade !== null && !upgradeDismissed && (_jsxs("div", { className: `${css.upgradeBanner}${upgradeFlash ? ` ${css.upgradeBannerFlash}` : ''}`, role: "alert", children: [_jsx("div", { className: css.upgradeBannerText, children: t('upgradeAvailable').replace('{version}', upgrade.latestVersion) }), _jsx("div", { className: css.upgradeCommands, children: upgrade.commands.map(cmd => (_jsxs("div", { className: css.upgradeCommandRow, children: [_jsx("code", { className: css.upgradeCommand, children: cmd }), _jsx("button", { type: "button", className: `${css.copyBtn}${copiedCmd === cmd ? ` ${css.copyBtnDone}` : ''}`, onClick: () => { copyUpgradeCommand(cmd); }, children: copiedCmd === cmd ? t('copied') : t('copy') })] }, cmd))) }), _jsx("div", { className: css.upgradeHint, children: t('upgradeHint') }), _jsx("button", { type: "button", className: css.upgradeClose, onClick: () => { actions.setUpgradeDismissed(true); }, "aria-label": t('close'), children: "\u00D7" })] })), !loading && error === null && visibleRows.length === 0 && (_jsx("div", { className: css.notice, children: monitored !== null && monitored.length === 0 ? t('noMonitored') : t('waiting') })), visibleRows.map((row) => {
|
|
509
|
+
const state = barStateOf(row);
|
|
510
|
+
const pct = row.limit > 0 ? Math.min(100, Math.round((row.used / row.limit) * 100)) : 0;
|
|
511
|
+
const barClass = state === 'over'
|
|
512
|
+
? css.fillOver
|
|
513
|
+
: state === 'warn'
|
|
514
|
+
? css.fillWarn
|
|
515
|
+
: css.fillIdle;
|
|
516
|
+
const editing = editingKey === row.key;
|
|
517
|
+
return (_jsxs("div", { className: `${css.row}${dragKey === row.key ? ` ${css.rowDragging}` : ''}${dropKey === row.key && dragKey !== row.key ? ` ${css.rowDropTarget}` : ''}`, onDragOver: (event) => {
|
|
518
|
+
if (dragKey === null || dragKey === row.key)
|
|
519
|
+
return;
|
|
520
|
+
event.preventDefault();
|
|
521
|
+
setDropKey(row.key);
|
|
522
|
+
}, onDrop: (event) => {
|
|
523
|
+
event.preventDefault();
|
|
524
|
+
if (dragKey !== null && dragKey !== row.key) {
|
|
525
|
+
setModelOrder(reorderKeys(rows.map(r => r.key), dragKey, row.key));
|
|
526
|
+
}
|
|
527
|
+
setDragKey(null);
|
|
528
|
+
setDropKey(null);
|
|
529
|
+
}, onDragEnd: () => { setDragKey(null); setDropKey(null); }, children: [_jsxs("div", { className: css.rowHeader, children: [_jsx("span", { className: css.rowDrag, draggable: true, title: t('reorderHint'), onDragStart: (event) => {
|
|
530
|
+
setDragKey(row.key);
|
|
531
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
532
|
+
// Firefox only starts a drag when some data is set.
|
|
533
|
+
event.dataTransfer.setData('text/plain', row.key);
|
|
534
|
+
}, children: "\u283F" }), _jsxs("span", { className: css.rowName, title: row.key, children: [row.name, row.current && _jsx("span", { className: css.currentBadge, children: t('current') }), sessionId !== undefined && !row.current && (_jsx("button", { type: "button", className: css.selectBtn, onClick: () => { selectModel(sessionId, row.provider, row.model); }, children: t('select') }))] }), _jsxs("span", { className: css.rowMeta, children: [row.limit > 0
|
|
535
|
+
? `${formatTokens(row.used)} / ${formatTokens(row.limit)}`
|
|
536
|
+
: `${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')
|
|
537
|
+
commitLimit(row); } }), _jsx("button", { type: "button", className: css.save, onClick: () => { commitLimit(row); }, children: t('save') })] }))] }, row.key));
|
|
538
|
+
})] })] }), 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) => {
|
|
354
539
|
const base = monitored === null ? allModels.map(m => m.key) : monitored;
|
|
355
540
|
const next = new Set(base);
|
|
356
541
|
if (event.target.checked)
|
|
@@ -386,6 +571,21 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
|
|
|
386
571
|
: 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')
|
|
387
572
|
.replace('{from}', String(logCurrentPage * logPageSize + 1))
|
|
388
573
|
.replace('{to}', String(Math.min((logCurrentPage + 1) * logPageSize, filteredLogEntries.length)))
|
|
389
|
-
.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') })] })] }) }))
|
|
574
|
+
.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') })] })] }) })), notes.filter(note => note.visible).map((note) => (_jsxs("div", { className: `${css.noteWin}${note.collapsed ? ` ${css.noteWinCollapsed}` : ''}`, style: {
|
|
575
|
+
left: `${note.x}px`,
|
|
576
|
+
top: `${note.y}px`,
|
|
577
|
+
width: `${note.width}px`,
|
|
578
|
+
zIndex: activeNote === note.id ? 62 : 60,
|
|
579
|
+
...(note.collapsed ? {} : { height: `${note.height}px` }),
|
|
580
|
+
}, onPointerDown: () => { setActiveNote(note.id); }, children: [_jsxs("div", { className: css.noteWinBar, onPointerDown: (event) => { beginNoteBarDrag(event, note.id); }, onDoubleClick: () => { patchNote(note.id, { collapsed: false }); }, children: [_jsx("span", { className: css.noteWinGripBar, "aria-hidden": "true", children: "\u283F" }), editingTitle === note.id
|
|
581
|
+
? (_jsxs(_Fragment, { children: [_jsx("input", { className: css.noteWinTitle, value: titleDraft, autoFocus: true, placeholder: t('noteUntitled'), onChange: (event) => { setTitleDraft(event.target.value); }, onKeyDown: (event) => {
|
|
582
|
+
if (event.key === 'Enter')
|
|
583
|
+
commitTitle(note.id);
|
|
584
|
+
if (event.key === 'Escape')
|
|
585
|
+
setEditingTitle(null);
|
|
586
|
+
} }), _jsx("button", { type: "button", className: css.noteWinBtn, title: t('noteConfirmTitle'), onClick: () => { commitTitle(note.id); }, children: "\u2713" })] }))
|
|
587
|
+
: (_jsxs(_Fragment, { children: [_jsx("span", { className: css.noteWinTitleText, title: note.title.trim() === '' ? t('noteUntitled') : note.title, children: note.title.trim() === '' ? t('noteUntitled') : note.title }), _jsx("button", { type: "button", className: css.noteWinBtn, title: t('noteEditTitle'), onClick: () => { setEditingTitle(note.id); setTitleDraft(note.title); }, children: "\u270E" })] })), _jsx("button", { type: "button", className: css.noteWinBtn, title: note.collapsed ? t('noteExpand') : t('noteCollapse'), onClick: () => { patchNote(note.id, { collapsed: !note.collapsed }); }, children: note.collapsed ? '▸' : '▾' }), _jsx("button", { type: "button", className: css.noteWinBtn, title: t('noteClose'), onClick: () => { closeNote(note.id); }, children: "\u00D7" })] }), !note.collapsed && (_jsxs(_Fragment, { children: [_jsx("textarea", { className: css.noteWinText, value: note.text, placeholder: t('notePlaceholder'), spellCheck: false, onChange: (event) => { patchNote(note.id, { text: event.target.value }, false); } }), _jsx("span", { className: css.noteWinResize, title: t('noteResizeHint'), onPointerDown: (event) => {
|
|
588
|
+
beginResize(event, event.currentTarget.parentElement, size => { patchNote(note.id, size, false); });
|
|
589
|
+
} })] }))] }, note.id))), noteMenu !== null && (_jsxs(_Fragment, { children: [_jsx("div", { className: css.noteMenuBackdrop, onPointerDown: () => { setNoteMenu(null); }, onContextMenu: (event) => { event.preventDefault(); setNoteMenu(null); } }), _jsxs("div", { className: css.noteMenu, style: { left: `${noteMenu.x}px`, top: `${noteMenu.y}px` }, children: [_jsx("button", { type: "button", className: css.noteMenuItem, onClick: () => { closeNote(noteMenu.id); setNoteMenu(null); }, children: t('noteClose') }), _jsx("button", { type: "button", className: css.noteMenuItem, onClick: () => { removeNote(noteMenu.id); }, children: t('noteDelete') })] })] }))] }));
|
|
390
590
|
}
|
|
391
591
|
//# sourceMappingURL=TokenQuotaPanel.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TOKEN_QUOTA_MAX_NOTES } from '@jxgame2020/dsh-token-quota/types';
|
|
1
2
|
import { createTokenQuotaPanelStore } from "./store.js";
|
|
2
3
|
import { TokenQuotaPanel } from "./TokenQuotaPanel.js";
|
|
3
4
|
import { TokenQuotaMusic } from "./music.js";
|
|
@@ -89,6 +90,30 @@ export function apply(ctx) {
|
|
|
89
90
|
window.removeEventListener('pointerdown', autoStartMusic);
|
|
90
91
|
window.removeEventListener('keydown', autoStartMusic);
|
|
91
92
|
}, 'token-quota-ui: music autostart gestures');
|
|
93
|
+
/**
|
|
94
|
+
* Coerce the persisted note array into complete notes: older documents may
|
|
95
|
+
* lack fields added later, and a hand-edited file may hold junk. The cap is
|
|
96
|
+
* enforced here too so a tampered document cannot grow past it.
|
|
97
|
+
*/
|
|
98
|
+
const normalizeNotes = (raw) => {
|
|
99
|
+
if (!Array.isArray(raw))
|
|
100
|
+
return [];
|
|
101
|
+
return raw.slice(0, TOKEN_QUOTA_MAX_NOTES).flatMap((note) => {
|
|
102
|
+
if (typeof note !== 'object' || note === null || typeof note.id !== 'string')
|
|
103
|
+
return [];
|
|
104
|
+
return [{
|
|
105
|
+
id: note.id,
|
|
106
|
+
title: typeof note.title === 'string' ? note.title : '',
|
|
107
|
+
text: typeof note.text === 'string' ? note.text : '',
|
|
108
|
+
x: typeof note.x === 'number' ? note.x : 80,
|
|
109
|
+
y: typeof note.y === 'number' ? note.y : 80,
|
|
110
|
+
width: typeof note.width === 'number' ? note.width : 220,
|
|
111
|
+
height: typeof note.height === 'number' ? note.height : 220,
|
|
112
|
+
collapsed: note.collapsed === true,
|
|
113
|
+
visible: note.visible !== false,
|
|
114
|
+
}];
|
|
115
|
+
});
|
|
116
|
+
};
|
|
92
117
|
/** Build a writable music-settings clone with defaults applied. */
|
|
93
118
|
const musicDefaults = (raw) => ({
|
|
94
119
|
enabled: raw?.enabled ?? false,
|
|
@@ -251,6 +276,7 @@ export function apply(ctx) {
|
|
|
251
276
|
bound?.setCheckUpdates(doc?.checkUpdates ?? true);
|
|
252
277
|
bound?.setDimWhenIdle(doc?.dimWhenIdle ?? false);
|
|
253
278
|
bound?.setOrder(Array.isArray(doc?.order) ? doc.order : []);
|
|
279
|
+
bound?.setNotes(normalizeNotes(doc?.notes));
|
|
254
280
|
bound?.setReset(lastReset);
|
|
255
281
|
const musicSettings = musicDefaults(doc?.music);
|
|
256
282
|
lastOnlyCurrentSession = musicSettings.onlyCurrentSession;
|
|
@@ -338,6 +364,10 @@ export function apply(ctx) {
|
|
|
338
364
|
bound?.setOrder(order);
|
|
339
365
|
void scope.set('order', order);
|
|
340
366
|
};
|
|
367
|
+
/** Persist the floating scratchpad notes (content, title, placement). */
|
|
368
|
+
const saveNotes = (notes) => {
|
|
369
|
+
void scope.set('notes', notes.slice(0, TOKEN_QUOTA_MAX_NOTES));
|
|
370
|
+
};
|
|
341
371
|
const setMusicEnabled = (enabled) => {
|
|
342
372
|
const doc = scope.getSnapshot().value;
|
|
343
373
|
const current = musicDefaults(doc?.music);
|
|
@@ -455,7 +485,7 @@ export function apply(ctx) {
|
|
|
455
485
|
load, setLimit, selectModel, setMonitored, setOnFull, setReset,
|
|
456
486
|
setCheckUpdates, checkUpdatesNow, clearLogBefore, setDimWhenIdle,
|
|
457
487
|
setMusicEnabled, setMusicVolume, setMusicStyle, setMusicOnlyCurrentSession,
|
|
458
|
-
setModelOrder,
|
|
488
|
+
setModelOrder, saveNotes,
|
|
459
489
|
};
|
|
460
490
|
};
|
|
461
491
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
@@ -29,6 +29,17 @@ export declare const zh: {
|
|
|
29
29
|
settingsTabMusic: string;
|
|
30
30
|
settingsTabOther: string;
|
|
31
31
|
reorderHint: string;
|
|
32
|
+
noteNewHint: string;
|
|
33
|
+
noteMaxHint: string;
|
|
34
|
+
noteUntitled: string;
|
|
35
|
+
noteEditTitle: string;
|
|
36
|
+
noteConfirmTitle: string;
|
|
37
|
+
noteCollapse: string;
|
|
38
|
+
noteExpand: string;
|
|
39
|
+
noteClose: string;
|
|
40
|
+
noteDelete: string;
|
|
41
|
+
noteResizeHint: string;
|
|
42
|
+
notePlaceholder: string;
|
|
32
43
|
monitorLabel: string;
|
|
33
44
|
monitorHint: string;
|
|
34
45
|
monitorAll: string;
|
|
@@ -113,6 +124,17 @@ export declare const en: {
|
|
|
113
124
|
settingsTabMusic: string;
|
|
114
125
|
settingsTabOther: string;
|
|
115
126
|
reorderHint: string;
|
|
127
|
+
noteNewHint: string;
|
|
128
|
+
noteMaxHint: string;
|
|
129
|
+
noteUntitled: string;
|
|
130
|
+
noteEditTitle: string;
|
|
131
|
+
noteConfirmTitle: string;
|
|
132
|
+
noteCollapse: string;
|
|
133
|
+
noteExpand: string;
|
|
134
|
+
noteClose: string;
|
|
135
|
+
noteDelete: string;
|
|
136
|
+
noteResizeHint: string;
|
|
137
|
+
notePlaceholder: string;
|
|
116
138
|
monitorLabel: string;
|
|
117
139
|
monitorHint: string;
|
|
118
140
|
monitorAll: string;
|
|
@@ -29,6 +29,17 @@ export const zh = {
|
|
|
29
29
|
settingsTabMusic: '音乐设置',
|
|
30
30
|
settingsTabOther: '其它',
|
|
31
31
|
reorderHint: '拖动排序',
|
|
32
|
+
noteNewHint: '新建笔记(最多 5 个)',
|
|
33
|
+
noteMaxHint: '最多 5 个笔记',
|
|
34
|
+
noteUntitled: '未命名',
|
|
35
|
+
noteEditTitle: '编辑标题',
|
|
36
|
+
noteConfirmTitle: '完成编辑',
|
|
37
|
+
noteCollapse: '收起',
|
|
38
|
+
noteExpand: '展开',
|
|
39
|
+
noteClose: '关闭',
|
|
40
|
+
noteDelete: '删除',
|
|
41
|
+
noteResizeHint: '拖动改变大小',
|
|
42
|
+
notePlaceholder: '在这里记录…',
|
|
32
43
|
monitorLabel: '监控模型',
|
|
33
44
|
monitorHint: '未勾选的模型不显示、不计入用量、不受限额限制',
|
|
34
45
|
monitorAll: '全部',
|
|
@@ -111,6 +122,17 @@ export const en = {
|
|
|
111
122
|
settingsTabMusic: 'Music',
|
|
112
123
|
settingsTabOther: 'Other',
|
|
113
124
|
reorderHint: 'Drag to reorder',
|
|
125
|
+
noteNewHint: 'New note (max 5)',
|
|
126
|
+
noteMaxHint: 'At most 5 notes',
|
|
127
|
+
noteUntitled: 'Untitled',
|
|
128
|
+
noteEditTitle: 'Edit title',
|
|
129
|
+
noteConfirmTitle: 'Done',
|
|
130
|
+
noteCollapse: 'Collapse',
|
|
131
|
+
noteExpand: 'Expand',
|
|
132
|
+
noteClose: 'Close',
|
|
133
|
+
noteDelete: 'Delete',
|
|
134
|
+
noteResizeHint: 'Drag to resize',
|
|
135
|
+
notePlaceholder: 'Write here…',
|
|
114
136
|
monitorLabel: 'Monitored models',
|
|
115
137
|
monitorHint: 'Unchecked models are hidden, not metered, and never capped',
|
|
116
138
|
monitorAll: 'All',
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-store';
|
|
11
11
|
import type { ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types';
|
|
12
|
-
import type { TokenQuotaFullAction, TokenQuotaLog, TokenQuotaMusicStyle, TokenQuotaReset, TokenQuotaSnapshot, TokenQuotaUpgrade } from '@jxgame2020/dsh-token-quota/types';
|
|
12
|
+
import type { TokenQuotaFullAction, TokenQuotaLog, TokenQuotaMusicStyle, TokenQuotaNote, TokenQuotaReset, TokenQuotaSnapshot, TokenQuotaUpgrade } from '@jxgame2020/dsh-token-quota/types';
|
|
13
13
|
/** One rendered model row: directory identity merged with the quota snapshot. */
|
|
14
14
|
export interface ModelQuotaRow {
|
|
15
15
|
/** Stable key `provider/model`. */
|
|
@@ -55,6 +55,14 @@ export interface TokenQuotaPanelState {
|
|
|
55
55
|
* Keys not listed keep their catalog order after the listed ones.
|
|
56
56
|
*/
|
|
57
57
|
order: string[];
|
|
58
|
+
/** Floating scratchpad notes as persisted by the Host. */
|
|
59
|
+
notes: TokenQuotaNote[];
|
|
60
|
+
/**
|
|
61
|
+
* Whether the notes above have been read from the Host at least once. The
|
|
62
|
+
* panel seeds its local editing copy exactly once, on this flip, so a
|
|
63
|
+
* background poll can never clobber what the user is typing.
|
|
64
|
+
*/
|
|
65
|
+
notesLoaded: boolean;
|
|
58
66
|
/** Live music settings (mirrored from the settings document). */
|
|
59
67
|
musicEnabled: boolean;
|
|
60
68
|
musicVolume: number;
|
|
@@ -104,6 +112,8 @@ export type TokenQuotaPanelActions = {
|
|
|
104
112
|
setDimWhenIdle: (d: TokenQuotaPanelState, dimWhenIdle: boolean) => void;
|
|
105
113
|
/** Replace the drag-to-reorder model display order. */
|
|
106
114
|
setOrder: (d: TokenQuotaPanelState, order: string[]) => void;
|
|
115
|
+
/** Adopt the Host's persisted notes and mark them as loaded. */
|
|
116
|
+
setNotes: (d: TokenQuotaPanelState, notes: TokenQuotaNote[]) => void;
|
|
107
117
|
setMusicEnabled: (d: TokenQuotaPanelState, enabled: boolean) => void;
|
|
108
118
|
setMusicVolume: (d: TokenQuotaPanelState, volume: number) => void;
|
|
109
119
|
setMusicStyle: (d: TokenQuotaPanelState, style: TokenQuotaMusicStyle) => void;
|
|
@@ -29,6 +29,8 @@ export function createTokenQuotaPanelStore() {
|
|
|
29
29
|
checkUpdates: true,
|
|
30
30
|
dimWhenIdle: false,
|
|
31
31
|
order: [],
|
|
32
|
+
notes: [],
|
|
33
|
+
notesLoaded: false,
|
|
32
34
|
musicEnabled: false,
|
|
33
35
|
musicVolume: 0.5,
|
|
34
36
|
musicStyle: 'pentatonic',
|
|
@@ -59,6 +61,7 @@ export function createTokenQuotaPanelStore() {
|
|
|
59
61
|
setCheckUpdates: (d, checkUpdates) => { d.checkUpdates = checkUpdates; },
|
|
60
62
|
setDimWhenIdle: (d, dimWhenIdle) => { d.dimWhenIdle = dimWhenIdle; },
|
|
61
63
|
setOrder: (d, order) => { d.order = order; },
|
|
64
|
+
setNotes: (d, notes) => { d.notes = notes; d.notesLoaded = true; },
|
|
62
65
|
setMusicEnabled: (d, enabled) => { d.musicEnabled = enabled; },
|
|
63
66
|
setMusicVolume: (d, volume) => { d.musicVolume = volume; },
|
|
64
67
|
setMusicStyle: (d, style) => { d.musicStyle = style; },
|