pi-open-tui 0.2.14 → 0.2.15
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/extensions/open-tui/settings-command.ts +72 -14
- package/extensions/open-tui/state.ts +11 -8
- package/extensions/open-tui/telemetry.ts +295 -298
- package/extensions/open-tui/utils.ts +365 -361
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ Key options:
|
|
|
93
93
|
| --- | --- | --- |
|
|
94
94
|
| `settingsLanguage` | `en`, `zh` | Changes the `/open-tui` interface language |
|
|
95
95
|
| `cursorStyle` | `block`, `bar`, `underline` | `bar` and `underline` require terminal cursor-shape support |
|
|
96
|
-
| `fullscreen.wheelScrollLines` | `1`-`10` | Lines scrolled per mouse-wheel notch in fullscreen mode; defaults to `4`. `/open-tui
|
|
96
|
+
| `fullscreen.wheelScrollLines` | `1`-`10` | Lines scrolled per mouse-wheel notch in fullscreen mode; defaults to `4`. In `/open-tui`, press Enter on this item and type a number (values are clamped to `1`-`10`) |
|
|
97
97
|
| `icons.mode` | `auto`, `nerd`, `ascii` | Controls footer and telemetry icons |
|
|
98
98
|
| `footerSegments` | Boolean flags | Shows or hides individual footer data |
|
|
99
99
|
| `telemetry` | Boolean flags | Enables telemetry and its individual measurements |
|
package/README.zh-CN.md
CHANGED
|
@@ -93,7 +93,7 @@ pi -e npm:pi-open-tui
|
|
|
93
93
|
| --- | --- | --- |
|
|
94
94
|
| `settingsLanguage` | `en`、`zh` | 切换 `/open-tui` 设置界面的语言 |
|
|
95
95
|
| `cursorStyle` | `block`、`bar`、`underline` | `bar` 和 `underline` 需要终端支持光标形状转义序列 |
|
|
96
|
-
| `fullscreen.wheelScrollLines` | `1`-`10` | 全屏模式下滚轮每格滚动的行数,默认值为 `4`;`/open-tui`
|
|
96
|
+
| `fullscreen.wheelScrollLines` | `1`-`10` | 全屏模式下滚轮每格滚动的行数,默认值为 `4`;`/open-tui` 中在该项上按 Enter 后直接输入数字(超出范围会自动钳制到 `1`-`10`) |
|
|
97
97
|
| `icons.mode` | `auto`、`nerd`、`ascii` | 控制底栏和遥测通知使用的图标 |
|
|
98
98
|
| `footerSegments` | 布尔开关 | 分别控制底栏中的各项数据 |
|
|
99
99
|
| `telemetry` | 布尔开关 | 控制遥测总开关和各项指标 |
|
|
@@ -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,17 @@ 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
|
-
totals.
|
|
50
|
-
totals.
|
|
51
|
-
|
|
46
|
+
const input = finiteOrZero(u.input);
|
|
47
|
+
const cacheRead = finiteOrZero(u.cacheRead);
|
|
48
|
+
const cacheWrite = finiteOrZero(u.cacheWrite);
|
|
49
|
+
totals.input += input;
|
|
50
|
+
totals.output += finiteOrZero(u.output);
|
|
51
|
+
totals.cacheRead += cacheRead;
|
|
52
|
+
totals.cacheWrite += cacheWrite;
|
|
53
|
+
totals.cost += finiteOrZero(u.cost?.total);
|
|
54
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
52
55
|
if (promptTokens > 0) {
|
|
53
|
-
totals.latestCacheHitRate = (
|
|
56
|
+
totals.latestCacheHitRate = (cacheRead / promptTokens) * 100;
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
}
|