Skip to content

feat(web): 输入框按 ↑ 调出之前发过的消息 - #912

Open
6iKUN6 wants to merge 1 commit into
openpi-dev:mainfrom
6iKUN6:feat/web-composer-input-history
Open

6iKUN6 wants to merge 1 commit into
openpi-dev:mainfrom
6iKUN6:feat/web-composer-input-history

Conversation

@6iKUN6

@6iKUN6 6iKUN6 commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Problem

Web 的输入框不能像 TUI 那样按 ↑ 调出之前发过的消息。

  • Pi 原生的 TUI 输入框可以用 ↑ / ↓ 翻自己发过的消息,改一改或者原样再发一次。
  • Web 输入框(web/ui/src/features/composer/Composer.tsx)里的 ↑ / ↓ 只在斜杠命令菜单打开时用来选命令。菜单关着时按 ↑ 只会移动光标,想复用上一条只能翻对话记录复制粘贴。

两边用起来不一致,从 TUI 换到 Web 的用户会很不习惯。

Value

在 Web 里也能快速重发、微调上一条或者更早的消息,操作和 TUI 一样。

Approach

按键规则:和 Pi 的 TUI 一致

参照 pi-tui 里 Editor.navigateHistory:

  • 按 ↑ 往更早的一条翻。只在这几种情况下生效:输入框是空的;光标在最开头;已经在翻历史了。另外光标必须在第一行,没有选中文字。其他时候 ↑ 还是正常移动光标。
  • 按 ↓ 往更新的一条翻,只在翻历史、并且光标在最后一行时生效。翻过最新一条后,恢复开始翻之前的草稿和光标位置。
  • 调出一条历史后,只要改了内容就退出翻历史的状态,再按 ↑ 会从最新一条重新开始。之后按 Enter 照常发送。
  • 这几种情况不处理,原来的按键行为保持不变:斜杠命令菜单打开时;正在用输入法拼字时(这个判断在原有逻辑里就排在最前面);按着 Shift、Ctrl、Alt 或 Cmd 时。
  • 调出的内容如果以 / 开头,不会自动弹出斜杠命令菜单,免得 ↑ / ↓ 被菜单抢走。

历史从哪来

  • 用当前会话里已加载的用户消息,按从新到旧的顺序。连续两条一样的只算一条,空白消息跳过。
  • 对 setup 请求的处理和"跳到第几轮"导航一样(setupDisplayMessage / isSetupPromptEcho),同一次输入不会出现两次。
  • 不另外存一份历史,以 Pi 的 Session 为准。所以刷新页面、换浏览器以后都还在,切换会话后翻到的是那个会话自己的历史。
  • 很长的消息在页面上只投影了前 12,000 字(带 visibleText 截断标记)。调出这种消息时,先用已有的 /api/session/item 分页取回全文再填进输入框。取不到、取回时会话变了,或者用户已经在输入框里改了内容,就不填,不会把截断后的半截文字当成原文。
  • 只恢复文字,当时附带的图片和文件不会重新附上。

代码

  • 新文件 web/ui/src/features/composer/prompt-history.ts:从会话记录里提取历史、判断按键是否触发、计算下一个位置。都是纯函数,方便单独测试。
  • Composer.tsx:在按键处理里,斜杠命令菜单那段之后、Enter 发送之前,加一个 recallPrompt。写入输入框复用已有的 updateDraft,所以草稿的保存和恢复(composer-drafts.ts)照常生效。
  • 没加设置项,没加后端接口,发送流程和 Plan 审批都没动。
  • web/dist 已经重新构建。

范围限制

只翻已经加载到页面上的那部分会话(最多最近 250 条记录)。更早的消息以后可以接 /api/session/prompt-history 分页加载,这次没做。

Validation

基线是上游 0addb73,Pi 1.2.0,环境是 macOS arm64、Node v24.16.0。

新加的测试:tests/web/composer-prompt-history.spec.ts,10 个用例。

用例 修改前 修改后
↑ 按从新到旧调出,连续重复只算一次;↓ 依次往回,最后回到空输入框 ❌ ✅
有没发出的草稿时,↑ 翻历史,↓ 翻回来后草稿原样恢复 ❌ ✅
多行草稿、光标不在开头时,↑ 只移动光标;移到开头后再按 ↑ 才调出历史 ❌ ✅
调出后改了内容再发送,发出去的是改过的内容;改完之后 ↓ 不再翻历史 ❌ ✅
被截断的长消息:调出时先保持空白,分两页取回全文后填进输入框,内容和原文逐字一致 ❌ ✅
切换会话后,翻到的是新会话的历史 ❌ ✅
按着 Shift、Ctrl、Alt、Cmd,或者正在用输入法拼字时,↑ 不调出历史 ✅ ✅
斜杠命令菜单打开时,↑ / ↓ 仍然用来选命令 ✅ ✅
被截断的长消息取不回全文时,什么都不填 ✅ ✅
只有用户自己发的、非空的消息算历史 ✅ ✅

最后 4 个在修改前就能通过,是用来保证原有行为没被破坏的。

其他命令:

  • 输入框相关的已有测试:composer-*、斜杠命令、command-turn-state、prompt-visibility、app-render、模型选择器,加上新测试,共 11 个文件 320/320 通过。
  • bun run check:通过。
  • bun run test,Node 部分:2452 个,2442 通过、8 跳过、2 失败。
    • 失败的是 tests/extensions/plan-mode/result-rendering.test.ts 里的 a rich plan renders without throwing and keeps its content 和 expanded survives edge-content plans at extreme widths。
    • 这两个在没改过的 main 上本地也失败,上游 CI 是绿的,是我本机环境的问题。
  • bun run test,UI 部分:Node 部分失败后脚本没有继续,我用 bun run test:ui 单独补跑了,109 个文件、1642 个用例全部通过。
  • 没有在真实浏览器里手动试按键,也没测真实的中文输入法,只有上面的 jsdom 测试。

Impact

  • 用户可见: Web 输入框里按 ↑ / ↓ 可以翻自己发过的消息。光标在多行文字中间、斜杠命令菜单打开、按着修饰键、输入法拼字这几种情况下,按键行为和原来一样。
  • 模型可见的上下文和工具: 无。
  • 运行时和生命周期: 无。被截断的长消息在调出时会多发一两次 /api/session/item 请求,切换会话或卸载组件时会取消。
  • 持久化的配置和数据: 无。历史直接读当前会话,不额外存储。
  • 兼容性和风险: 输入框为空、或者光标在最开头时按 ↑,以前什么都不会发生,现在会调出上一条消息。这正是这个功能要的效果,和 TUI 一致。

🤖 Generated with Claude Code

Pi's TUI editor recalls earlier prompts with Up and Down, but the Web
composer only used the arrows for the slash command menu. Add the same
behaviour.

Up recalls from an empty editor, from the very start of the text, or
while already browsing; Down walks back toward the newest prompt and then
restores the unsent draft. Any edit ends browsing. The slash command menu,
IME composition and modified arrows keep their existing behaviour.

History is the selected Session's loaded user prompts, using the same
setup-echo rules as turn navigation, so it survives a reload and is never
a second store of user input. A prompt the transcript only projects in
part is recalled from its complete native text, or not at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant