forked from earendil-works/pi
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathworking-indicator.ts
More file actions
123 lines (111 loc) · 3.35 KB
/
Copy pathworking-indicator.ts
File metadata and controls
123 lines (111 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* Working Indicator Extension
*
* Demonstrates `ctx.ui.setWorkingIndicator()` for customizing the inline
* working indicator shown while pi is streaming a response.
*
* Usage:
* pi --extension examples/extensions/working-indicator.ts
*
* Commands:
* /working-indicator Show current mode
* /working-indicator dot Use a static dot indicator
* /working-indicator pulse Use a custom animated indicator
* /working-indicator none Hide the indicator entirely
* /working-indicator spinner Restore an animated spinner
* /working-indicator reset Restore pi's default spinner
*/
import type { ExtensionAPI, ExtensionContext, WorkingIndicatorOptions } from "@earendil-works/pi-coding-agent";
type WorkingIndicatorMode = "dot" | "none" | "pulse" | "spinner" | "default";
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
const PASTEL_RAINBOW = [
"\x1b[38;2;255;179;186m",
"\x1b[38;2;255;223;186m",
"\x1b[38;2;255;255;186m",
"\x1b[38;2;186;255;201m",
"\x1b[38;2;186;225;255m",
"\x1b[38;2;218;186;255m",
];
const RESET_FG = "\x1b[39m";
const HIDDEN_INDICATOR: WorkingIndicatorOptions = {
frames: [],
};
function colorize(text: string, color: string): string {
return `${color}${text}${RESET_FG}`;
}
function getIndicator(mode: WorkingIndicatorMode): WorkingIndicatorOptions | undefined {
switch (mode) {
case "dot":
return {
frames: [colorize("●", PASTEL_RAINBOW[0])],
};
case "none":
return HIDDEN_INDICATOR;
case "pulse":
return {
frames: [
colorize("·", PASTEL_RAINBOW[0]),
colorize("•", PASTEL_RAINBOW[2]),
colorize("●", PASTEL_RAINBOW[4]),
colorize("•", PASTEL_RAINBOW[5]),
],
intervalMs: 120,
};
case "spinner":
return {
frames: SPINNER_FRAMES.map((frame, index) =>
colorize(frame, PASTEL_RAINBOW[index % PASTEL_RAINBOW.length]!),
),
intervalMs: 80,
};
case "default":
return undefined;
}
}
function describeMode(mode: WorkingIndicatorMode): string {
switch (mode) {
case "dot":
return "static dot";
case "none":
return "hidden";
case "pulse":
return "custom pulse";
case "spinner":
return "custom spinner";
case "default":
return "pi default spinner";
}
}
export default function (pi: ExtensionAPI) {
let mode: WorkingIndicatorMode = "spinner";
const applyIndicator = (ctx: ExtensionContext) => {
ctx.ui.setWorkingIndicator(getIndicator(mode));
ctx.ui.setStatus("working-indicator", ctx.ui.theme.fg("dim", `Indicator: ${describeMode(mode)}`));
};
pi.on("session_start", async (_event, ctx) => {
applyIndicator(ctx);
});
pi.registerCommand("working-indicator", {
description: "Set the streaming working indicator: dot, pulse, none, spinner, or reset.",
handler: async (args, ctx) => {
const nextMode = args.trim().toLowerCase();
if (!nextMode) {
ctx.ui.notify(`Working indicator: ${describeMode(mode)}`, "info");
return;
}
if (
nextMode !== "dot" &&
nextMode !== "none" &&
nextMode !== "pulse" &&
nextMode !== "spinner" &&
nextMode !== "reset"
) {
ctx.ui.notify("Usage: /working-indicator [dot|pulse|none|spinner|reset]", "error");
return;
}
mode = nextMode === "reset" ? "default" : nextMode;
applyIndicator(ctx);
ctx.ui.notify(`Working indicator set to: ${describeMode(mode)}`, "info");
},
});
}