pi-open-tui 0.3.6 → 0.3.7
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 +8 -5
- package/README.zh-CN.md +8 -5
- package/extensions/open-tui/config.ts +4 -0
- package/extensions/open-tui/editor.ts +5 -0
- package/extensions/open-tui/footer.ts +14 -0
- package/extensions/open-tui/icons.ts +9 -26
- package/extensions/open-tui/index.ts +56 -56
- package/extensions/open-tui/settings-command.ts +7 -1
- package/extensions/open-tui/state.ts +2 -1
- package/extensions/open-tui/utils.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,11 +41,11 @@ pi -e npm:pi-open-tui
|
|
|
41
41
|
|
|
42
42
|
Download any patched font from the official [Nerd Fonts downloads page](https://www.nerdfonts.com/font-downloads) or [latest GitHub release](https://github.com/ryanoasis/nerd-fonts/releases/latest). Install it, select that font in your terminal profile, and restart the terminal.
|
|
43
43
|
|
|
44
|
-
The default `auto` mode
|
|
44
|
+
The default `auto` mode checks the terminal environment, not the installed font file. It uses Nerd Font icons in interactive UTF-8 TTYs, including terminals running through a runner or subshell. If icons appear as boxes or incorrect symbols, open `/open-tui` and choose one of these modes under **Appearance**:
|
|
45
45
|
|
|
46
46
|
- `nerd`: force Nerd Font icons after configuring a Nerd Font in the terminal
|
|
47
47
|
- `ascii`: use plain-text icons with no patched font required
|
|
48
|
-
- `auto`: use Nerd Font icons in
|
|
48
|
+
- `auto`: use Nerd Font icons in interactive UTF-8 TTYs; use ASCII for non-interactive output, `TERM=dumb`, or an explicitly non-UTF-8 locale
|
|
49
49
|
|
|
50
50
|
If the font is installed but `auto` still selects ASCII, choose `nerd` explicitly. In VS Code, Windows Terminal, and similar apps, configure the font in the terminal profile rather than only installing it in the operating system.
|
|
51
51
|
|
|
@@ -66,6 +66,7 @@ Run `/open-tui` to open the settings dialog. It provides **General**, **Appearan
|
|
|
66
66
|
},
|
|
67
67
|
"footerSegments": {
|
|
68
68
|
"cwd": true,
|
|
69
|
+
"hostname": false,
|
|
69
70
|
"sessionName": false,
|
|
70
71
|
"gitBranch": true,
|
|
71
72
|
"gitStatus": true,
|
|
@@ -74,7 +75,8 @@ Run `/open-tui` to open the settings dialog. It provides **General**, **Appearan
|
|
|
74
75
|
"context": true,
|
|
75
76
|
"tokens": true,
|
|
76
77
|
"cost": true,
|
|
77
|
-
"extensionStatuses": true
|
|
78
|
+
"extensionStatuses": true,
|
|
79
|
+
"capitalizeProviderName": true
|
|
78
80
|
},
|
|
79
81
|
"telemetry": {
|
|
80
82
|
"enabled": true,
|
|
@@ -100,10 +102,11 @@ Key options:
|
|
|
100
102
|
| `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`) |
|
|
101
103
|
| `icons.mode` | `auto`, `nerd`, `ascii` | Controls footer and telemetry icons |
|
|
102
104
|
| `footerSegments` | Boolean flags | Shows or hides individual footer data |
|
|
105
|
+
| `footerSegments.capitalizeProviderName` | Boolean | Capitalizes the first character of the provider name in the footer; set it to `false` to keep the provider's original casing |
|
|
103
106
|
| `telemetry` | Boolean flags | Enables telemetry and its individual measurements |
|
|
104
107
|
| `thinkingPeek.lines` | `0`, `1`, `2` | Off, one-line, or two-line hidden thinking preview |
|
|
105
108
|
|
|
106
|
-
`sessionName` appears only when the session has a name. `gitCommit` shows the short hash and tag in detached HEAD state. Disabling `extensionStatuses` hides the entire extension status line, including MCP status.
|
|
109
|
+
`sessionName` appears only when the session has a name. `hostname` shows the short host name (first label of the machine's host name, e.g. `mba` from `mba.example.com`) with a server icon. `gitCommit` shows the short hash and tag in detached HEAD state. Disabling `extensionStatuses` hides the entire extension status line, including MCP status.
|
|
107
110
|
|
|
108
111
|
Fullscreen wheel speed uses an isolated compatibility shim for Pi 0.84.2's runtime field because Pi does not yet expose a public setter. On Pi versions without a compatible field, the setting is ignored and Pi's default scrolling remains active.
|
|
109
112
|
|
|
@@ -125,7 +128,7 @@ When Pi's **Hide thinking** setting is enabled, pi-open-tui shows a compact tick
|
|
|
125
128
|
|
|
126
129
|
- while the model is reasoning, the current thinking tail streams by with a spinner (`~ think ⠋ …`);
|
|
127
130
|
- when the answer starts, it settles on a check mark (`~ think ✓`);
|
|
128
|
-
- in 2-line mode, the previous and latest thinking lines share the same text indentation; if the latest line overflows, both rows follow its newest tail as tokens arrive instead of retaining the previous line;
|
|
131
|
+
- in 2-line mode, the previous and latest thinking lines share the same text indentation; if the latest line overflows, both rows follow its newest tail as tokens arrive instead of retaining the previous line;
|
|
129
132
|
- after the task settles, the native `Thinking...` label is restored.
|
|
130
133
|
|
|
131
134
|
```text
|
package/README.zh-CN.md
CHANGED
|
@@ -41,11 +41,11 @@ pi -e npm:pi-open-tui
|
|
|
41
41
|
|
|
42
42
|
可从 [Nerd Fonts 官方下载页](https://www.nerdfonts.com/font-downloads)或 [GitHub 最新版本](https://github.com/ryanoasis/nerd-fonts/releases/latest)下载任意已修补字体。安装后,请在终端配置中选择该字体,并重启终端。
|
|
43
43
|
|
|
44
|
-
默认的 `auto`
|
|
44
|
+
默认的 `auto` 模式检查终端环境,而不是已安装的字体文件。在支持 UTF-8 的交互式 TTY 中,它会使用 Nerd Font 图标,即使 Pi 运行在 runner 或子 shell 中。如果图标显示为方框、乱码或错误符号,请打开 `/open-tui`,在**外观**页选择合适的模式:
|
|
45
45
|
|
|
46
46
|
- `nerd`:终端已配置 Nerd Font 时,强制使用 Nerd Font 图标
|
|
47
47
|
- `ascii`:使用纯文本图标,无需安装修补字体
|
|
48
|
-
- `auto
|
|
48
|
+
- `auto`:在支持 UTF-8 的交互式 TTY 中使用 Nerd Font 图标;非交互式输出、`TERM=dumb` 或明确配置为非 UTF-8 的 locale 使用 ASCII
|
|
49
49
|
|
|
50
50
|
如果已经安装字体,但 `auto` 仍选择 ASCII,请手动切换为 `nerd`。使用 VS Code、Windows Terminal 等应用时,只在操作系统中安装字体还不够,还需要在对应的终端配置中选中该字体。
|
|
51
51
|
|
|
@@ -66,6 +66,7 @@ pi -e npm:pi-open-tui
|
|
|
66
66
|
},
|
|
67
67
|
"footerSegments": {
|
|
68
68
|
"cwd": true,
|
|
69
|
+
"hostname": false,
|
|
69
70
|
"sessionName": false,
|
|
70
71
|
"gitBranch": true,
|
|
71
72
|
"gitStatus": true,
|
|
@@ -74,7 +75,8 @@ pi -e npm:pi-open-tui
|
|
|
74
75
|
"context": true,
|
|
75
76
|
"tokens": true,
|
|
76
77
|
"cost": true,
|
|
77
|
-
"extensionStatuses": true
|
|
78
|
+
"extensionStatuses": true,
|
|
79
|
+
"capitalizeProviderName": true
|
|
78
80
|
},
|
|
79
81
|
"telemetry": {
|
|
80
82
|
"enabled": true,
|
|
@@ -100,10 +102,11 @@ pi -e npm:pi-open-tui
|
|
|
100
102
|
| `fullscreen.wheelScrollLines` | `1`-`10` | 全屏模式下滚轮每格滚动的行数,默认值为 `4`;`/open-tui` 中在该项上按 Enter 后直接输入数字(超出范围会自动钳制到 `1`-`10`) |
|
|
101
103
|
| `icons.mode` | `auto`、`nerd`、`ascii` | 控制底栏和遥测通知使用的图标 |
|
|
102
104
|
| `footerSegments` | 布尔开关 | 分别控制底栏中的各项数据 |
|
|
105
|
+
| `footerSegments.capitalizeProviderName` | 布尔开关 | 将底栏中提供商名称的首字母大写;设为 `false` 时保留原始大小写 |
|
|
103
106
|
| `telemetry` | 布尔开关 | 控制遥测总开关和各项指标 |
|
|
104
107
|
| `thinkingPeek.lines` | `0`、`1`、`2` | 关闭、单行或双行思考预览 |
|
|
105
108
|
|
|
106
|
-
`sessionName` 仅在会话有名称时显示;`gitCommit` 会在 detached HEAD 状态下显示短哈希和标签;关闭 `extensionStatuses` 会隐藏整行扩展状态,其中也包括 MCP 状态。
|
|
109
|
+
`sessionName` 仅在会话有名称时显示;`hostname` 会显示主机名的短名称(主机名的第一个标签,例如从 `mba.example.com` 显示为 `mba`),并使用服务器图标;`gitCommit` 会在 detached HEAD 状态下显示短哈希和标签;关闭 `extensionStatuses` 会隐藏整行扩展状态,其中也包括 MCP 状态。
|
|
107
110
|
|
|
108
111
|
全屏滚轮速度通过隔离的兼容层写入 Pi 0.84.2 的运行时字段,因为 Pi 尚未提供公开 setter。若后续 Pi 版本不再包含兼容字段,该设置会被忽略并继续使用 Pi 的默认滚动行为。
|
|
109
112
|
|
|
@@ -125,7 +128,7 @@ TPS 的计算方式是:将本次运行中服务商报告的全部 Assistant
|
|
|
125
128
|
|
|
126
129
|
- 模型推理时,思考内容的末尾片段会随 spinner 滚动(`~ think ⠋ …`);
|
|
127
130
|
- 开始输出正文时定格为对勾(`~ think ✓`);
|
|
128
|
-
- 双行模式中,上一条和最新一条思考内容使用相同的文字缩进;如果最新一行超出宽度,则两行随新 Token 持续显示它的最新末尾片段,不再保留上一条;
|
|
131
|
+
- 双行模式中,上一条和最新一条思考内容使用相同的文字缩进;如果最新一行超出宽度,则两行随新 Token 持续显示它的最新末尾片段,不再保留上一条;
|
|
129
132
|
- 任务结束后恢复原生 `Thinking...` 标签。
|
|
130
133
|
|
|
131
134
|
```text
|
|
@@ -15,6 +15,7 @@ export type { IconMode } from "./icons.ts";
|
|
|
15
15
|
|
|
16
16
|
export interface FooterSegments {
|
|
17
17
|
cwd: boolean;
|
|
18
|
+
hostname: boolean;
|
|
18
19
|
sessionName: boolean;
|
|
19
20
|
gitBranch: boolean;
|
|
20
21
|
gitStatus: boolean;
|
|
@@ -24,6 +25,7 @@ export interface FooterSegments {
|
|
|
24
25
|
tokens: boolean;
|
|
25
26
|
cost: boolean;
|
|
26
27
|
extensionStatuses: boolean;
|
|
28
|
+
capitalizeProviderName: boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface TelemetryConfig {
|
|
@@ -69,6 +71,7 @@ export const DEFAULT_CONFIG: OpenTuiConfig = {
|
|
|
69
71
|
},
|
|
70
72
|
footerSegments: {
|
|
71
73
|
cwd: true,
|
|
74
|
+
hostname: false,
|
|
72
75
|
sessionName: false,
|
|
73
76
|
gitBranch: true,
|
|
74
77
|
gitStatus: true,
|
|
@@ -78,6 +81,7 @@ export const DEFAULT_CONFIG: OpenTuiConfig = {
|
|
|
78
81
|
tokens: true,
|
|
79
82
|
cost: true,
|
|
80
83
|
extensionStatuses: true,
|
|
84
|
+
capitalizeProviderName: true,
|
|
81
85
|
},
|
|
82
86
|
telemetry: {
|
|
83
87
|
enabled: true,
|
|
@@ -185,6 +185,11 @@ export class OpenTuiEditor extends CustomEditor {
|
|
|
185
185
|
const renderedLines = super.render(width);
|
|
186
186
|
if (this.cursorStyle === "block") return renderedLines;
|
|
187
187
|
|
|
188
|
+
// Pi re-applies settings.showHardwareCursor after session_start (/reload,
|
|
189
|
+
// /new, session switches). Non-block styles have no software cursor left to
|
|
190
|
+
// fall back on, so re-assert the hardware cursor instead of losing it.
|
|
191
|
+
if (!this.tui.getShowHardwareCursor()) configureCursor(this.tui, this.cursorStyle);
|
|
192
|
+
|
|
188
193
|
// A focused overlay suppresses the editor's cursor marker. Preserve its
|
|
189
194
|
// position only for the live settings preview, then clear it on refocus.
|
|
190
195
|
let cursorMarker = this.previewHardwareCursor && !this.focused ? CURSOR_MARKER : "";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext, Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { hostname as osHostname } from "node:os";
|
|
2
3
|
import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
3
4
|
import type { OpenTuiConfig } from "./config.ts";
|
|
4
5
|
import type { IconGlyphs } from "./icons.ts";
|
|
@@ -25,6 +26,10 @@ import {
|
|
|
25
26
|
import type { FooterState, ModelMeta, UsageTotals } from "./state.ts";
|
|
26
27
|
import { getUsageTotals } from "./state.ts";
|
|
27
28
|
|
|
29
|
+
export function shortHostname(hostname: string): string {
|
|
30
|
+
return hostname.split(".")[0] ?? "";
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
function renderBar(theme: Theme, pct: number, barWidth: number, ascii: boolean): string {
|
|
29
34
|
const filled = Math.max(0, Math.min(barWidth, Math.round((pct / 100) * barWidth)));
|
|
30
35
|
const empty = barWidth - filled;
|
|
@@ -246,6 +251,15 @@ export function installFooter(
|
|
|
246
251
|
},
|
|
247
252
|
});
|
|
248
253
|
}
|
|
254
|
+
if (segments.hostname) {
|
|
255
|
+
const shortHost = shortHostname(osHostname());
|
|
256
|
+
if (shortHost) {
|
|
257
|
+
leftParts.push({
|
|
258
|
+
text: `${theme.fg("dim", glyphs.host)} ${theme.fg("accent", shortHost)}`,
|
|
259
|
+
priority: 1,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
249
263
|
if (segments.sessionName) {
|
|
250
264
|
const sessionName = ctx.sessionManager.getSessionName();
|
|
251
265
|
if (sessionName) {
|
|
@@ -2,6 +2,7 @@ export type IconMode = "auto" | "nerd" | "ascii";
|
|
|
2
2
|
|
|
3
3
|
export interface IconGlyphs {
|
|
4
4
|
cwd: string;
|
|
5
|
+
host: string;
|
|
5
6
|
session: string;
|
|
6
7
|
git: string;
|
|
7
8
|
working: string;
|
|
@@ -31,6 +32,7 @@ export interface IconGlyphs {
|
|
|
31
32
|
|
|
32
33
|
const NERD_GLYPHS: IconGlyphs = {
|
|
33
34
|
cwd: "",
|
|
35
|
+
host: "",
|
|
34
36
|
session: "",
|
|
35
37
|
git: "",
|
|
36
38
|
working: "",
|
|
@@ -64,6 +66,7 @@ const NERD_GLYPHS: IconGlyphs = {
|
|
|
64
66
|
// avoid collisions with the git-status set {= S ! A ? r x ^ v}.
|
|
65
67
|
const ASCII_GLYPHS: IconGlyphs = {
|
|
66
68
|
cwd: "@",
|
|
69
|
+
host: "h",
|
|
67
70
|
session: "s",
|
|
68
71
|
git: "*",
|
|
69
72
|
working: "o",
|
|
@@ -91,33 +94,13 @@ const ASCII_GLYPHS: IconGlyphs = {
|
|
|
91
94
|
deleted: "x",
|
|
92
95
|
};
|
|
93
96
|
|
|
94
|
-
const NERD_FONT_TERMINALS = new Set([
|
|
95
|
-
"iTerm.app",
|
|
96
|
-
"Ghostty",
|
|
97
|
-
"WezTerm",
|
|
98
|
-
"kitty",
|
|
99
|
-
"rio",
|
|
100
|
-
"tabby",
|
|
101
|
-
"WindowsTerminal",
|
|
102
|
-
"vscode",
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
97
|
export function detectNerdFont(): boolean {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (process.env.TERM === "xterm-kitty") return true;
|
|
113
|
-
|
|
114
|
-
// Windows Terminal sets WT_SESSION (not TERM_PROGRAM)
|
|
115
|
-
if (process.env.WT_SESSION) return true;
|
|
116
|
-
|
|
117
|
-
// VS Code integrated terminal
|
|
118
|
-
if (process.env.TERM_PROGRAM === "vscode") return true;
|
|
119
|
-
|
|
120
|
-
return false;
|
|
98
|
+
// TTY and locale checks are the only reliable signals available here. The
|
|
99
|
+
// terminal emulator owns font selection, so auto mode is optimistic once
|
|
100
|
+
// output is an interactive UTF-8 TTY.
|
|
101
|
+
if (process.env.TERM === "dumb" || process.stdout.isTTY !== true) return false;
|
|
102
|
+
const locale = [process.env.LC_ALL, process.env.LC_CTYPE, process.env.LANG].find(Boolean);
|
|
103
|
+
return locale === undefined || /utf-?8/i.test(locale);
|
|
121
104
|
}
|
|
122
105
|
|
|
123
106
|
export function resolveIconMode(mode: IconMode): "nerd" | "ascii" {
|
|
@@ -23,10 +23,10 @@ import {
|
|
|
23
23
|
type PeekState,
|
|
24
24
|
} from "./peek.ts";
|
|
25
25
|
|
|
26
|
-
type PendingUiChange = "install" | "uninstall";
|
|
27
|
-
const HIDDEN_THINKING_HORIZONTAL_PADDING = 2;
|
|
28
|
-
// Pi's fullscreen transcript can reserve one more column for its scrollbar.
|
|
29
|
-
const HIDDEN_THINKING_SCROLLBAR_RESERVE = 1;
|
|
26
|
+
type PendingUiChange = "install" | "uninstall";
|
|
27
|
+
const HIDDEN_THINKING_HORIZONTAL_PADDING = 2;
|
|
28
|
+
// Pi's fullscreen transcript can reserve one more column for its scrollbar.
|
|
29
|
+
const HIDDEN_THINKING_SCROLLBAR_RESERVE = 1;
|
|
30
30
|
|
|
31
31
|
export function getPendingUiChange(enabled: boolean, active: boolean): PendingUiChange | undefined {
|
|
32
32
|
if (enabled === active) return undefined;
|
|
@@ -57,10 +57,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
57
57
|
let editor: ReturnType<typeof installEditor> | undefined;
|
|
58
58
|
let pendingUiChange: PendingUiChange | undefined;
|
|
59
59
|
|
|
60
|
-
// thinking-peek state (rendered by Pi inside its hidden-thinking block)
|
|
61
|
-
const peek: PeekState = createPeekState();
|
|
62
|
-
let peekFrame = 0;
|
|
63
|
-
let peekSettleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
60
|
+
// thinking-peek state (rendered by Pi inside its hidden-thinking block)
|
|
61
|
+
const peek: PeekState = createPeekState();
|
|
62
|
+
let peekFrame = 0;
|
|
63
|
+
let peekSettleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
64
64
|
let peekTaskEpoch = 0; // bumped at every agent task boundary
|
|
65
65
|
let peekLabelActive = false;
|
|
66
66
|
|
|
@@ -78,47 +78,47 @@ export default function (pi: ExtensionAPI) {
|
|
|
78
78
|
/** Pi decides whether this label is visible; our toggle only controls updates. */
|
|
79
79
|
const isPeekEnabled = () => sessionLifecycle.isCurrent() && config.enabled && config.thinkingPeek.lines > 0 && active;
|
|
80
80
|
|
|
81
|
-
/** Keep each native hidden-thinking label row within Pi's narrowest transcript width. */
|
|
82
|
-
const hiddenThinkingLabelWidth = (): number => {
|
|
83
|
-
if (!editor) throw new Error("Open TUI editor is not installed");
|
|
84
|
-
return Math.max(
|
|
85
|
-
1,
|
|
86
|
-
editor.getViewportWidth() - HIDDEN_THINKING_HORIZONTAL_PADDING - HIDDEN_THINKING_SCROLLBAR_RESERVE,
|
|
87
|
-
);
|
|
88
|
-
};
|
|
81
|
+
/** Keep each native hidden-thinking label row within Pi's narrowest transcript width. */
|
|
82
|
+
const hiddenThinkingLabelWidth = (): number => {
|
|
83
|
+
if (!editor) throw new Error("Open TUI editor is not installed");
|
|
84
|
+
return Math.max(
|
|
85
|
+
1,
|
|
86
|
+
editor.getViewportWidth() - HIDDEN_THINKING_HORIZONTAL_PADDING - HIDDEN_THINKING_SCROLLBAR_RESERVE,
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
89
|
|
|
90
90
|
const setPeekLabel = (ctx?: ExtensionContext): void => {
|
|
91
91
|
if (!isPeekEnabled() || peek.phase === "idle") return;
|
|
92
92
|
const target = ctx ?? lastCtx;
|
|
93
93
|
if (!target || !isTuiContext(target)) return;
|
|
94
|
-
if (!editor) throw new Error("Open TUI editor is not installed");
|
|
95
|
-
editor.setLatestHiddenThinkingLabel(
|
|
96
|
-
buildPeekLabel(
|
|
97
|
-
peek,
|
|
98
|
-
peekFrame,
|
|
99
|
-
resolveGlyphs(config.icons.mode),
|
|
100
|
-
hiddenThinkingLabelWidth(),
|
|
101
|
-
config.thinkingPeek.lines,
|
|
102
|
-
),
|
|
103
|
-
);
|
|
94
|
+
if (!editor) throw new Error("Open TUI editor is not installed");
|
|
95
|
+
editor.setLatestHiddenThinkingLabel(
|
|
96
|
+
buildPeekLabel(
|
|
97
|
+
peek,
|
|
98
|
+
peekFrame,
|
|
99
|
+
resolveGlyphs(config.icons.mode),
|
|
100
|
+
hiddenThinkingLabelWidth(),
|
|
101
|
+
config.thinkingPeek.lines,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
104
|
peekLabelActive = true;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
const clearPeekLabel = (ctx?: ExtensionContext): void => {
|
|
108
|
-
if (!peekLabelActive) return;
|
|
109
|
-
const target = ctx ?? lastCtx;
|
|
110
|
-
if (!target || !isTuiContext(target)) return;
|
|
111
|
-
// The global reset is safe here: every completed message returns to Pi's native label.
|
|
112
|
-
target.ui.setHiddenThinkingLabel();
|
|
108
|
+
if (!peekLabelActive) return;
|
|
109
|
+
const target = ctx ?? lastCtx;
|
|
110
|
+
if (!target || !isTuiContext(target)) return;
|
|
111
|
+
// The global reset is safe here: every completed message returns to Pi's native label.
|
|
112
|
+
target.ui.setHiddenThinkingLabel();
|
|
113
113
|
peekLabelActive = false;
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
/** Reset peek state to idle and cancel any scheduled cleanup. */
|
|
117
|
-
const resetPeek = () => {
|
|
118
|
-
peek.phase = "idle";
|
|
119
|
-
peek.tail = "";
|
|
120
|
-
editor?.resetHiddenThinkingLabelTarget();
|
|
121
|
-
if (peekSettleTimer) {
|
|
116
|
+
/** Reset peek state to idle and cancel any scheduled cleanup. */
|
|
117
|
+
const resetPeek = () => {
|
|
118
|
+
peek.phase = "idle";
|
|
119
|
+
peek.tail = "";
|
|
120
|
+
editor?.resetHiddenThinkingLabelTarget();
|
|
121
|
+
if (peekSettleTimer) {
|
|
122
122
|
clearTimeout(peekSettleTimer);
|
|
123
123
|
peekSettleTimer = undefined;
|
|
124
124
|
}
|
|
@@ -135,7 +135,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
135
135
|
ctx,
|
|
136
136
|
() => state,
|
|
137
137
|
() => config,
|
|
138
|
-
() => getModelMeta(ctx, getThinkingLevel),
|
|
138
|
+
() => getModelMeta(ctx, getThinkingLevel, config.footerSegments.capitalizeProviderName),
|
|
139
139
|
{
|
|
140
140
|
setRequestRender: (fn) => {
|
|
141
141
|
requestFooterRender = fn ?? undefined;
|
|
@@ -292,22 +292,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
292
292
|
pi.on("message_update", (event, ctx) => {
|
|
293
293
|
turnTelemetry.handle(event);
|
|
294
294
|
if (!sessionLifecycle.isCurrent() || isAgentIdle(ctx)) return;
|
|
295
|
-
if (!isPeekEnabled()) return;
|
|
296
|
-
const message = event.message;
|
|
297
|
-
if (!message || message.role !== "assistant") return;
|
|
298
|
-
if (peek.phase === "done") return;
|
|
299
|
-
const parts = collectPeekParts(message.content);
|
|
300
|
-
const next = reducePeek(peek, parts);
|
|
295
|
+
if (!isPeekEnabled()) return;
|
|
296
|
+
const message = event.message;
|
|
297
|
+
if (!message || message.role !== "assistant") return;
|
|
298
|
+
if (peek.phase === "done") return;
|
|
299
|
+
const parts = collectPeekParts(message.content);
|
|
300
|
+
const next = reducePeek(peek, parts);
|
|
301
301
|
const changed = next.phase !== peek.phase || next.tail !== peek.tail;
|
|
302
302
|
peek.phase = next.phase;
|
|
303
|
-
peek.tail = next.tail;
|
|
304
|
-
if (!changed) return;
|
|
305
|
-
if (peek.phase === "thinking") {
|
|
306
|
-
peekFrame++;
|
|
307
|
-
setPeekLabel(ctx);
|
|
308
|
-
} else if (peek.phase === "done") {
|
|
309
|
-
setPeekLabel(ctx);
|
|
310
|
-
}
|
|
303
|
+
peek.tail = next.tail;
|
|
304
|
+
if (!changed) return;
|
|
305
|
+
if (peek.phase === "thinking") {
|
|
306
|
+
peekFrame++;
|
|
307
|
+
setPeekLabel(ctx);
|
|
308
|
+
} else if (peek.phase === "done") {
|
|
309
|
+
setPeekLabel(ctx);
|
|
310
|
+
}
|
|
311
311
|
});
|
|
312
312
|
|
|
313
313
|
pi.on("tool_execution_start", (event) => {
|
|
@@ -346,10 +346,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
346
346
|
pi.on("message_end", (event, ctx) => {
|
|
347
347
|
turnTelemetry.handle(event);
|
|
348
348
|
if (!sessionLifecycle.isCurrent()) return;
|
|
349
|
-
if (event.message?.role === "assistant" && peek.phase === "thinking") {
|
|
350
|
-
// Sub-message finished (answer text or a tool call): freeze the peek line.
|
|
351
|
-
peek.phase = "done";
|
|
352
|
-
setPeekLabel(ctx);
|
|
349
|
+
if (event.message?.role === "assistant" && peek.phase === "thinking") {
|
|
350
|
+
// Sub-message finished (answer text or a tool call): freeze the peek line.
|
|
351
|
+
peek.phase = "done";
|
|
352
|
+
setPeekLabel(ctx);
|
|
353
353
|
}
|
|
354
354
|
invalidateUsageCache();
|
|
355
355
|
refreshInteractiveState(ctx);
|
|
@@ -39,6 +39,7 @@ const COPY = {
|
|
|
39
39
|
cursorStyle: "Cursor style",
|
|
40
40
|
iconMode: "Icon mode",
|
|
41
41
|
cwd: "CWD",
|
|
42
|
+
hostname: "Hostname",
|
|
42
43
|
sessionName: "Session name",
|
|
43
44
|
gitBranch: "Git branch",
|
|
44
45
|
gitStatus: "Git status",
|
|
@@ -48,6 +49,7 @@ const COPY = {
|
|
|
48
49
|
tokens: "Tokens",
|
|
49
50
|
cost: "Cost",
|
|
50
51
|
extensionStatuses: "Extension status line",
|
|
52
|
+
capitalizeProviderName: "Capitalize provider name",
|
|
51
53
|
totalDuration: "Total duration",
|
|
52
54
|
tokenCounts: "Token counts",
|
|
53
55
|
stallDetails: "Stall details",
|
|
@@ -76,6 +78,7 @@ const COPY = {
|
|
|
76
78
|
cursorStyle: "光标样式",
|
|
77
79
|
iconMode: "图标模式",
|
|
78
80
|
cwd: "当前目录",
|
|
81
|
+
hostname: "主机名",
|
|
79
82
|
sessionName: "会话名",
|
|
80
83
|
gitBranch: "Git 分支",
|
|
81
84
|
gitStatus: "Git 状态",
|
|
@@ -85,6 +88,7 @@ const COPY = {
|
|
|
85
88
|
tokens: "Token",
|
|
86
89
|
cost: "费用",
|
|
87
90
|
extensionStatuses: "扩展状态行",
|
|
91
|
+
capitalizeProviderName: "提供商名称首字母大写",
|
|
88
92
|
totalDuration: "总耗时",
|
|
89
93
|
tokenCounts: "Token 数量",
|
|
90
94
|
stallDetails: "停顿详情",
|
|
@@ -196,6 +200,7 @@ function buildSegmentsItems(config: OpenTuiConfig, copy: SettingsCopy): SettingI
|
|
|
196
200
|
const flag = (value: boolean) => value ? copy.values.on : copy.values.off;
|
|
197
201
|
return [
|
|
198
202
|
{ id: "cwd", label: copy.labels.cwd, currentValue: flag(segs.cwd) },
|
|
203
|
+
{ id: "hostname", label: copy.labels.hostname, currentValue: flag(segs.hostname) },
|
|
199
204
|
{ id: "sessionName", label: copy.labels.sessionName, currentValue: flag(segs.sessionName) },
|
|
200
205
|
{ id: "gitBranch", label: copy.labels.gitBranch, currentValue: flag(segs.gitBranch) },
|
|
201
206
|
{ id: "gitStatus", label: copy.labels.gitStatus, currentValue: flag(segs.gitStatus) },
|
|
@@ -205,6 +210,7 @@ function buildSegmentsItems(config: OpenTuiConfig, copy: SettingsCopy): SettingI
|
|
|
205
210
|
{ id: "tokens", label: copy.labels.tokens, currentValue: flag(segs.tokens) },
|
|
206
211
|
{ id: "cost", label: copy.labels.cost, currentValue: flag(segs.cost) },
|
|
207
212
|
{ id: "extensionStatuses", label: copy.labels.extensionStatuses, currentValue: flag(segs.extensionStatuses) },
|
|
213
|
+
{ id: "capitalizeProviderName", label: copy.labels.capitalizeProviderName, currentValue: flag(segs.capitalizeProviderName) },
|
|
208
214
|
];
|
|
209
215
|
}
|
|
210
216
|
|
|
@@ -374,7 +380,7 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
374
380
|
description: editing || this.compact ? undefined : item.currentValue,
|
|
375
381
|
} as SelectItem;
|
|
376
382
|
});
|
|
377
|
-
this.selectList = new SelectList(items, Math.min(items.length,
|
|
383
|
+
this.selectList = new SelectList(items, Math.min(items.length, 12), {
|
|
378
384
|
selectedPrefix: (t) => this.theme.fg("accent", t),
|
|
379
385
|
selectedText: (t) => this.theme.fg("accent", t),
|
|
380
386
|
description: (t) => this.theme.fg("muted", t),
|
|
@@ -94,8 +94,9 @@ export interface ModelMeta {
|
|
|
94
94
|
export function getModelMeta(
|
|
95
95
|
ctx: ExtensionContext,
|
|
96
96
|
getThinkingLevel: () => string,
|
|
97
|
+
capitalizeProviderName: boolean,
|
|
97
98
|
): ModelMeta {
|
|
98
|
-
const provider = formatProviderLabel(ctx.model?.provider);
|
|
99
|
+
const provider = formatProviderLabel(ctx.model?.provider, capitalizeProviderName);
|
|
99
100
|
const model = ctx.model?.name ?? ctx.model?.id ?? "no-model";
|
|
100
101
|
const reasoning = ctx.model?.reasoning ?? false;
|
|
101
102
|
const effort = reasoning ? getThinkingLevel() : undefined;
|
|
@@ -97,9 +97,9 @@ export function formatModelLabel(model: { provider?: string; id?: string } | nul
|
|
|
97
97
|
return model.provider ? `${model.provider}/${model.id}` : model.id;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export function formatProviderLabel(provider: string | undefined): string {
|
|
100
|
+
export function formatProviderLabel(provider: string | undefined, capitalize: boolean): string {
|
|
101
101
|
if (!provider) return "Unknown";
|
|
102
|
-
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
102
|
+
return capitalize ? provider.charAt(0).toUpperCase() + provider.slice(1) : provider;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export function alignRight(left: string, right: string, width: number, theme: Theme): string {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-open-tui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "A polished TUI for Pi coding agent: animated logo header, Starship-style footer, rounded editor with model metadata, and prompt-box user messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|