pi-open-tui 0.2.14 → 0.2.16
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/assets/preview_dashboard_1.png +0 -0
- package/extensions/open-tui/footer.ts +329 -328
- package/extensions/open-tui/settings-command.ts +72 -14
- package/extensions/open-tui/state.ts +13 -8
- package/extensions/open-tui/telemetry.ts +303 -298
- package/extensions/open-tui/utils.ts +372 -361
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
|
+
Input,
|
|
4
5
|
Key,
|
|
5
6
|
matchesKey,
|
|
6
7
|
SelectList,
|
|
@@ -9,8 +10,10 @@ import {
|
|
|
9
10
|
Text,
|
|
10
11
|
} from "@earendil-works/pi-tui";
|
|
11
12
|
import type { CursorStyle, IconMode, OpenTuiConfig, SettingsLanguage } from "./config.ts";
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
15
|
+
normalizeFullscreenWheelScrollLines,
|
|
16
|
+
} from "./fullscreen-scroll.ts";
|
|
14
17
|
|
|
15
18
|
interface SettingItem {
|
|
16
19
|
id: string;
|
|
@@ -26,7 +29,7 @@ const COPY = {
|
|
|
26
29
|
en: {
|
|
27
30
|
title: "Open TUI Settings",
|
|
28
31
|
tabs: { features: "General", icons: "Appearance", segments: "Footer", telemetry: "Telemetry" },
|
|
29
|
-
hint: "Tab/Shift+Tab/←/→: tabs · ↑/↓: move · Enter/Space: change · Esc/q: close",
|
|
32
|
+
hint: "Tab/Shift+Tab/←/→: tabs · ↑/↓: move · Enter/Space: change · Enter on wheel speed: type 1-10 · Esc/q: close",
|
|
30
33
|
labels: {
|
|
31
34
|
enabled: "Enabled",
|
|
32
35
|
language: "Language",
|
|
@@ -53,6 +56,7 @@ const COPY = {
|
|
|
53
56
|
off: "Off",
|
|
54
57
|
languages: { en: "English", zh: "简体中文" },
|
|
55
58
|
wheelLines: (count: number) => `${count} ${count === 1 ? "line" : "lines"} / notch`,
|
|
59
|
+
wheelPrompt: (count: number) => `Wheel scroll lines per notch, 1-10 (current: ${count}). Enter: apply · Esc: cancel`,
|
|
56
60
|
cursorStyles: { block: "Block", bar: "Bar", underline: "Underline" },
|
|
57
61
|
icons: { auto: "Auto", nerd: "Nerd", ascii: "ASCII" },
|
|
58
62
|
},
|
|
@@ -60,7 +64,7 @@ const COPY = {
|
|
|
60
64
|
zh: {
|
|
61
65
|
title: "Open TUI 设置",
|
|
62
66
|
tabs: { features: "常规", icons: "外观", segments: "Footer", telemetry: "遥测" },
|
|
63
|
-
hint: "Tab/Shift+Tab/←/→:切页 · ↑/↓:移动 · Enter/Space:更改 · Esc/q:关闭",
|
|
67
|
+
hint: "Tab/Shift+Tab/←/→:切页 · ↑/↓:移动 · Enter/Space:更改 · 滚轮速度项 Enter 输入 1-10 · Esc/q:关闭",
|
|
64
68
|
labels: {
|
|
65
69
|
enabled: "启用",
|
|
66
70
|
language: "语言",
|
|
@@ -87,6 +91,7 @@ const COPY = {
|
|
|
87
91
|
off: "关闭",
|
|
88
92
|
languages: { en: "English", zh: "简体中文" },
|
|
89
93
|
wheelLines: (count: number) => `每格 ${count} 行`,
|
|
94
|
+
wheelPrompt: (count: number) => `滚轮每格滚动行数(当前 ${count},范围 1-10),输入后 Enter 应用 · Esc 取消`,
|
|
90
95
|
cursorStyles: { block: "块", bar: "竖线", underline: "下划线" },
|
|
91
96
|
icons: { auto: "自动", nerd: "Nerd", ascii: "ASCII" },
|
|
92
97
|
},
|
|
@@ -127,10 +132,17 @@ function cycleCursorStyle(config: OpenTuiConfig): OpenTuiConfig {
|
|
|
127
132
|
return { ...config, cursorStyle: next };
|
|
128
133
|
}
|
|
129
134
|
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
135
|
+
function setWheelScrollLines(config: OpenTuiConfig, raw: string): OpenTuiConfig | undefined {
|
|
136
|
+
if (!/^\d+$/.test(raw)) return undefined;
|
|
137
|
+
const parsed = Number(raw);
|
|
138
|
+
const bounded = Number.isFinite(parsed) ? parsed : Number.MAX_SAFE_INTEGER;
|
|
139
|
+
return {
|
|
140
|
+
...config,
|
|
141
|
+
fullscreen: {
|
|
142
|
+
...config.fullscreen,
|
|
143
|
+
wheelScrollLines: normalizeFullscreenWheelScrollLines(bounded, DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
function toggleTelemetry(config: OpenTuiConfig, key: keyof OpenTuiConfig["telemetry"]): OpenTuiConfig {
|
|
@@ -208,7 +220,6 @@ function handleSettingChange(
|
|
|
208
220
|
if (tab === "features") {
|
|
209
221
|
if (itemId === "enabled") return toggleEnabled(config);
|
|
210
222
|
if (itemId === "settingsLanguage") return toggleLanguage(config);
|
|
211
|
-
if (itemId === "wheelScrollLines") return cycleWheelScrollLines(config);
|
|
212
223
|
}
|
|
213
224
|
if (tab === "icons") {
|
|
214
225
|
if (itemId === "mode") return cycleIconMode(config);
|
|
@@ -241,6 +252,7 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
241
252
|
private cachedWidth: number | undefined;
|
|
242
253
|
private cachedLines: string[] | undefined;
|
|
243
254
|
private compact = false;
|
|
255
|
+
private wheelInput: Input | undefined;
|
|
244
256
|
|
|
245
257
|
constructor(
|
|
246
258
|
theme: Theme,
|
|
@@ -265,11 +277,35 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
265
277
|
|
|
266
278
|
private applySetting(itemId: string): void {
|
|
267
279
|
this.selectedItemByTab[this.tab] = itemId;
|
|
280
|
+
if (this.tab === "features" && itemId === "wheelScrollLines") {
|
|
281
|
+
this.openWheelInput();
|
|
282
|
+
this.invalidate();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
268
285
|
this.config = handleSettingChange(this.tab, itemId, this.config);
|
|
269
286
|
this.onChange(this.config);
|
|
270
287
|
this.rebuild(itemId);
|
|
271
288
|
}
|
|
272
289
|
|
|
290
|
+
private openWheelInput(): void {
|
|
291
|
+
const input = new Input();
|
|
292
|
+
input.onSubmit = (value) => {
|
|
293
|
+
const next = setWheelScrollLines(this.config, value);
|
|
294
|
+
this.wheelInput = undefined;
|
|
295
|
+
if (next) {
|
|
296
|
+
this.config = next;
|
|
297
|
+
this.onChange(this.config);
|
|
298
|
+
}
|
|
299
|
+
this.rebuild("wheelScrollLines");
|
|
300
|
+
};
|
|
301
|
+
input.onEscape = () => {
|
|
302
|
+
this.wheelInput = undefined;
|
|
303
|
+
this.rebuild("wheelScrollLines");
|
|
304
|
+
};
|
|
305
|
+
this.wheelInput = input;
|
|
306
|
+
this.rebuild("wheelScrollLines");
|
|
307
|
+
}
|
|
308
|
+
|
|
273
309
|
private switchTab(offset: number): void {
|
|
274
310
|
const idx = TABS.indexOf(this.tab);
|
|
275
311
|
this.tab = TABS[(idx + offset + TABS.length) % TABS.length]!;
|
|
@@ -289,11 +325,17 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
289
325
|
this.container.addChild(new Text(tabBar, 1, 0));
|
|
290
326
|
this.container.addChild(new Text(this.theme.fg("dim", copy.hint), 1, 0));
|
|
291
327
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
328
|
+
const editingWheel = this.tab === "features" && this.wheelInput !== undefined;
|
|
329
|
+
const items = buildItems(this.tab, this.config).map((item) => {
|
|
330
|
+
const editing = editingWheel && item.id === "wheelScrollLines";
|
|
331
|
+
return {
|
|
332
|
+
value: item.id,
|
|
333
|
+
label: editing
|
|
334
|
+
? (this.compact ? `${item.label}:` : item.label)
|
|
335
|
+
: (this.compact ? `${item.label}: ${item.currentValue}` : item.label),
|
|
336
|
+
description: editing || this.compact ? undefined : item.currentValue,
|
|
337
|
+
} as SelectItem;
|
|
338
|
+
});
|
|
297
339
|
this.selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
298
340
|
selectedPrefix: (t) => this.theme.fg("accent", t),
|
|
299
341
|
selectedText: (t) => this.theme.fg("accent", t),
|
|
@@ -316,12 +358,28 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
316
358
|
this.onClose();
|
|
317
359
|
};
|
|
318
360
|
this.container.addChild(this.selectList);
|
|
361
|
+
if (editingWheel) {
|
|
362
|
+
this.wheelInput!.focused = true;
|
|
363
|
+
const wheelInputGroup = new Box(4, 0);
|
|
364
|
+
wheelInputGroup.addChild(new Text(
|
|
365
|
+
this.theme.fg("muted", copy.values.wheelPrompt(this.config.fullscreen.wheelScrollLines)),
|
|
366
|
+
0,
|
|
367
|
+
0,
|
|
368
|
+
));
|
|
369
|
+
wheelInputGroup.addChild(this.wheelInput!);
|
|
370
|
+
this.container.addChild(wheelInputGroup);
|
|
371
|
+
}
|
|
319
372
|
|
|
320
373
|
this.cachedWidth = undefined;
|
|
321
374
|
this.cachedLines = undefined;
|
|
322
375
|
}
|
|
323
376
|
|
|
324
377
|
handleInput(data: string): void {
|
|
378
|
+
if (this.wheelInput && this.tab === "features") {
|
|
379
|
+
this.wheelInput.handleInput(data);
|
|
380
|
+
this.invalidate();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
325
383
|
if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {
|
|
326
384
|
this.switchTab(1);
|
|
327
385
|
this.invalidate();
|
|
@@ -3,7 +3,7 @@ import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
|
3
3
|
import type { GitStatus } from "./git.ts";
|
|
4
4
|
import { emptyGitStatus } from "./git.ts";
|
|
5
5
|
import type { RuntimeInfo } from "./runtime.ts";
|
|
6
|
-
import { fmtTokens, formatProviderLabel } from "./utils.ts";
|
|
6
|
+
import { finiteOrZero, fmtTokens, formatProviderLabel } from "./utils.ts";
|
|
7
7
|
|
|
8
8
|
export interface FooterState {
|
|
9
9
|
git: GitStatus;
|
|
@@ -43,14 +43,19 @@ export function getUsageTotals(ctx: ExtensionContext): UsageTotals {
|
|
|
43
43
|
const m = entry.message as AssistantMessage;
|
|
44
44
|
const u = m.usage;
|
|
45
45
|
if (!u) continue;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
const input = finiteOrZero(u.input);
|
|
47
|
+
const cacheRead = finiteOrZero(u.cacheRead);
|
|
48
|
+
const cacheWrite = finiteOrZero(u.cacheWrite);
|
|
49
|
+
// input matches /session's "uncached" total: cacheWrite is billed near full
|
|
50
|
+
// price (fresh content), only cacheRead is discounted repeat content.
|
|
51
|
+
totals.input += input + cacheWrite;
|
|
52
|
+
totals.output += finiteOrZero(u.output);
|
|
53
|
+
totals.cacheRead += cacheRead;
|
|
54
|
+
totals.cacheWrite += cacheWrite;
|
|
55
|
+
totals.cost += finiteOrZero(u.cost?.total);
|
|
56
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
52
57
|
if (promptTokens > 0) {
|
|
53
|
-
totals.latestCacheHitRate = (
|
|
58
|
+
totals.latestCacheHitRate = (cacheRead / promptTokens) * 100;
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
}
|