pi-zh-cn 0.1.1 → 0.2.1
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 +24 -32
- package/extensions/footer.ts +309 -0
- package/extensions/header.ts +52 -0
- package/extensions/index.ts +31 -804
- package/extensions/localized-tools.ts +265 -0
- package/extensions/tool-renders.ts +184 -0
- package/extensions/trust.ts +144 -0
- package/extensions/ui-copy.ts +53 -0
- package/extensions/{i18n.ts → zh.ts} +50 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,58 +1,50 @@
|
|
|
1
1
|
# pi-zh-cn
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
纯扩展 API 实现,不 patch 源码、不侵入安装;正常情况下可随 pi 升级保持兼容。
|
|
3
|
+
把 [pi](https://github.com/earendil-works/pi) 编码代理的界面变成简体中文。装上重启即可,不想要了随时卸载。
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
原理上它只是一个普通扩展:调用官方扩展 API 替换文案,不打补丁、不改安装目录里的任何文件,所以 pi 正常升级后一般也能直接用。
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
10
|
-
| 启动 header(标题 + 快捷键提示行 + 引导语) | `ctx.ui.setHeader()`,快捷键跟随用户键位配置 |
|
|
11
|
-
| 流式加载提示(默认 "Working...") | `ctx.ui.setWorkingMessage()` → 「正在处理…」 |
|
|
12
|
-
| thinking 折叠块标签 | `ctx.ui.setHiddenThinkingLabel()` → 「思考过程已折叠」 |
|
|
13
|
-
| 底部状态栏 | `ctx.ui.setFooter()` 中文重建:目录/分支/会话名、token/缓存/费用/上下文用量、模型/思考等级、扩展状态行 |
|
|
14
|
-
| read 工具渲染 | 同名重注册,标签「读取」+ 中文状态文案 |
|
|
15
|
-
| bash 工具渲染 | 同名重注册,标签「执行」+「完成 / 退出码 N / 输出过长已截断」 |
|
|
16
|
-
| edit 工具渲染 | 同名重注册,标签「编辑」+「已修改 / +N / -N」 |
|
|
17
|
-
| write 工具渲染 | 同名重注册,标签「写入」+「已写入」 |
|
|
18
|
-
| grep 工具渲染 | 同名重注册,标签「搜索内容」+ 中文搜索结果状态 |
|
|
19
|
-
| find 工具渲染 | 同名重注册,标签「查找文件」+ 中文查找结果状态 |
|
|
20
|
-
| ls 工具渲染 | 同名重注册,标签「列出目录」+ 中文目录状态 |
|
|
21
|
-
| PowerShell 工具渲染 | 同名重注册,标签「执行 PowerShell」+ 中文执行状态 |
|
|
7
|
+
## 会变成中文的部分
|
|
22
8
|
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
- 启动画面:标题、快捷键提示、引导语(快捷键显示跟随你自己的键位配置)
|
|
10
|
+
- 底部状态栏:当前目录 / 分支 / 会话名、token 与缓存用量、费用、上下文占比、模型和思考等级
|
|
11
|
+
- 加载提示:「正在处理…」;thinking 折叠块的标签
|
|
12
|
+
- 八个内置工具的执行结果:读取、执行(bash)、编辑、写入、搜索内容、查找文件、列出目录、PowerShell——状态行都是中文,比如「完成」「退出码 N」「输出过长已截断」
|
|
13
|
+
- 项目信任弹窗:信任/不信任选择,含「信任父文件夹」变体
|
|
25
14
|
|
|
26
|
-
|
|
15
|
+
发给模型的内容不变。工具的 description 保持英文原文,避免影响模型行为。
|
|
27
16
|
|
|
28
|
-
|
|
17
|
+
## 保持原样的部分
|
|
29
18
|
|
|
30
|
-
|
|
19
|
+
- 内置菜单和其他硬编码在 pi 源码里的英文(扩展 API 够不到,除非 fork 上游重编译,不值得)
|
|
20
|
+
- 工具的启用范围:`grep`、`find`、`ls` 或 `powershell` 没开就不会被这个插件偷偷打开
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
# 本地路径安装(路径引用,不复制文件)
|
|
34
|
-
pi install /path/to/pi-zh
|
|
22
|
+
## 使用
|
|
35
23
|
|
|
36
|
-
|
|
24
|
+
```bash
|
|
25
|
+
# 从 npm 安装
|
|
37
26
|
pi install npm:pi-zh-cn
|
|
38
27
|
|
|
39
|
-
#
|
|
28
|
+
# 查看/卸载用这里显示的 source 名
|
|
40
29
|
pi list
|
|
41
30
|
```
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
觉得中文状态栏不如原生好读,可以临时切回英文:
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
```text
|
|
35
|
+
/footer
|
|
36
|
+
```
|
|
47
37
|
|
|
48
38
|
## 开发
|
|
49
39
|
|
|
50
40
|
```bash
|
|
51
|
-
npm install
|
|
41
|
+
npm install
|
|
52
42
|
./node_modules/.bin/tsc -p . # 类型检查
|
|
53
43
|
|
|
54
44
|
# 本地试跑
|
|
55
45
|
pi --no-session -e ./extensions/index.ts -p "hi"
|
|
56
46
|
```
|
|
57
47
|
|
|
58
|
-
|
|
48
|
+
所有文案都集中在 [`extensions/zh.ts`](extensions/zh.ts),想改词只动这一个文件就够了。
|
|
49
|
+
|
|
50
|
+
改动记录见 [CHANGELOG.md](CHANGELOG.md)。pi 大版本升级后如果出现错位或残留英文,欢迎提 issue。
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中文 footer:逐行复刻内置信息(pwd 行 / 统计行 / 扩展状态行),仅描述文案用中文。
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
7
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
8
|
+
import {
|
|
9
|
+
CONFIG_DIR_NAME,
|
|
10
|
+
getAgentDir,
|
|
11
|
+
type ExtensionContext,
|
|
12
|
+
} from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
14
|
+
import { zh } from "./zh.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 安装中文 footer(仅 TUI 模式,其他模式返回 undefined)。
|
|
18
|
+
*
|
|
19
|
+
* footer 逐行复刻内置信息结构:
|
|
20
|
+
* 1. pwd 行 —— 当前目录(~ 缩写)+ git 分支 + 会话名;
|
|
21
|
+
* 2. 统计行 —— 左侧为输入/输出 token、缓存读写与命中率、费用、上下文
|
|
22
|
+
* 占比(按阈值变色),右侧为 provider • 模型 • 思考等级(放不下时省略);
|
|
23
|
+
* 3. 扩展状态行 —— 各扩展 `setStatus` 的文本。
|
|
24
|
+
* 仅描述文案用中文,数据格式与 built-in 一致。
|
|
25
|
+
*
|
|
26
|
+
* @param ctx 当前会话上下文
|
|
27
|
+
* @returns 切换函数:第一次调用恢复内置 footer,再调用切回中文 footer;
|
|
28
|
+
* 非 TUI 模式下为 undefined
|
|
29
|
+
*/
|
|
30
|
+
export function setupChineseFooter(ctx: ExtensionContext): (() => void) | undefined {
|
|
31
|
+
if (ctx.mode !== "tui") return undefined;
|
|
32
|
+
|
|
33
|
+
let usingZhFooter = true;
|
|
34
|
+
|
|
35
|
+
const applyZhFooter = () => {
|
|
36
|
+
usingZhFooter = true;
|
|
37
|
+
// 自动压缩标记:全局设置为基准,受信任项目的本地设置优先。
|
|
38
|
+
// 一秒缓存避免流式渲染时频繁读文件,同时能跟随 /settings 的运行时修改。
|
|
39
|
+
const settingsPaths = [join(getAgentDir(), "settings.json")];
|
|
40
|
+
if (ctx.isProjectTrusted())
|
|
41
|
+
settingsPaths.push(join(ctx.cwd, CONFIG_DIR_NAME, "settings.json"));
|
|
42
|
+
let autoCompactEnabled = true;
|
|
43
|
+
let autoCompactCheckedAt = 0;
|
|
44
|
+
/**
|
|
45
|
+
* 返回自动压缩标记文案。设置读取带 1 秒缓存:避免流式渲染时每次
|
|
46
|
+
* 重绘都读文件,同时仍能跟随 /settings 的运行时修改。全局设置为基准,
|
|
47
|
+
* 受信任项目的本地设置优先覆盖。
|
|
48
|
+
*/
|
|
49
|
+
const getAutoCompactSuffix = () => {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
if (now - autoCompactCheckedAt >= 1000) {
|
|
52
|
+
autoCompactEnabled = true;
|
|
53
|
+
for (const settingsPath of settingsPaths) {
|
|
54
|
+
try {
|
|
55
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
56
|
+
if (typeof settings?.compaction?.enabled === "boolean") {
|
|
57
|
+
autoCompactEnabled = settings.compaction.enabled;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// 设置文件不存在或内容无效时忽略该层。
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
autoCompactCheckedAt = now;
|
|
64
|
+
}
|
|
65
|
+
return autoCompactEnabled ? zh.footer.autoCompactSuffix : "";
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let unsubscribeBranch: (() => void) | undefined;
|
|
69
|
+
|
|
70
|
+
// footer 渲染器:onBranchChange 触发重绘;dispose 时取消订阅。
|
|
71
|
+
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
72
|
+
unsubscribeBranch?.();
|
|
73
|
+
unsubscribeBranch = footerData.onBranchChange(() =>
|
|
74
|
+
tui.requestRender(),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const home = homedir();
|
|
78
|
+
return {
|
|
79
|
+
dispose() {
|
|
80
|
+
unsubscribeBranch?.();
|
|
81
|
+
unsubscribeBranch = undefined;
|
|
82
|
+
},
|
|
83
|
+
invalidate() {},
|
|
84
|
+
/** 汇总会话 usage 统计并渲染三行中文 footer,见模块级说明。 */
|
|
85
|
+
render(width: number): string[] {
|
|
86
|
+
let input = 0,
|
|
87
|
+
output = 0,
|
|
88
|
+
cacheRead = 0,
|
|
89
|
+
cacheWrite = 0,
|
|
90
|
+
cost = 0;
|
|
91
|
+
const addUsage = (t: {
|
|
92
|
+
input: number;
|
|
93
|
+
output: number;
|
|
94
|
+
cacheRead: number;
|
|
95
|
+
cacheWrite: number;
|
|
96
|
+
cost: { total: number };
|
|
97
|
+
}) => {
|
|
98
|
+
input += t.input;
|
|
99
|
+
output += t.output;
|
|
100
|
+
cacheRead += t.cacheRead;
|
|
101
|
+
cacheWrite += t.cacheWrite;
|
|
102
|
+
cost += t.cost.total;
|
|
103
|
+
};
|
|
104
|
+
let latestRate: number | undefined;
|
|
105
|
+
|
|
106
|
+
for (const e of ctx.sessionManager.getEntries()) {
|
|
107
|
+
if (e.type === "message" && e.message.role === "assistant") {
|
|
108
|
+
const u = (e.message as AssistantMessage).usage;
|
|
109
|
+
addUsage(u);
|
|
110
|
+
const promptTokens = u.input + u.cacheRead + u.cacheWrite;
|
|
111
|
+
latestRate =
|
|
112
|
+
promptTokens > 0
|
|
113
|
+
? (u.cacheRead / promptTokens) * 100
|
|
114
|
+
: undefined;
|
|
115
|
+
} else if (
|
|
116
|
+
e.type === "message" &&
|
|
117
|
+
e.message.role === "toolResult" &&
|
|
118
|
+
e.message.usage
|
|
119
|
+
) {
|
|
120
|
+
addUsage(e.message.usage as any);
|
|
121
|
+
} else if (
|
|
122
|
+
(e.type === "branch_summary" || e.type === "compaction") &&
|
|
123
|
+
(e as any).usage
|
|
124
|
+
) {
|
|
125
|
+
addUsage((e as any).usage);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const fmt = (n: number) =>
|
|
130
|
+
n < 1e3
|
|
131
|
+
? `${n}`
|
|
132
|
+
: n < 1e4
|
|
133
|
+
? `${(n / 1e3).toFixed(1)}k`
|
|
134
|
+
: n < 1e6
|
|
135
|
+
? `${Math.round(n / 1e3)}k`
|
|
136
|
+
: n < 1e7
|
|
137
|
+
? `${(n / 1e6).toFixed(1)}M`
|
|
138
|
+
: `${Math.round(n / 1e6)}M`;
|
|
139
|
+
|
|
140
|
+
// 第 1 行:当前目录:~路径(分支)• 会话名
|
|
141
|
+
let pwd = ctx.sessionManager.getCwd();
|
|
142
|
+
const homeRelative = relative(home, pwd);
|
|
143
|
+
if (homeRelative === "") pwd = "~";
|
|
144
|
+
else if (
|
|
145
|
+
homeRelative &&
|
|
146
|
+
!homeRelative.startsWith("..") &&
|
|
147
|
+
!homeRelative.startsWith("/")
|
|
148
|
+
)
|
|
149
|
+
pwd = `~/${homeRelative}`;
|
|
150
|
+
const branch = footerData.getGitBranch();
|
|
151
|
+
if (branch) pwd += ` ${zh.footer.branchPrefix}(${branch})`;
|
|
152
|
+
const sessionName = ctx.sessionManager.getSessionName();
|
|
153
|
+
if (sessionName) pwd += ` • ${sessionName}`;
|
|
154
|
+
pwd = `${zh.footer.cwdPrefix}${pwd}`;
|
|
155
|
+
|
|
156
|
+
// 第 2 行左:严格按内置顺序:输入、输出、缓存、费用、上下文占比。
|
|
157
|
+
const clauses: string[] = [];
|
|
158
|
+
if (input > 0 || output > 0) {
|
|
159
|
+
const io: string[] = [];
|
|
160
|
+
if (input > 0) io.push(`${zh.footer.inputLabel}↑${fmt(input)}`);
|
|
161
|
+
if (output > 0)
|
|
162
|
+
io.push(`${zh.footer.outputLabel}↓${fmt(output)}`);
|
|
163
|
+
clauses.push(io.join("/"));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const cacheParts: string[] = [];
|
|
167
|
+
if (cacheRead > 0)
|
|
168
|
+
cacheParts.push(`${zh.footer.cacheReadLabel}${fmt(cacheRead)}`);
|
|
169
|
+
if (cacheWrite > 0)
|
|
170
|
+
cacheParts.push(
|
|
171
|
+
`${zh.footer.cacheWriteLabel}${fmt(cacheWrite)}`,
|
|
172
|
+
);
|
|
173
|
+
if (
|
|
174
|
+
(cacheRead > 0 || cacheWrite > 0) &&
|
|
175
|
+
latestRate !== undefined
|
|
176
|
+
) {
|
|
177
|
+
cacheParts.push(
|
|
178
|
+
`${zh.footer.cacheHitLabel}${latestRate.toFixed(1)}%`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (cacheParts.length > 0) clauses.push(cacheParts.join(" "));
|
|
182
|
+
|
|
183
|
+
const provider = ctx.model
|
|
184
|
+
? ctx.modelRegistry.getProvider(ctx.model.provider)
|
|
185
|
+
: undefined;
|
|
186
|
+
const usingSubscription =
|
|
187
|
+
ctx.model?.provider === "kimi-coding" ||
|
|
188
|
+
(!!ctx.model &&
|
|
189
|
+
ctx.modelRegistry.isUsingOAuth(ctx.model) &&
|
|
190
|
+
provider?.auth.oauth?.isSubscription === true);
|
|
191
|
+
if (cost > 0 || usingSubscription) {
|
|
192
|
+
clauses.push(
|
|
193
|
+
`${zh.footer.costLabel}$${cost.toFixed(3)}${usingSubscription ? zh.footer.subSuffix : ""}`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// built-in 顺序的最后一项:上下文占比 / 上下文窗口(自动压缩)。
|
|
198
|
+
const cu = ctx.getContextUsage();
|
|
199
|
+
const windowSize =
|
|
200
|
+
cu?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
201
|
+
const pctValue = cu?.percent ?? 0;
|
|
202
|
+
// 与 built-in 一致:明确为 null 时显示未知;尚无 usage 时按 0.0% 显示。
|
|
203
|
+
const pctText =
|
|
204
|
+
cu?.percent === null ? "?" : `${pctValue.toFixed(1)}%`;
|
|
205
|
+
const contextClause = `${zh.footer.ratioLabel}${pctText}/${fmt(windowSize)}${getAutoCompactSuffix()}`;
|
|
206
|
+
const contextIndex = clauses.length;
|
|
207
|
+
clauses.push(contextClause);
|
|
208
|
+
|
|
209
|
+
// 占比按阈值变色(>90% 红、>70% 黄);实验标记沿用 built-in 样式。
|
|
210
|
+
const statsLeft = clauses
|
|
211
|
+
.map((clause, index) => {
|
|
212
|
+
if (index === contextIndex) {
|
|
213
|
+
return pctValue > 90
|
|
214
|
+
? theme.fg("error", clause)
|
|
215
|
+
: pctValue > 70
|
|
216
|
+
? theme.fg("warning", clause)
|
|
217
|
+
: theme.fg("dim", clause);
|
|
218
|
+
}
|
|
219
|
+
return theme.fg("dim", clause);
|
|
220
|
+
})
|
|
221
|
+
.join(" ");
|
|
222
|
+
|
|
223
|
+
// 第 2 行右:provider • 模型 • 思考等级(等级映射为中文)
|
|
224
|
+
let rightSideWithoutProvider = ctx.model?.id ?? zh.footer.noModel;
|
|
225
|
+
if (ctx.model?.reasoning) {
|
|
226
|
+
const tl = ctx.thinkingLevel || "off";
|
|
227
|
+
const tlLabel =
|
|
228
|
+
tl === "off"
|
|
229
|
+
? zh.footer.thinkingOff
|
|
230
|
+
: ((zh.footer.thinkingLevels as Record<string, string>)[
|
|
231
|
+
tl
|
|
232
|
+
] ?? tl);
|
|
233
|
+
rightSideWithoutProvider = `${rightSideWithoutProvider} • ${tlLabel}`;
|
|
234
|
+
}
|
|
235
|
+
let rightSide = rightSideWithoutProvider;
|
|
236
|
+
if (footerData.getAvailableProviderCount() > 1 && ctx.model) {
|
|
237
|
+
rightSide = `(${ctx.model.provider}) ${rightSideWithoutProvider}`;
|
|
238
|
+
if (
|
|
239
|
+
visibleWidth(statsLeft) + 2 + visibleWidth(rightSide) >
|
|
240
|
+
width
|
|
241
|
+
) {
|
|
242
|
+
rightSide = rightSideWithoutProvider;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let statsDisplay = statsLeft;
|
|
247
|
+
let statsWidth = visibleWidth(statsDisplay);
|
|
248
|
+
if (statsWidth > width) {
|
|
249
|
+
statsDisplay = truncateToWidth(statsDisplay, width, "...");
|
|
250
|
+
statsWidth = visibleWidth(statsDisplay);
|
|
251
|
+
}
|
|
252
|
+
const availableForRight = width - statsWidth - 2;
|
|
253
|
+
let statsLine = statsDisplay;
|
|
254
|
+
if (availableForRight > 0) {
|
|
255
|
+
const truncatedRight = truncateToWidth(
|
|
256
|
+
rightSide,
|
|
257
|
+
availableForRight,
|
|
258
|
+
"",
|
|
259
|
+
);
|
|
260
|
+
const padding = " ".repeat(
|
|
261
|
+
Math.max(
|
|
262
|
+
0,
|
|
263
|
+
width - statsWidth - visibleWidth(truncatedRight),
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
statsLine = `${statsDisplay}${padding}${theme.fg("dim", truncatedRight)}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const lines = [
|
|
270
|
+
truncateToWidth(
|
|
271
|
+
theme.fg("dim", pwd),
|
|
272
|
+
width,
|
|
273
|
+
theme.fg("dim", "..."),
|
|
274
|
+
),
|
|
275
|
+
];
|
|
276
|
+
lines.push(statsLine);
|
|
277
|
+
|
|
278
|
+
// 第 3 行:各扩展的 setStatus 状态
|
|
279
|
+
const statuses = footerData.getExtensionStatuses();
|
|
280
|
+
if (statuses.size > 0) {
|
|
281
|
+
const statusLine = Array.from(statuses.entries())
|
|
282
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
283
|
+
.map(([, text]) =>
|
|
284
|
+
text
|
|
285
|
+
.replace(/[\r\n\t]/g, " ")
|
|
286
|
+
.replace(/ +/g, " ")
|
|
287
|
+
.trim(),
|
|
288
|
+
)
|
|
289
|
+
.join(" ");
|
|
290
|
+
lines.push(
|
|
291
|
+
truncateToWidth(statusLine, width, theme.fg("dim", "...")),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return lines;
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
applyZhFooter();
|
|
301
|
+
return () => {
|
|
302
|
+
if (usingZhFooter) {
|
|
303
|
+
usingZhFooter = false;
|
|
304
|
+
ctx.ui.setFooter(undefined);
|
|
305
|
+
} else {
|
|
306
|
+
applyZhFooter();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中文 header:复刻内置结构(标题 + 折叠提示行 + 引导语),
|
|
3
|
+
* 快捷键跟随用户键位配置。
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
keyHint,
|
|
7
|
+
keyText,
|
|
8
|
+
rawKeyHint,
|
|
9
|
+
VERSION,
|
|
10
|
+
type ExtensionContext,
|
|
11
|
+
} from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { zh } from "./zh.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 设置中文启动 header(仅 TUI 模式,其他模式无效果)。
|
|
16
|
+
*
|
|
17
|
+
* 复刻内置结构:logo + 折叠提示行(快捷键跟随用户键位配置)+
|
|
18
|
+
* 展开帮助语 + 引导文案。
|
|
19
|
+
*
|
|
20
|
+
* @param ctx 当前会话上下文
|
|
21
|
+
*/
|
|
22
|
+
export function setChineseHeader(ctx: ExtensionContext) {
|
|
23
|
+
if (ctx.mode !== "tui") return;
|
|
24
|
+
ctx.ui.setHeader((_tui, theme) => ({
|
|
25
|
+
render(_width: number): string[] {
|
|
26
|
+
const logo =
|
|
27
|
+
theme.bold(theme.fg("accent", "pi")) +
|
|
28
|
+
theme.fg("dim", ` v${VERSION}`);
|
|
29
|
+
const compact = [
|
|
30
|
+
keyHint("app.interrupt", zh.header.interrupt),
|
|
31
|
+
rawKeyHint(
|
|
32
|
+
`${keyText("app.clear")}/${keyText("app.exit")}`,
|
|
33
|
+
zh.header.clearExit,
|
|
34
|
+
),
|
|
35
|
+
rawKeyHint("/", zh.header.commands),
|
|
36
|
+
rawKeyHint("!", "bash"),
|
|
37
|
+
keyHint("app.tools.expand", zh.header.more),
|
|
38
|
+
].join(theme.fg("muted", " · "));
|
|
39
|
+
return [
|
|
40
|
+
logo,
|
|
41
|
+
compact,
|
|
42
|
+
theme.fg(
|
|
43
|
+
"dim",
|
|
44
|
+
`${zh.header.expandHelp} (${keyText("app.tools.expand")})`,
|
|
45
|
+
),
|
|
46
|
+
"",
|
|
47
|
+
theme.fg("dim", zh.header.onboarding),
|
|
48
|
+
];
|
|
49
|
+
},
|
|
50
|
+
invalidate() {},
|
|
51
|
+
}));
|
|
52
|
+
}
|