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 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: a60a5d2e42f935f4f2912e8db12512d6cdda576c
7
- README.zh.md: e9c0ce1bc84cc03fbb25c96e318522a4dcce066a
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: one decimal + K, trailing zeros dropped |
16
- | `Think(1.2M)` | A million or more: one decimal + M |
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,去尾 0 |
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
- let s2 = (n / 1e3).toFixed(1);
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
  }
package/package.json CHANGED
@@ -36,7 +36,7 @@
36
36
  "聊天界面",
37
37
  "雷霆大思考"
38
38
  ],
39
- "version": "0.1.0",
39
+ "version": "0.2.0",
40
40
  "license": "MIT",
41
41
  "repository": {
42
42
  "type": "git",