omp packages

@muzi3/pi-ui0.2.4

Pi extension: 双行仪表盘 footer + 主题化工作指示器 + 折叠 thinking 标签 spinner 动画 + Catppuccin Mocha 层次增强主题

muzi3 Published 2 hours ago MIT ~358 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

@muzi3/pi-ui

Pi 扩展:把页脚换成双行上下文仪表盘,在编辑器边框上常驻显示模型名与耗时,并给折叠的 thinking 标签加 spinner 动画,主题跟着当前主题走。

替代 pi-catppuccin-tui 的自实现版本,修掉了原实现的两处问题:指示器颜色和多处 token 写死了 Macchiato 的十六进制色(换主题后错色),以及自定义 footer 整体替换内置 footer 后、其他扩展的 setStatus 状态 (如 @muzi3/comate-quota 的额度)变得不可见。

能力

能力 说明
footer 双行仪表盘页脚,两条进度条置顶,替换内置 footer
indicator 盲文 spinner ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ @80ms(与 pi 自带 Loader 逐帧相同、accent 单色);关掉后不显示(内置指示器回退为 pi 默认)
working 编辑器边框上常驻显示模型名与耗时,是否在生成只由盲文 spinner 表达
thinking 折叠的 thinking 块标签前加同款盲文 spinner ⠋ Thinking... @80ms(复用 indicator 的帧),只在流式期间转

footer 布局

Comate  ████░░░░░░░░░░  25% / ¥50.00  ~/muzi/pi-packages/packages/pi-ui    main
Context ██░░░░░░░░░░░░  1.7% / 272k  ↑74.7k ↓3.8k $0.009

色:标签 muted|进度条 按占用率 绿/黄/红|占比 跟条同色|/ 窗口、/ ¥ 与未知占位 dim| ↑ mdLink 蓝、↓ syntaxNumber 桃、$ syntaxOperator 青|工作目录 dim|分支 accent(图标与分支名同色)

  • 第 1 行:其他扩展通过 setStatus 设置的文本(如 @muzi3/comate-quota 的额度条)+ 工作目录 + git 分支。 工作目录取 ctx.cwd,$HOME 前缀缩成 ~(不在 HOME 下就保留绝对路径);宽度不够时从中间 丢段、保住末段(~/muzi/…/packages/pi-ui,项目名在末尾,左截断会先砍掉最该看的字), 连末段都放不下就整段丢掉,不显示成只有省略号的 ~/…。 分支前是 Nerd Font 的 git 图标 U+E0A0(nf-pl-branch,与 starship 默认提示符同一字形, 分支前是 Nerd Font 的 git 图标 U+E0A0(nf-pl-branch,与 starship 默认提示符同一字形, accent 色),需要 Nerd Font 字体,否则显示为豆腐块;分支名同色 accent(图标与名字 合成一整块,在行尾立成一个锚点)
  • 第 2 行:上下文进度条(14 格)+ 百分比 / 窗口 + 累计用量(↑in ↓out $cost)。 进度条配色按占用率分级:<70% 绿(success)、70~90% 黄(warning)、>90% 红(error); 刚压缩完占用未知时显示为暗色 ?
  • 分色规则:语义色(绿/黄/红)只用于占用率档位,其余按“角色”取色,避免整行一片灰或一片绿。 标签 muted;占比跟进度条同色,/ 窗口 降为 dim;用量三块各一色(见上)。 mdLink/syntaxNumber/syntaxOperator 是按“颜色”选的 token:pi 的 ThemeColor 是闭合的 56 个语义 token,没有给 footer 留名字。Mocha 的 teal 因此取不到——26 色里只有它和 rosewater/flamingo/pink/base/crust 没有任何 token 指向,改主题里这些 token 的映射等于改这里的观感
  • 第 1、2 行的标签列固定 8 列(Comate / Context ,含标签后的分隔空格),两条进度条因此左对齐。 Context 由本包补齐;扩展状态行是别的包产出的文本,要自己把标签补齐到 8 列、标签色用 muted (参考 @muzi3/comate-quota)——这是跨包视觉契约
  • 行尾的工作目录、分支与累计用量是次要信息,放不下就整段丢弃(不截断): 第 1 行先缩路径、再丢分支、最后才丢路径;进度条与百分比不参与降级
  • footer 高度不超过 2 行:没装扩展就没有额度条,不在 git 仓库就没有分支, 取不到 cwd 就没有工作目录
  • 内核 footer 右侧的“模型名 • 思考等级”两项都不要:模型名在编辑器边框上常驻,思考等级由边框 颜色区分(每个等级一个 thinking* token,主题里已拉开层次)
  • 累计用量口径与内置 footer 一致:assistant、带 usage 的 toolResult、branch_summary/compaction 求和

编辑器边框标题

╭─ claude-sonnet-4-5 ⏱ 12s ───────────────────────────────╮  空闲
╭─ ⠼ claude-sonnet-4-5 ⏱ 12s ─────────────────────────────╮  流式(模型名上有高光带扫过)
│ ❯                                                        │
╰──────────────────────────────────────────────────────────╯
  • 用自定义编辑器(PiUiBorderEditor extends CustomEditor)接管编辑器上边框。pi 自带的 WorkingStatusIndicator 只在流式期间挂在边框上(turn_start 挂、turn_end 清),空闲即消失, 承载不了“常驻”;所以子类覆写 setWorkingStatusIndicator,忽略 pi 的挂/清,恒挂自己的标题。 边框拼接、截断、↑ N more 滚动提示全部沿用 CustomEditor.renderTopBorder,没有重写排版
  • 输入被滚动时不用取舍:↑ N more 会与标题并列出现在同一行边框上 (── ⠼ model ⏱ 12s ──── ↑ 5 more ────),这是继承来的行为,不是额外实现
  • 指示器:流式为 ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ @80ms,帧序列与间隔都对齐 pi 自带 Loader(@earendil-works/pi-tui 的 DEFAULT_FRAMES / DEFAULT_INTERVAL_MS),颜色跟内置 working 指示器一样用 accent 单色。 用盲文而不是 ▘▝▗▖/◰◳◲◱ 这类整格块:后者在等宽字体下几乎占满整格,在边框上比正文还抢眼; 盲文墨水轻、不挑字体。10 帧在 visibleWidth 下都是 1 列,边框标题不抖。 早先试过 4 帧矩形(⠁⠈⠠⠄ 绕四角 + dim→muted→accent→borderAccent 明暗递进):帧数翻到 10 帧后 逐帧换色会变成“亮度在扫”,反而不像 spinner,所以回到单色。 不转的时候完全不显示——空闲、或关掉 indicator 时都不留静止帧(静止的帧反而 更难看出在不在生成);代价只是模型名左右移 1 列,在以 ─ 为主的边框上几乎看不出来
  • 配色:模型名空闲时直接取编辑器当前的 borderColor(this.borderColor),边框是什么色它 就是什么色。pi 会按 thinking level 换这个函数(getThinkingBorderColor:off overlay0、low blue、 medium sapphire、high mauve……),bash 模式另有 getBashModeBorderColor,所以写死任何 token 都会在切换后与边框脱色(例如 medium 下边框是青色而模型名还是灰的)。 流式时模型名换成扫光帧,配色改由 shimmer.ts 的三档负责(dim/muted/accent+bold), 此时不再跟随 borderColor;⏱ 耗时 始终为 dim
  • 耗时口径与旧版 footer 一致,只是位置变了:流式为已进行秒数(随 spinner 帧重绘跳秒), 空闲为上一轮总耗时;宽度不足时先丢掉 ⏱ 耗时,再逐字截断模型名
  • 退出/关闭 working 会换回内置编辑器,还原 pi 默认:仅流式期间在边框显示模型名
  • 需要 pi >= 0.85(CustomEditor 的 embedWorkingStatus 与 setWorkingStatusIndicator 从该版本才有); 本包不依赖 @earendil-works/pi-coding-agent 的本地副本,运行时由 pi 的扩展加载器别名到自身
  • 圆角与 ❯:覆写 renderTopBorder / renderBottomBorder / render,四角换成 ╭╮╰╯、左右补 │; 圆角只在 visibleWidth(line) === width 时替换,宁可没圆角也不要把宿主截断过的行改坏
  • ❯ 靠预留左内边距实现(用户内边距之外再加 3 列槽位),不接管宿主的文本编辑;输入被滚动时 不画 ❯(否则图标会漂到半截文字前面),极窄宽度放不下槽位时也不画。颜色取 this.borderColor, 所以跟边框、跟 thinking level 同步
  • 模型名在流式时换成扫光帧(shimmer.ts:dim → muted → accent+bold 三档,高光带 3 列宽, 帧数 = 字符数 + 2),40ms 一帧;盲文仍是 80ms 一帧,两套相位独立,转速不变。空闲回落为纯边框色

模型名覆盖(跨扩展)

边框标题默认显示当前会话模型,其他扩展可以在自己占用会话期间临时把它换掉:

pi.events.emit("pi-ui:model-override", "glm-4.6"); // 覆盖
pi.events.emit("pi-ui:model-override", undefined); // 恢复会话模型

通道是软约定:没扩展发布时什么都不发生,空串与 undefined 都表示恢复, session_start/session_shutdown 会清空残留值。@muzi3/compaction-custom-model 靠它在压缩期间显示压缩所用模型。

宽度不足时的降级顺序

自定义 footer 会整体接管内置 footer,内置第三行(扩展状态)随之消失,所以扩展状态置于页脚最前。 两行各自独立降级,每行都保住它最有信息量的部分:

  1. 第 1 行:完整(额度条 + 工作目录 + 分支)→ 路径中段省略 → 丢分支 → 丢工作目录 → 额度条截断加省略号
  2. 第 2 行:完整(进度条 + 计数 + 累计用量)→ 丢累计用量 → 丢 Context 标签 → 丢百分比/窗口 → 只剩进度条;进度条不接受被截断

thinking spinner 动画

折叠 thinking 块的标签(hideThinkingBlock 或 Ctrl+T 收起时显示的那行)前面盲文 spinner 照转,文字上有高光带扫过:

⠋ Thinking...   ← 流式中:盲文 @80ms + 文字上 40ms 扫光(复用边框指示器的帧)
✓ Thinking...   ← 结束后回落的静态帧(accent 色对勾,同 pi 选择器标记当前项的那个)

帧表不在这里另写一份,直接复用 indicator.ts 的 buildSpinnerFrames 与 shimmer.ts 的 shimmerFrames:两处观感要一致,两份列表迟早漂移。节奏分两套——盲文沿用 SPINNER_INTERVAL_MS (80ms,与边框 spinner 同速),扫光用 SHIMMER_TICK_MS(40ms,与边框标题同速)。

  • pi 只提供静态的 setHiddenThinkingLabel,动画靠定时器反复改写标签实现。每次改写都会重绘 整条 transcript,所以定时器只在 assistant 消息流式期间开表(message_start 开、 message_end 停,agent_settled 兜住用户中断);流式期间 pi 本来就在逐 token 重绘,这点 额外开销看不出来,空闲时零成本
  • 一轮里可能有多条 assistant 消息、中间夹着工具调用,动画跟消息粒度走而不是跟轮次走: 工具执行期间标签保持静态,不会被误看成还在思考
  • 宿主那个 setter 是全局广播(一个标签打给所有行实例),而每行拿自己的 lastMessage 重绘, 所以不做拦截的话,前面已完成的思考块会跟着新一轮一起转回进行态。thinking-rows.ts 在标签落到 实例前拦一道:只有 lastMessage 就是当前流式那条消息(同一个对象引用)的行拿动画帧,其余行 换成静止标签。行的身份必须用对象引用——AssistantMessage 上没有 timestamp 之类的字段 (pi-ai/dist/types.d.ts 里就只有 role/content/api/provider/model/usage/stopReason 等), 按字段比对在真实场景里恒不成立,补丁会静默退化成什么都不做。没有活动消息时补丁透传, 宿主的内部结构一旦变了也不会抛异常
  • 配色:盲文沿用 accent(同边框指示器),扫光三档 dim → muted → accent+bold,空闲回落 静态 thinkingText。两段都自带完整着色:pi 把整串包进 fg("thinkingText", label),内层 fg 的 reset 会让后面的行内文本不再跟随 thinkingText,颜色塌成终端默认色
  • 帧宽一致(盲文各占 1 列),标签不会左右抖;静止帧用 ✓(U+2713)而不是 ✔️, 后者带 emoji 变体选择符、多数终端占 2 列,结束瞬间会把标签推右一格。帧是每帧现取主题色 (换主题后立刻跟上),不像边框 spinner 那样需要按主题名重建缓存
  • 只在 thinking 块折叠时可见;Ctrl+T 展开思考原文时这个标签不显示,动画照旧开着(不额外 探测 pi 内部的 hideThinkingBlock,它没有对外读接口)

关闭 thinking 会调 setHiddenThinkingLabel(undefined) 还原 pi 默认的 Thinking...。

命令

/pi-ui                                        # 查看四块能力的开关状态
/pi-ui footer|indicator|working|thinking on|off  # 单独开关

关闭 footer 会恢复 pi 内置 footer;关闭 working 会换回内置编辑器(流式期间才显示模型名);关闭 indicator 会让 spinner 不再显示;关闭 thinking 会把折叠 thinking 标签还原成 pi 默认文案。 开关只在本次会话内有效,不写盘。

主题

themes/pi-ui-catppuccin.json(主题名 pi-ui-catppuccin)在 Catppuccin Mocha 基础上只改层次关系, 色板本身不变:

token 原值 现值 原因
toolErrorBg surface0 redBg #493446 原来与 toolSuccessBg 同为 surface0,成功/失败的工具块看不出区别。Catppuccin 没有暗红底色,#493446 是在 base 上叠 20% red 得到的
selectedBg surface0 surface1 原来与 userMessageBg 同色,选中行在列表里几乎不可见
searchMatchBg 未定义(回落 selectedBg) surface2 搜索命中需要比选中行更强的对比
searchMatchText 未定义(回落 text) text 显式写出,避免跟随 selectedBg 改动漂移
scrollbarTrack 未定义(回落 muted) surface0 全屏模式下滚动条轨道应当几乎不可见
scrollbarThumb 未定义(回落 text) overlay0 滑块比轨道亮,但不该抢正文
mdCodeBlockBorder surface2 surface1 与主 border 同级时,代码块外框噪音过大
mdQuoteBorder surface2 surface1 同上,引用属于次要结构
mdHeading mauve lavender mauve 同时是 accent/mdListBullet/syntaxKeyword/thinkingHigh,标题再占用就分不出主次
mdListBullet mauve overlay2 列表符号属于次要装饰,降为灰色
thinkingOff surface1 overlay0 1.80:1 时编辑框边框(thinking off 档)几乎看不见,模型名也一致地暗;换成 overlay0 后 3.36:1
thinkingMinimal surface2 overlay1 随 thinkingOff 上移,否则 off(3.36)会比 minimal(2.46)还亮,暗端递进反转
thinkingXhigh red maroon 给 thinkingMax 让出 red,形成 mauve → maroon → red 的递进
thinkingMax 未定义 red 补上最高档,取代回落的默认色

色板与配色思路来自 Catppuccin(MIT)与 pi 主题 catppuccin-tui-mocha(MIT)。

安装

pi install npm:@muzi3/pi-ui

从 pi-catppuccin-tui 迁移时,先装本包、把 ~/.pi/agent/settings.json 的 theme 改成 pi-ui-catppuccin, 再卸载前者。两个包都会调用 setFooter,后加载者生效,顺序不确定;先卸载可避免页脚互相覆盖。

敏感信息

本包不含个人信息、内部工具名与任何凭证。发布前扫描见仓库根 .pi/RULES.md。