@underactive/pi-topping 0.6.4 → 0.6.5
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.md +81 -81
- package/package.json +1 -1
- package/src/session.ts +19 -2
- package/src/util.ts +9 -0
- package/src/word-packs.ts +4 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ We garnish our pies. It seemed rude not to extend Pi the same courtesy. This is
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
**“Working” Loader** — animated spinner (with color choice), randomized activity word, optional word packs, shimmer
|
|
9
|
+
**“Working” Loader** — animated spinner (with color choice), randomized activity word, optional word packs, shimmer, token activity monitor, elapsed timer, output token display, live output-token rate, and the response model when it meaningfully differs from the selected model — all arrangeable left to right. During a blocking extension prompt, the loader switches to a stable dim waiting line with a pulsing indicator, then resumes afterward.
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
@@ -14,83 +14,91 @@ We garnish our pies. It seemed rude not to extend Pi the same courtesy. This is
|
|
|
14
14
|
|
|
15
15
|

|
|
16
16
|
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pi install npm:@underactive/pi-topping
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Restart Pi (or run `/reload`) to pick it up.
|
|
24
|
+
|
|
17
25
|
## Settings
|
|
18
26
|
|
|
19
27
|
Run `/topping-settings` (TUI only) to customize your toppings. Settings persist to
|
|
20
28
|
`~/.pi/agent/pi-topping/settings.json`; hand edits are tolerated on load.
|
|
21
29
|
|
|
22
30
|
```
|
|
23
|
-
╔═[ Pi Topping: Settings ]
|
|
24
|
-
╟─ Preview
|
|
25
|
-
║
|
|
26
|
-
║ ⠋ Crafting… ⣠⣤⣶⣶⣤⣠⣀⢀ 28 tps · 3s · ↓ 84 tokens · test-model
|
|
27
|
-
║
|
|
28
|
-
╟─ User Prompt
|
|
29
|
-
║ [■] High-vis prompt ON
|
|
30
|
-
║ [■] Border style ‹ double ›
|
|
31
|
-
║ [■] Border color ‹ borderAccent ›
|
|
32
|
-
║ [■] Pi icon ON
|
|
33
|
-
║ [■] Timestamp ON
|
|
34
|
-
║ [■] Provider ON
|
|
35
|
-
║ [■] Model ON
|
|
36
|
-
║
|
|
37
|
-
╟─ “Working” Loader
|
|
38
|
-
║ [■] Animated spinner ON
|
|
39
|
-
║ ❯ [■] Animated spinner color ‹ accent ›
|
|
40
|
-
║ [■] Randomize “Working” text ON
|
|
41
|
-
║ [■] Text shimmer ON
|
|
42
|
-
║ [■] Invert shimmer OFF
|
|
43
|
-
║ [■] Text shimmer direction ‹ Left to Right ›
|
|
44
|
-
║ [■] Text shimmer speed ‹ Normal ›
|
|
45
|
-
║ [■] Token activity monitor ON
|
|
46
|
-
║ [■] Token activity monitor color ‹ accent ›
|
|
47
|
-
║ [■] Token activity monitor direction ‹ Right to Left ›
|
|
48
|
-
║ [■] Token activity monitor dimmed OFF
|
|
49
|
-
║ [■] Elapsed time since prompt ON
|
|
50
|
-
║ [■] Show output tokens ON
|
|
51
|
-
║ [■] Token rate ON
|
|
52
|
-
║ [■] Token rate color ‹ warning ›
|
|
53
|
-
║ [■] Token rate dimmed OFF
|
|
54
|
-
║ [■] Response model ON
|
|
55
|
-
║ [■] Response model color ‹ accent ›
|
|
56
|
-
║ [■] Response model dimmed OFF
|
|
57
|
-
║
|
|
58
|
-
╟─ Elements Order
|
|
59
|
-
║ [ ] Animated spinner
|
|
60
|
-
║ [ ] “Working” text
|
|
61
|
-
║ [ ] Token activity monitor
|
|
62
|
-
║ [ ] Token rate
|
|
63
|
-
║ [ ] Elapsed time
|
|
64
|
-
║ [ ] Output tokens
|
|
65
|
-
║ [ ] Response model
|
|
66
|
-
║
|
|
67
|
-
╟─ Completion Marker
|
|
68
|
-
║ [■] Show completion marker ON
|
|
69
|
-
║ [■] Marker style ‹ elite ›
|
|
70
|
-
║ [■] Border style ‹ none ›
|
|
71
|
-
║ [■] Border color ‹ borderAccent ›
|
|
72
|
-
║ [■] Pi icon ON
|
|
73
|
-
║ [■] Randomize “Worked” text ON
|
|
74
|
-
║ [■] Tokens spent ON
|
|
75
|
-
║ [■] Mid-turn inputs ON
|
|
76
|
-
║
|
|
77
|
-
╟─ Word Packs
|
|
78
|
-
║ [ ] Doctor Who OFF
|
|
79
|
-
║ [ ] Firefly OFF
|
|
80
|
-
║ [ ] Hitchhiker's Guide OFF
|
|
81
|
-
║ [ ] The Lord of the Rings OFF
|
|
82
|
-
║ [ ] The Matrix OFF
|
|
83
|
-
║ [ ] Portal OFF
|
|
84
|
-
║ [ ] SimCity OFF
|
|
85
|
-
║ [ ] Star Trek OFF
|
|
86
|
-
║ [ ] Star Wars OFF
|
|
87
|
-
║
|
|
88
|
-
╟─ Options
|
|
89
|
-
║ [■] Use NerdFont icons ON
|
|
90
|
-
|
|
91
|
-
║ ↑↓ move PgUp/PgDn page ←→ select
|
|
92
|
-
║ ␣ toggle ⏎ apply esc cancel
|
|
93
|
-
|
|
31
|
+
╔═[ Pi Topping: Settings ]══════════════════════════════════════╗
|
|
32
|
+
╟─ Preview ─────────────────────────────────────────────────────╢
|
|
33
|
+
║ ║
|
|
34
|
+
║ ⠋ Crafting… ⣠⣤⣶⣶⣤⣠⣀⢀ 28 tps · 3s · ↓ 84 tokens · test-model ║
|
|
35
|
+
║ ║
|
|
36
|
+
╟─ User Prompt ─────────────────────────────────────────────────╢
|
|
37
|
+
║ [■] High-vis prompt ON ║
|
|
38
|
+
║ [■] Border style ‹ double › ║
|
|
39
|
+
║ [■] Border color ‹ borderAccent › ║
|
|
40
|
+
║ [■] Pi icon ON ║
|
|
41
|
+
║ [■] Timestamp ON ║
|
|
42
|
+
║ [■] Provider ON ║
|
|
43
|
+
║ [■] Model ON ║
|
|
44
|
+
║ ║
|
|
45
|
+
╟─ “Working” Loader ────────────────────────────────────────────╢
|
|
46
|
+
║ [■] Animated spinner ON ║
|
|
47
|
+
║ ❯ [■] Animated spinner color ‹ accent › ║
|
|
48
|
+
║ [■] Randomize “Working” text ON ║
|
|
49
|
+
║ [■] Text shimmer ON ║
|
|
50
|
+
║ [■] Invert shimmer OFF ║
|
|
51
|
+
║ [■] Text shimmer direction ‹ Left to Right › ║
|
|
52
|
+
║ [■] Text shimmer speed ‹ Normal › ║
|
|
53
|
+
║ [■] Token activity monitor ON ║
|
|
54
|
+
║ [■] Token activity monitor color ‹ accent › ║
|
|
55
|
+
║ [■] Token activity monitor direction ‹ Right to Left › ║
|
|
56
|
+
║ [■] Token activity monitor dimmed OFF ║
|
|
57
|
+
║ [■] Elapsed time since prompt ON ║
|
|
58
|
+
║ [■] Show output tokens ON ║
|
|
59
|
+
║ [■] Token rate ON ║
|
|
60
|
+
║ [■] Token rate color ‹ warning › ║
|
|
61
|
+
║ [■] Token rate dimmed OFF ║
|
|
62
|
+
║ [■] Response model ON ║
|
|
63
|
+
║ [■] Response model color ‹ accent › ║
|
|
64
|
+
║ [■] Response model dimmed OFF ║
|
|
65
|
+
║ ║
|
|
66
|
+
╟─ Elements Order ──────────────────────────────────────────────╢
|
|
67
|
+
║ [ ] Animated spinner ║
|
|
68
|
+
║ [ ] “Working” text ║
|
|
69
|
+
║ [ ] Token activity monitor ║
|
|
70
|
+
║ [ ] Token rate ║
|
|
71
|
+
║ [ ] Elapsed time ║
|
|
72
|
+
║ [ ] Output tokens ║
|
|
73
|
+
║ [ ] Response model ║
|
|
74
|
+
║ ║
|
|
75
|
+
╟─ Completion Marker ───────────────────────────────────────────╢
|
|
76
|
+
║ [■] Show completion marker ON ║
|
|
77
|
+
║ [■] Marker style ‹ elite › ║
|
|
78
|
+
║ [■] Border style ‹ none › ║
|
|
79
|
+
║ [■] Border color ‹ borderAccent › ║
|
|
80
|
+
║ [■] Pi icon ON ║
|
|
81
|
+
║ [■] Randomize “Worked” text ON ║
|
|
82
|
+
║ [■] Tokens spent ON ║
|
|
83
|
+
║ [■] Mid-turn inputs ON ║
|
|
84
|
+
║ ║
|
|
85
|
+
╟─ Word Packs ──────────────────────────────────────────────────╢
|
|
86
|
+
║ [ ] Doctor Who OFF ║
|
|
87
|
+
║ [ ] Firefly OFF ║
|
|
88
|
+
║ [ ] Hitchhiker's Guide OFF ║
|
|
89
|
+
║ [ ] The Lord of the Rings OFF ║
|
|
90
|
+
║ [ ] The Matrix OFF ║
|
|
91
|
+
║ [ ] Portal OFF ║
|
|
92
|
+
║ [ ] SimCity OFF ║
|
|
93
|
+
║ [ ] Star Trek OFF ║
|
|
94
|
+
║ [ ] Star Wars OFF ║
|
|
95
|
+
║ ║
|
|
96
|
+
╟─ Options ─────────────────────────────────────────────────────╢
|
|
97
|
+
║ [■] Use NerdFont icons ON ║
|
|
98
|
+
╟───────────────────────────────────────────────────────────────╢
|
|
99
|
+
║ ↑↓ move PgUp/PgDn page ←→ select ║
|
|
100
|
+
║ ␣ toggle ⏎ apply esc cancel ║
|
|
101
|
+
╚═══════════════════════════════════════════════════════[ 9/50 ]╝
|
|
94
102
|
```
|
|
95
103
|
|
|
96
104
|
### Word packs
|
|
@@ -123,15 +131,7 @@ full brightness for 1.5 seconds, then fades through five theme-aware shades to t
|
|
|
123
131
|
the next 0.25 seconds before returning to the placeholder; a new count restores full brightness and
|
|
124
132
|
restarts the cycle.
|
|
125
133
|
|
|
126
|
-
The response model is captured from assistant responses, displayed as its sanitized value with no label, and defaults to the final loader detail after output tokens. It can be hidden, recolored, permanently dimmed, or reordered. After the agent settles, its final value remains visible for 3 seconds, fades through five theme-aware shades over 0.5 seconds, then clears; new work cancels the pending fade.
|
|
127
|
-
|
|
128
|
-
## Install
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
pi install npm:@underactive/pi-topping
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Restart Pi (or run `/reload`) to pick it up.
|
|
134
|
+
The response model is captured from assistant responses, displayed as its sanitized value with no label only when it meaningfully differs from the selected model, and defaults to the final loader detail after output tokens. Equivalent local paths and decorated filenames are suppressed. It can be hidden, recolored, permanently dimmed, or reordered. After the agent settles, its final value remains visible for 3 seconds, fades through five theme-aware shades over 0.5 seconds, then clears; new work cancels the pending fade.
|
|
135
135
|
|
|
136
136
|
## Requirements
|
|
137
137
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@underactive/pi-topping",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "A Pi extension that adds quality of life improvements to Pi's UX: a high-vis user prompt, a shimmering working loader with token activity meter and elapsed timer, and an end-of-turn completion marker",
|
|
5
5
|
"keywords": ["pi-package", "pi-extension"],
|
|
6
6
|
"license": "MIT",
|
package/src/session.ts
CHANGED
|
@@ -42,7 +42,7 @@ import { applyMenuResult, buildMenuSections, loadSettings, saveSettings, type Se
|
|
|
42
42
|
import { notifyMissingToppingsOnce } from "./toppings.ts";
|
|
43
43
|
import { PreviewRenderer } from "./preview.ts";
|
|
44
44
|
import { buildCompletionMarkerContent, buildCompletionMarkerLine, PROMPT_BOX_TYPE, promptBoxRenderer, type PromptBoxDetails } from "./prompt-decorator.ts";
|
|
45
|
-
import { stripControlChars } from "./util.ts";
|
|
45
|
+
import { modelsResemble, stripControlChars } from "./util.ts";
|
|
46
46
|
import { loadBundledWordPacks, loadUserWordPacks, pickWorkingTextSelection, type WorkingTextSelection, type WordPack } from "./word-packs.ts";
|
|
47
47
|
|
|
48
48
|
type MessageStartEvent = Extract<ExtensionEvent, { type: "message_start" }>;
|
|
@@ -86,6 +86,8 @@ interface SessionState {
|
|
|
86
86
|
lastTokenRateSampledAt: number;
|
|
87
87
|
lastTokenRateTotal: number;
|
|
88
88
|
responseModel: string;
|
|
89
|
+
/** Last raw responseModel value seen, or the NOT_SENT sentinel before the first call. */
|
|
90
|
+
lastResponseModelRaw: unknown;
|
|
89
91
|
responseModelHoldTimer: ReturnType<typeof setTimeout> | null;
|
|
90
92
|
responseModelFadeTimer: ReturnType<typeof setInterval> | null;
|
|
91
93
|
responseModelFadeGeneration: number;
|
|
@@ -123,6 +125,7 @@ function makeFreshState(): SessionState {
|
|
|
123
125
|
lastTokenRateSampledAt: 0,
|
|
124
126
|
lastTokenRateTotal: 0,
|
|
125
127
|
responseModel: "",
|
|
128
|
+
lastResponseModelRaw: NOT_SENT,
|
|
126
129
|
responseModelHoldTimer: null,
|
|
127
130
|
responseModelFadeTimer: null,
|
|
128
131
|
responseModelFadeGeneration: 0,
|
|
@@ -148,6 +151,7 @@ export class SessionManager {
|
|
|
148
151
|
#settings = loadSettings();
|
|
149
152
|
#userPacks: WordPack[] = [];
|
|
150
153
|
#bundledPacks = loadBundledWordPacks();
|
|
154
|
+
#allPacks: WordPack[] = [...this.#bundledPacks];
|
|
151
155
|
#currentCtx: ExtensionContext | null = null;
|
|
152
156
|
readonly #pi: ExtensionAPI;
|
|
153
157
|
|
|
@@ -163,6 +167,7 @@ export class SessionManager {
|
|
|
163
167
|
this.#state = makeFreshState();
|
|
164
168
|
this.#settings = loadSettings();
|
|
165
169
|
this.#userPacks = loadUserWordPacks();
|
|
170
|
+
this.#allPacks = [...this.#bundledPacks, ...this.#userPacks];
|
|
166
171
|
this.#state.activityMeter.setDirection(this.#settings.decorations.meterDirection);
|
|
167
172
|
if (this.usable(ctx)) {
|
|
168
173
|
this.applyIndicator(ctx);
|
|
@@ -446,7 +451,17 @@ export class SessionManager {
|
|
|
446
451
|
}
|
|
447
452
|
|
|
448
453
|
private updateResponseModel(raw: unknown): void {
|
|
454
|
+
if (raw === this.#state.lastResponseModelRaw) return;
|
|
455
|
+
this.#state.lastResponseModelRaw = raw;
|
|
449
456
|
const responseModel = typeof raw === "string" ? stripControlChars(raw).trim() : "";
|
|
457
|
+
const selectedModel = this.#currentCtx?.model?.id;
|
|
458
|
+
if (responseModel && modelsResemble(selectedModel, responseModel)) {
|
|
459
|
+
if (this.#state.responseModel) {
|
|
460
|
+
this.#state.responseModel = "";
|
|
461
|
+
this.tick();
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
450
465
|
if (responseModel && responseModel !== this.#state.responseModel) {
|
|
451
466
|
this.#state.responseModel = responseModel;
|
|
452
467
|
this.tick();
|
|
@@ -511,7 +526,7 @@ export class SessionManager {
|
|
|
511
526
|
}
|
|
512
527
|
|
|
513
528
|
private pickWorkingWord(): WorkingTextSelection {
|
|
514
|
-
return pickWorkingTextSelection(this.#settings.wordPacks,
|
|
529
|
+
return pickWorkingTextSelection(this.#settings.wordPacks, this.#allPacks);
|
|
515
530
|
}
|
|
516
531
|
|
|
517
532
|
private resetTurn(now: number): void {
|
|
@@ -523,6 +538,7 @@ export class SessionManager {
|
|
|
523
538
|
state.confirmTokens = 0;
|
|
524
539
|
state.liveTokens = 0;
|
|
525
540
|
state.responseModel = "";
|
|
541
|
+
state.lastResponseModelRaw = NOT_SENT;
|
|
526
542
|
state.activityMeter.reset();
|
|
527
543
|
resetTokenRateState(state);
|
|
528
544
|
state.midTurnInputs = 0;
|
|
@@ -639,6 +655,7 @@ export class SessionManager {
|
|
|
639
655
|
}
|
|
640
656
|
|
|
641
657
|
this.#userPacks = loadUserWordPacks();
|
|
658
|
+
this.#allPacks = [...this.#bundledPacks, ...this.#userPacks];
|
|
642
659
|
const before = this.indicatorFingerprint();
|
|
643
660
|
const preview = new PreviewRenderer(ctx, [...this.#bundledPacks, ...this.#userPacks]);
|
|
644
661
|
const result = await showMenu<Record<string, boolean | string>>(ctx, {
|
package/src/util.ts
CHANGED
|
@@ -7,3 +7,12 @@ export function isPlainObject(value: unknown): value is Record<string, unknown>
|
|
|
7
7
|
export function stripControlChars(text: string): string {
|
|
8
8
|
return text.replace(/[\p{Cc}\p{Cf}]/gu, "");
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
/** True when a reported response model appears to be a decorated form of the selected model. */
|
|
12
|
+
export function modelsResemble(selectedModel?: string, responseModel?: string): boolean {
|
|
13
|
+
if (!selectedModel || !responseModel) return false;
|
|
14
|
+
const selectedName = selectedModel.split(/[\\/]/).at(-1)?.toLowerCase() ?? "";
|
|
15
|
+
const selectedTokens = selectedName.match(/[a-z0-9]{2,}/g) ?? [];
|
|
16
|
+
const normalizedResponse = responseModel.toLowerCase();
|
|
17
|
+
return selectedTokens.length > 0 && selectedTokens.every((token) => normalizedResponse.includes(token));
|
|
18
|
+
}
|
package/src/word-packs.ts
CHANGED
|
@@ -84,7 +84,6 @@ export function loadBundledWordPacks(): WordPack[] {
|
|
|
84
84
|
packs.push(pack);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
if (!packs.some((pack) => pack.id === "simcity")) throw new Error("Missing bundled SimCity word pack");
|
|
88
87
|
const bundledIds = new Set(packs.map((pack) => pack.id));
|
|
89
88
|
for (const id of RESERVED_BUNDLED_PACK_IDS) {
|
|
90
89
|
if (!bundledIds.has(id)) throw new Error(`Missing bundled word pack: ${id}`);
|
|
@@ -109,8 +108,10 @@ export function isWordPackEnabled(id: string, enabled: Record<string, boolean>):
|
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
export function selectWorkingTextSelection(enabled: Record<string, boolean>, packs: readonly WordPack[], fraction: number): WorkingTextSelection {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
111
|
+
const enabledWords = packs.filter((pack) => isWordPackEnabled(pack.id, enabled)).flatMap((pack) => pack.words);
|
|
112
|
+
const total = WORDS.length + enabledWords.length;
|
|
113
|
+
const idx = Math.min(total - 1, Math.floor(fraction * total));
|
|
114
|
+
const entry = idx < WORDS.length ? WORDS[idx] : enabledWords[idx - WORDS.length];
|
|
114
115
|
return { text: `${entry.present_tense}…`, pastTense: entry.past_tense };
|
|
115
116
|
}
|
|
116
117
|
|