dsh-plugin-thinking-size 0.1.0 → 0.2.0
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/README.i18n.yaml +2 -2
- package/README.md +3 -2
- package/README.zh.md +4 -2
- package/lib/client.js +190 -12
- package/package.json +1 -1
package/README.i18n.yaml
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
# Both languages carry equal authority; after editing either side, bring the other
|
|
4
4
|
# along and re-record with:
|
|
5
5
|
# git hash-object README.md README.zh.md
|
|
6
|
-
README.md:
|
|
7
|
-
README.zh.md:
|
|
6
|
+
README.md: 7d01b07efae2dac9cc62b5008d4518d328f321bb
|
|
7
|
+
README.zh.md: b345ae93b49387b067d2adcb1fc31ca784938190
|
package/README.md
CHANGED
|
@@ -12,12 +12,13 @@ English | [中文](README.zh.md)
|
|
|
12
12
|
| Format | Rule |
|
|
13
13
|
| --- | --- |
|
|
14
14
|
| `Think(128)` | Below 1000 tokens: plain integer |
|
|
15
|
-
| `Think(5.2K)` | 1000 – 999 499:
|
|
16
|
-
| `Think(1.2M)` | A million or more:
|
|
15
|
+
| `Think(5.2K)` | 1000 – 999 499: 1 decimal + K, fixed digits (`5000 → 5.0K`) |
|
|
16
|
+
| `Think(1.2M)` | A million or more: 1 decimal + M (`1000000 → 1.0M`) |
|
|
17
17
|
|
|
18
18
|
- **Live** — the number ticks up while the reasoning is streaming.
|
|
19
19
|
- **Persistent** — every historical Think row keeps its badge too, across expand/collapse and session switches.
|
|
20
20
|
- **Subtle** — a 12px caption-colored span in tabular figures, appended inside the row title and marked `aria-hidden`, so the accessible title still reads just "Think".
|
|
21
|
+
- **Adjustable precision** — hover the badge to pick **1 or 2 decimal places** (default 1); the choice applies to every badge instantly and is remembered in `localStorage`, surviving reloads and restarts.
|
|
21
22
|
|
|
22
23
|
## 雷霆大思考, measured
|
|
23
24
|
|
package/README.zh.md
CHANGED
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
| 形态 | 规则 |
|
|
15
15
|
| --- | --- |
|
|
16
16
|
| `Think(128)` | 不足 1000:整数 |
|
|
17
|
-
| `Think(5.2K)` | 1000 – 999 499:一位小数 + K
|
|
18
|
-
| `Think(1.2M)` | 百万以上:一位小数 + M |
|
|
17
|
+
| `Think(5.2K)` | 1000 – 999 499:一位小数 + K,固定位数(`5000 → 5.0K`) |
|
|
18
|
+
| `Think(1.2M)` | 百万以上:一位小数 + M(`1000000 → 1.0M`) |
|
|
19
19
|
|
|
20
20
|
- **实时** —— 思考流式输出期间数字随之跳动。
|
|
21
21
|
- **持久** —— 历史消息的每个 Think 行都保留徽标,展开/折叠、切换会话均不丢失。
|
|
22
22
|
- **克制** —— 12px、说明文字色、等宽数字的 `aria-hidden` span,追加在行标题内;
|
|
23
23
|
标题的可读文本仍只有 "Think"。
|
|
24
|
+
- **精度可调** —— hover 徽标即可在 **1 位 / 2 位小数** 间切换(默认 1 位),全部徽标即时
|
|
25
|
+
生效;选择经 localStorage 记忆,刷新、重启后仍保留。
|
|
24
26
|
|
|
25
27
|
## 亲眼看看「雷霆大思考」有多长
|
|
26
28
|
|
package/lib/client.js
CHANGED
|
@@ -34,13 +34,14 @@ module.exports = __toCommonJS(index_exports);
|
|
|
34
34
|
|
|
35
35
|
// src/client/dom.ts
|
|
36
36
|
var BADGE_CLASS = "dsh-thinking-size-badge";
|
|
37
|
+
var PICKER_CLASS = "dsh-thinking-size-picker";
|
|
37
38
|
var STYLE_ID = "dsh-thinking-size-style";
|
|
38
39
|
var BADGE_ATTR = "data-dsh-thinking-size";
|
|
39
40
|
function injectStyle(doc = document) {
|
|
40
41
|
if (doc.getElementById(STYLE_ID) !== null) return;
|
|
41
42
|
const tag = doc.createElement("style");
|
|
42
43
|
tag.id = STYLE_ID;
|
|
43
|
-
tag.textContent = `.${BADGE_CLASS}{color:var(--dsw-alias-label-caption,#8a8a8a);font-size:12px;font-weight:400;margin-left:2px;font-variant-numeric:tabular-nums;user-select:none}`;
|
|
44
|
+
tag.textContent = `.${BADGE_CLASS}{color:var(--dsw-alias-label-caption,#8a8a8a);font-size:12px;font-weight:400;margin-left:2px;font-variant-numeric:tabular-nums;user-select:none}.${BADGE_CLASS}:hover{text-decoration:underline dotted;cursor:default}.${PICKER_CLASS}{position:absolute;z-index:9999;display:flex;gap:6px;align-items:center;padding:6px 8px;font-size:12px;line-height:1;border-radius:8px;white-space:nowrap;background:var(--dsw-alias-bg-layer-3,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));box-shadow:0 4px 12px rgba(0,0,0,.08);color:var(--dsw-alias-label-primary,#333);font-variant-numeric:tabular-nums;user-select:none}.${PICKER_CLASS}[hidden]{display:none}.${PICKER_CLASS} button{appearance:none;border:none;background:transparent;color:inherit;font:inherit;padding:3px 10px;border-radius:6px;cursor:pointer}.${PICKER_CLASS} button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.06))}.${PICKER_CLASS} button[aria-pressed="true"]{background:var(--dsw-alias-interactive-bg-active,#f0f0f0);font-weight:500}`;
|
|
44
45
|
doc.head.appendChild(tag);
|
|
45
46
|
}
|
|
46
47
|
function anchorKeyOf(row) {
|
|
@@ -193,6 +194,175 @@ function createReasoningSource(sessions) {
|
|
|
193
194
|
};
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
// src/client/settings.ts
|
|
198
|
+
var DECIMALS_STORAGE_KEY = "dsh-thinking-size.decimals";
|
|
199
|
+
var DEFAULT_DECIMALS = 1;
|
|
200
|
+
function parseRaw(raw) {
|
|
201
|
+
if (raw === "1") return 1;
|
|
202
|
+
if (raw === "2") return 2;
|
|
203
|
+
return DEFAULT_DECIMALS;
|
|
204
|
+
}
|
|
205
|
+
function resolveStorage(storage) {
|
|
206
|
+
if (storage !== void 0) return storage;
|
|
207
|
+
try {
|
|
208
|
+
return globalThis.localStorage ?? null;
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function createDecimalsStore(storage) {
|
|
214
|
+
const impl = resolveStorage(storage);
|
|
215
|
+
let value = DEFAULT_DECIMALS;
|
|
216
|
+
try {
|
|
217
|
+
value = parseRaw(impl?.getItem(DECIMALS_STORAGE_KEY) ?? null);
|
|
218
|
+
} catch {
|
|
219
|
+
value = DEFAULT_DECIMALS;
|
|
220
|
+
}
|
|
221
|
+
let disposed = false;
|
|
222
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
223
|
+
return {
|
|
224
|
+
get: () => value,
|
|
225
|
+
set(v) {
|
|
226
|
+
if (disposed) return;
|
|
227
|
+
const next = v === 2 ? 2 : 1;
|
|
228
|
+
if (next === value) return;
|
|
229
|
+
value = next;
|
|
230
|
+
try {
|
|
231
|
+
impl?.setItem(DECIMALS_STORAGE_KEY, String(next));
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
for (const listener of listeners) {
|
|
235
|
+
try {
|
|
236
|
+
listener();
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
subscribe(fn) {
|
|
242
|
+
listeners.add(fn);
|
|
243
|
+
return () => {
|
|
244
|
+
listeners.delete(fn);
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
dispose() {
|
|
248
|
+
disposed = true;
|
|
249
|
+
listeners.clear();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/client/picker.ts
|
|
255
|
+
function createPicker(deps) {
|
|
256
|
+
const { doc, store } = deps;
|
|
257
|
+
const hideDelayMs = deps.hideDelayMs ?? 120;
|
|
258
|
+
let popover = null;
|
|
259
|
+
let anchor = null;
|
|
260
|
+
let hideTimer = null;
|
|
261
|
+
const clearHideTimer = () => {
|
|
262
|
+
if (hideTimer !== null) {
|
|
263
|
+
clearTimeout(hideTimer);
|
|
264
|
+
hideTimer = null;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const ensurePopover = () => {
|
|
268
|
+
if (popover !== null) return popover;
|
|
269
|
+
const el = doc.createElement("div");
|
|
270
|
+
el.className = PICKER_CLASS;
|
|
271
|
+
el.hidden = true;
|
|
272
|
+
el.innerHTML = '<span>\u5C0F\u6570\u4F4D\u6570</span><button type="button" data-decimals="1" aria-pressed="false">1</button><button type="button" data-decimals="2" aria-pressed="false">2</button>';
|
|
273
|
+
el.addEventListener("click", onButtonClick);
|
|
274
|
+
doc.body.appendChild(el);
|
|
275
|
+
popover = el;
|
|
276
|
+
return el;
|
|
277
|
+
};
|
|
278
|
+
const refreshPressed = () => {
|
|
279
|
+
if (popover === null) return;
|
|
280
|
+
const current = String(store.get());
|
|
281
|
+
for (const button of popover.querySelectorAll("button[data-decimals]")) {
|
|
282
|
+
button.setAttribute(
|
|
283
|
+
"aria-pressed",
|
|
284
|
+
button.getAttribute("data-decimals") === current ? "true" : "false"
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const close = () => {
|
|
289
|
+
clearHideTimer();
|
|
290
|
+
anchor = null;
|
|
291
|
+
if (popover !== null) popover.hidden = true;
|
|
292
|
+
};
|
|
293
|
+
const open = (badge) => {
|
|
294
|
+
clearHideTimer();
|
|
295
|
+
anchor = badge;
|
|
296
|
+
const el = ensurePopover();
|
|
297
|
+
refreshPressed();
|
|
298
|
+
el.hidden = false;
|
|
299
|
+
const win = doc.defaultView;
|
|
300
|
+
const rect = badge.getBoundingClientRect();
|
|
301
|
+
const scrollX = win?.scrollX ?? 0;
|
|
302
|
+
const scrollY = win?.scrollY ?? 0;
|
|
303
|
+
let left = rect.left + scrollX;
|
|
304
|
+
const width = el.offsetWidth || 96;
|
|
305
|
+
const maxLeft = (win?.innerWidth ?? 1280) - width - 4;
|
|
306
|
+
if (left > maxLeft) left = Math.max(4, maxLeft);
|
|
307
|
+
el.style.left = `${Math.max(4, left)}px`;
|
|
308
|
+
el.style.top = `${Math.max(4, rect.bottom + scrollY + 4)}px`;
|
|
309
|
+
};
|
|
310
|
+
const scheduleHide = () => {
|
|
311
|
+
clearHideTimer();
|
|
312
|
+
hideTimer = setTimeout(() => {
|
|
313
|
+
hideTimer = null;
|
|
314
|
+
close();
|
|
315
|
+
}, hideDelayMs);
|
|
316
|
+
};
|
|
317
|
+
function onButtonClick(event) {
|
|
318
|
+
const target = event.target;
|
|
319
|
+
if (target === null || typeof target.closest !== "function") return;
|
|
320
|
+
const button = target.closest("button[data-decimals]");
|
|
321
|
+
if (button === null) return;
|
|
322
|
+
store.set(button.getAttribute("data-decimals") === "2" ? 2 : 1);
|
|
323
|
+
refreshPressed();
|
|
324
|
+
}
|
|
325
|
+
const contains = (container, node) => node != null && typeof node.contains === "function" && (container === node || container.contains(node));
|
|
326
|
+
const onOver = (event) => {
|
|
327
|
+
const target = event.target;
|
|
328
|
+
if (target === null || typeof target.closest !== "function") return;
|
|
329
|
+
if (popover !== null && contains(popover, target)) {
|
|
330
|
+
clearHideTimer();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const badge = target.closest("[data-dsh-thinking-size]");
|
|
334
|
+
if (badge !== null) open(badge);
|
|
335
|
+
};
|
|
336
|
+
const onOut = (event) => {
|
|
337
|
+
if (anchor === null) return;
|
|
338
|
+
const to = event.relatedTarget;
|
|
339
|
+
if (contains(anchor, to)) return;
|
|
340
|
+
if (popover !== null && contains(popover, to)) return;
|
|
341
|
+
scheduleHide();
|
|
342
|
+
};
|
|
343
|
+
const onScroll = () => {
|
|
344
|
+
if (anchor === null) return;
|
|
345
|
+
close();
|
|
346
|
+
};
|
|
347
|
+
doc.body.addEventListener("mouseover", onOver);
|
|
348
|
+
doc.body.addEventListener("mouseout", onOut);
|
|
349
|
+
doc.addEventListener("scroll", onScroll, true);
|
|
350
|
+
return {
|
|
351
|
+
prune() {
|
|
352
|
+
if (anchor !== null && !anchor.isConnected) close();
|
|
353
|
+
},
|
|
354
|
+
dispose() {
|
|
355
|
+
doc.body.removeEventListener("mouseover", onOver);
|
|
356
|
+
doc.body.removeEventListener("mouseout", onOut);
|
|
357
|
+
doc.removeEventListener("scroll", onScroll, true);
|
|
358
|
+
clearHideTimer();
|
|
359
|
+
anchor = null;
|
|
360
|
+
popover?.remove();
|
|
361
|
+
popover = null;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
196
366
|
// src/client/tokens.ts
|
|
197
367
|
var CJK = /[\u4E00-\u9FFF\u3400-\u4DBF\u3000-\u303F\uFF00-\uFFEF]/;
|
|
198
368
|
var TOKEN_PER_OTHER_CHAR = 0.3;
|
|
@@ -215,17 +385,12 @@ function tokensOfBlock(block) {
|
|
|
215
385
|
blockCache.set(block, tokens);
|
|
216
386
|
return tokens;
|
|
217
387
|
}
|
|
218
|
-
function formatTokens(n) {
|
|
388
|
+
function formatTokens(n, decimals = 1) {
|
|
389
|
+
const d = decimals === 2 ? 2 : 1;
|
|
219
390
|
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
220
391
|
if (n < 1e3) return String(Math.floor(n));
|
|
221
|
-
if (n < 999500)
|
|
222
|
-
|
|
223
|
-
if (s2.endsWith(".0")) s2 = s2.slice(0, -2);
|
|
224
|
-
return s2 + "K";
|
|
225
|
-
}
|
|
226
|
-
let s = (n / 1e6).toFixed(1);
|
|
227
|
-
if (s.endsWith(".0")) s = s.slice(0, -2);
|
|
228
|
-
return s + "M";
|
|
392
|
+
if (n < 999500) return (n / 1e3).toFixed(d) + "K";
|
|
393
|
+
return (n / 1e6).toFixed(d) + "M";
|
|
229
394
|
}
|
|
230
395
|
|
|
231
396
|
// src/client/controller.ts
|
|
@@ -250,6 +415,13 @@ function createController(deps) {
|
|
|
250
415
|
dirty = true;
|
|
251
416
|
})
|
|
252
417
|
);
|
|
418
|
+
if (deps.store !== void 0) {
|
|
419
|
+
disposers.push(
|
|
420
|
+
deps.store.subscribe(() => {
|
|
421
|
+
dirty = true;
|
|
422
|
+
})
|
|
423
|
+
);
|
|
424
|
+
}
|
|
253
425
|
const Observer = doc.defaultView?.MutationObserver ?? globalThis.MutationObserver;
|
|
254
426
|
const observer = new Observer(() => {
|
|
255
427
|
dirty = true;
|
|
@@ -276,8 +448,9 @@ function createController(deps) {
|
|
|
276
448
|
}
|
|
277
449
|
const title = titleSpanOf(row);
|
|
278
450
|
if (title === null) continue;
|
|
279
|
-
writeBadge(title, `(${formatTokens(tokensOfBlock(block))})`);
|
|
451
|
+
writeBadge(title, `(${formatTokens(tokensOfBlock(block), deps.store?.get() ?? 1)})`);
|
|
280
452
|
}
|
|
453
|
+
deps.picker?.prune();
|
|
281
454
|
}
|
|
282
455
|
return {
|
|
283
456
|
sweep,
|
|
@@ -306,13 +479,18 @@ function apply(ctx) {
|
|
|
306
479
|
}
|
|
307
480
|
const sessions = ctx.get("sessions");
|
|
308
481
|
if (sessions === void 0) return;
|
|
482
|
+
const store = createDecimalsStore();
|
|
483
|
+
const picker = createPicker({ doc: document, store });
|
|
309
484
|
const source = createReasoningSource(sessions);
|
|
310
485
|
const controller = createController({
|
|
311
486
|
source,
|
|
312
|
-
interval: (fn, ms) => ctx.interval(fn, ms)
|
|
487
|
+
interval: (fn, ms) => ctx.interval(fn, ms),
|
|
488
|
+
store,
|
|
489
|
+
picker
|
|
313
490
|
});
|
|
314
491
|
return () => {
|
|
315
492
|
controller.dispose();
|
|
493
|
+
picker.dispose();
|
|
316
494
|
source.dispose();
|
|
317
495
|
};
|
|
318
496
|
}
|