@lokiyou/pi-nano-footer 0.15.2 → 0.15.4
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/index.ts +49 -15
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ const icons = {
|
|
|
14
14
|
model: "\uec19", // nf-md-chip
|
|
15
15
|
mcp: "\u{f048d}", // nf-md-server-network
|
|
16
16
|
folder: "\uf115", // nf-fa-folder_open
|
|
17
|
+
thinking: "\ueab4", // nf-mdi-lightbulb
|
|
17
18
|
context: "\ue70f", // nf-dev-database
|
|
18
19
|
cache: "\uf1c0", // nf-fa-database
|
|
19
20
|
input: "\uf090", // nf-fa-sign_in
|
|
@@ -66,10 +67,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
66
67
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
67
68
|
requestRender = () => tui.requestRender();
|
|
68
69
|
const unsubBranch = footerData.onBranchChange(() => tui.requestRender());
|
|
70
|
+
let lastStatusSnapshot = snapshotStatuses(footerData);
|
|
71
|
+
const statusPoll = setInterval(() => {
|
|
72
|
+
const nextStatusSnapshot = snapshotStatuses(footerData);
|
|
73
|
+
if (nextStatusSnapshot !== lastStatusSnapshot) {
|
|
74
|
+
lastStatusSnapshot = nextStatusSnapshot;
|
|
75
|
+
tui.requestRender();
|
|
76
|
+
}
|
|
77
|
+
}, 500);
|
|
69
78
|
const clock = setInterval(() => tui.requestRender(), 30_000);
|
|
70
79
|
|
|
71
80
|
return {
|
|
72
|
-
dispose() {
|
|
81
|
+
dispose() {
|
|
82
|
+
unsubBranch();
|
|
83
|
+
clearInterval(statusPoll);
|
|
84
|
+
clearInterval(clock);
|
|
85
|
+
},
|
|
73
86
|
invalidate() {},
|
|
74
87
|
render(width: number): string[] {
|
|
75
88
|
if (width <= 0) return [];
|
|
@@ -124,10 +137,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
124
137
|
});
|
|
125
138
|
}
|
|
126
139
|
|
|
140
|
+
function snapshotStatuses(footerData: any): string {
|
|
141
|
+
const statuses = footerData?.getExtensionStatuses?.();
|
|
142
|
+
if (!statuses || typeof statuses.entries !== "function") return "";
|
|
143
|
+
|
|
144
|
+
return Array.from(statuses.entries())
|
|
145
|
+
.sort(([a], [b]) => String(a).localeCompare(String(b)))
|
|
146
|
+
.map(([key, value]) => `${String(key)}:${stripAnsi(String(value))}`)
|
|
147
|
+
.join("|");
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
128
151
|
// Helper 函数
|
|
129
152
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
130
153
|
|
|
154
|
+
function stripAnsi(text: string): string {
|
|
155
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
156
|
+
}
|
|
157
|
+
|
|
131
158
|
/** 16 进制色值 → ANSI 24-bit true color 前景色转义序列 */
|
|
132
159
|
function ansi(hex: string, text: string): string {
|
|
133
160
|
const h = hex.replace("#", "");
|
|
@@ -137,25 +164,24 @@ function ansi(hex: string, text: string): string {
|
|
|
137
164
|
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
|
|
138
165
|
}
|
|
139
166
|
|
|
167
|
+
function paint(hex: string, text: string, bold = false): string {
|
|
168
|
+
return bold ? `\x1b[1m${ansi(hex, text)}` : ansi(hex, text);
|
|
169
|
+
}
|
|
170
|
+
|
|
140
171
|
/** 渲染思考等级 */
|
|
141
172
|
function renderThinkingN(level: string): string {
|
|
142
|
-
const label = level === "minimal" ? "min"
|
|
143
|
-
: level === "medium" ? "med"
|
|
144
|
-
: level;
|
|
145
|
-
const text = `think:${label}`;
|
|
146
|
-
|
|
147
173
|
switch (level) {
|
|
148
174
|
case "high":
|
|
149
175
|
case "xhigh":
|
|
150
|
-
return
|
|
151
|
-
case "minimal":
|
|
152
|
-
return ansi(C.thinkingMinimal, text);
|
|
153
|
-
case "low":
|
|
154
|
-
return ansi(C.thinkingLow, text);
|
|
176
|
+
return paint("#fff6b0", icons.thinking, true);
|
|
155
177
|
case "medium":
|
|
156
|
-
return
|
|
178
|
+
return paint(C.thinkingMedium, icons.thinking, true);
|
|
179
|
+
case "low":
|
|
180
|
+
return paint(C.thinkingLow, icons.thinking);
|
|
181
|
+
case "minimal":
|
|
182
|
+
return paint(C.thinkingMinimal, icons.thinking);
|
|
157
183
|
default:
|
|
158
|
-
return
|
|
184
|
+
return paint(C.thinking, icons.thinking);
|
|
159
185
|
}
|
|
160
186
|
}
|
|
161
187
|
|
|
@@ -168,7 +194,7 @@ function renderContextN(ctx: any): string {
|
|
|
168
194
|
: pct >= 70 ? C.contextWarn
|
|
169
195
|
: C.context;
|
|
170
196
|
const maxStr = ctx.model?.contextWindow
|
|
171
|
-
? `/${(ctx.model.contextWindow
|
|
197
|
+
? `/${formatContextWindow(ctx.model.contextWindow)}`
|
|
172
198
|
: "";
|
|
173
199
|
return ansi(color, `${icons.context} ${pct?.toFixed(1) ?? "?"}%${maxStr}`);
|
|
174
200
|
}
|
|
@@ -183,7 +209,7 @@ function renderMcpN(footerData: any): string | null {
|
|
|
183
209
|
: Array.from(statuses.values()).find((value: unknown) => typeof value === "string" && value.includes("MCP:"));
|
|
184
210
|
if (typeof raw !== "string") return null;
|
|
185
211
|
|
|
186
|
-
const status = raw
|
|
212
|
+
const status = stripAnsi(raw).replace(/\s+/g, " ").trim();
|
|
187
213
|
|
|
188
214
|
const ratio = /MCP:\s*(\d+)\s*\/\s*(\d+)\s+servers?/i.exec(status);
|
|
189
215
|
if (ratio) {
|
|
@@ -230,6 +256,14 @@ function fmt(n: number): string {
|
|
|
230
256
|
return `${(n / 1_000_000).toFixed(1)}m`;
|
|
231
257
|
}
|
|
232
258
|
|
|
259
|
+
function formatContextWindow(n: number): string {
|
|
260
|
+
if (n < 1000) return `${n}`;
|
|
261
|
+
if (n < 1_000_000) return `${Math.floor(n / 1000)}K`;
|
|
262
|
+
const scaled = n / 1_000_000;
|
|
263
|
+
const text = Number.isInteger(scaled) ? `${scaled.toFixed(0)}` : scaled.toFixed(scaled >= 10 ? 0 : 1).replace(/\.0$/, "");
|
|
264
|
+
return `${text}M`;
|
|
265
|
+
}
|
|
266
|
+
|
|
233
267
|
/** 缩短模型名 */
|
|
234
268
|
function shorten(m: string): string {
|
|
235
269
|
return m
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokiyou/pi-nano-footer",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.4",
|
|
4
4
|
"description": "Lightweight powerline-style footer for Pi Coding Agent that shows model, thinking level, directory, MCP status, context usage, tokens, and cost while keeping the built-in Working indicator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|