- 缩小标题加 ❯ 引导符、彩色版本号、中文说明 - 去除启动头部段落间空行 - /is 本地扩展和 npm 扩展合并为 [插件] 分组展示 - /is 新增 [工具] 区块显示所有注册工具及激活状态 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
159 lines
5.9 KiB
TypeScript
159 lines
5.9 KiB
TypeScript
/**
|
||
* 启动界面中文翻译扩展
|
||
*
|
||
* 将 pi 的初始启动界面中的介绍和引导文字翻译为中文,
|
||
* 快捷键和命令提示保持原样。
|
||
*/
|
||
|
||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||
import { keyHint, keyText, rawKeyHint, VERSION } from "@mariozechner/pi-coding-agent";
|
||
|
||
const SPROUTCLAW_LOGO = [
|
||
"███████╗██████╗ ██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗██╗ █████╗ ██╗ ██╗",
|
||
"██╔════╝██╔══██╗██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██╔════╝██║ ██╔══██╗██║ ██║",
|
||
"███████╗██████╔╝██████╔╝██║ ██║██║ ██║ ██║ ██║ ██║ ███████║██║ █╗ ██║",
|
||
"╚════██║██╔═══╝ ██╔══██╗██║ ██║██║ ██║ ██║ ██║ ██║ ██╔══██║██║███╗██║",
|
||
"███████║██║ ██║ ██║╚██████╔╝╚██████╔╝ ██║ ╚██████╗███████╗██║ ██║╚███╔███╔╝",
|
||
"╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ",
|
||
];
|
||
|
||
const LOGO_PALETTE = [
|
||
[125, 255, 230],
|
||
[154, 255, 140],
|
||
[255, 245, 135],
|
||
[255, 176, 235],
|
||
[150, 210, 255],
|
||
[190, 170, 255],
|
||
];
|
||
|
||
function rgb(text: string, color: number[]): string {
|
||
return `\x1b[38;2;${color[0]};${color[1]};${color[2]}m${text}\x1b[39m`;
|
||
}
|
||
|
||
function lerp(a: number, b: number, t: number): number {
|
||
return Math.round(a + (b - a) * t);
|
||
}
|
||
|
||
function gradientText(text: string, offset = 0): string {
|
||
const chars = [...text];
|
||
const last = Math.max(1, chars.length - 1);
|
||
return chars
|
||
.map((char, index) => {
|
||
if (char === " ") return char;
|
||
const position = (index / last + offset) % 1;
|
||
const scaled = position * (LOGO_PALETTE.length - 1);
|
||
const startIndex = Math.floor(scaled);
|
||
const endIndex = Math.min(LOGO_PALETTE.length - 1, startIndex + 1);
|
||
const t = scaled - startIndex;
|
||
const start = LOGO_PALETTE[startIndex];
|
||
const end = LOGO_PALETTE[endIndex];
|
||
return rgb(char, [
|
||
lerp(start[0], end[0], t),
|
||
lerp(start[1], end[1], t),
|
||
lerp(start[2], end[2], t),
|
||
]);
|
||
})
|
||
.join("");
|
||
}
|
||
|
||
function startupLine(label: string, text: string, color: number[]): string {
|
||
return `${rgb(label, color)} ${rgb(text, [210, 235, 255])}`;
|
||
}
|
||
|
||
function keyTextOr(action: string, fallback: string): string {
|
||
const text = keyText(action as any).trim();
|
||
return text || fallback;
|
||
}
|
||
|
||
function renderDivider(theme: { fg: (color: string, text: string) => string }, width: number): string {
|
||
return theme.fg("dim", "─".repeat(Math.max(1, width)));
|
||
}
|
||
|
||
function renderSproutclawLogo(theme: any, width: number): string[] {
|
||
if (width < 96) {
|
||
return [
|
||
[
|
||
rgb("❯ ", [100, 200, 255]),
|
||
theme.bold(gradientText("SproutClaw")),
|
||
rgb(` v${VERSION}`, [170, 235, 255]),
|
||
rgb(" · ", [80, 100, 130]),
|
||
rgb("萌芽运维开发助手", [190, 170, 255]),
|
||
].join(""),
|
||
];
|
||
}
|
||
|
||
const lines: string[] = [];
|
||
for (let i = 0; i < SPROUTCLAW_LOGO.length; i++) {
|
||
lines.push(theme.bold(gradientText(SPROUTCLAW_LOGO[i], i * 0.08)));
|
||
}
|
||
return lines;
|
||
}
|
||
|
||
export default function (pi: ExtensionAPI) {
|
||
pi.on("session_start", async (_event, ctx) => {
|
||
if (!ctx.hasUI) return;
|
||
|
||
ctx.ui.setHeader((_tui, theme) => {
|
||
const expandedInstructions = [
|
||
keyHint("app.interrupt", "中断当前任务"),
|
||
keyHint("app.clear", "清空输入"),
|
||
rawKeyHint(`${keyText("app.clear")} twice`, "退出"),
|
||
keyHint("app.exit", "空输入退出"),
|
||
keyHint("app.suspend", "挂起进程"),
|
||
keyHint("tui.editor.deleteToLineEnd" as any, "删除到行尾"),
|
||
keyHint("app.thinking.cycle", "切换思考强度"),
|
||
rawKeyHint(
|
||
`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`,
|
||
"切换模型",
|
||
),
|
||
keyHint("app.model.select", "选择模型"),
|
||
keyHint("app.tools.expand", "展开工具输出"),
|
||
keyHint("app.thinking.toggle", "展开思考过程"),
|
||
keyHint("app.editor.external", "外部编辑器"),
|
||
rawKeyHint("/", "Agent 命令"),
|
||
rawKeyHint("!", "执行 shell"),
|
||
rawKeyHint("!!", "执行 shell 且不进上下文"),
|
||
keyHint("app.message.followUp", "追加后续消息"),
|
||
keyHint("app.message.dequeue", "编辑队列消息"),
|
||
keyHint("app.clipboard.pasteImage", "粘贴图片"),
|
||
rawKeyHint("drop files", "附加文件"),
|
||
].join("\n");
|
||
const compactOnboarding = startupLine(
|
||
"工作台",
|
||
`描述要处理的代码、部署或服务器问题,我会执行命令、修改文件并同步进度。按 ${keyTextOr("app.tools.expand", "Ctrl+O")} 展开详情。`,
|
||
[150, 255, 210],
|
||
);
|
||
const onboarding = [
|
||
startupLine("SproutClaw", "树萌芽运维开发助手,负责代码修改、服务部署、服务器操作、排障和知识检索。", [255, 232, 140]),
|
||
rgb("直接输入任务即可开始。", [255, 190, 235]),
|
||
].join("\n");
|
||
|
||
let expanded = false;
|
||
|
||
return {
|
||
render(width: number): string[] {
|
||
const divider = renderDivider(theme, width);
|
||
const lines: string[] = [
|
||
divider,
|
||
...renderSproutclawLogo(theme, width),
|
||
divider,
|
||
rgb("萌芽运维开发Agent助手", [170, 235, 255]),
|
||
];
|
||
if (expanded) {
|
||
lines.push(expandedInstructions);
|
||
lines.push(onboarding);
|
||
} else {
|
||
lines.push(compactOnboarding);
|
||
lines.push(onboarding);
|
||
}
|
||
return lines;
|
||
},
|
||
invalidate() {},
|
||
setExpanded(v: boolean) {
|
||
expanded = v;
|
||
},
|
||
};
|
||
});
|
||
});
|
||
}
|