Commit Graph

8 Commits

Author SHA1 Message Date
DaiChaoXiong 71d83d23ed fix(tui): show pending tool calls while streaming; pause-follow hint
1. tool calls inside a streaming assistant message were dropped entirely
   (push_content_block dropped the ToolCall arm after the merged-block
   refactor), so the call info only appeared once the whole turn finished.
   They now render as pending UiBlock::Tool blocks (ExecStatus::Pending, no
   status marker) as soon as the model issues them, then transition to the
   running execution blocks, then the merged request+result block.

2. with two thinkings, the first was pushed out of view by auto-follow when
   the second streamed; scrolling up already pauses follow, and the status
   bar now hints '↑跟随已暂停(滚到最底恢复)' while a run is active and the
   user is reviewing history.

regression test: a streaming partial with a tool call renders a pending Tool
block with the concrete summary.
2026-08-09 22:59:50 +08:00
DaiChaoXiong 7d289580b5 fix(tui): merged tool request+result blocks; fix wheel-down bottom jump
1. wheel-down jumped to the bottom because max_scroll() derived the limit from
   geometry, which only covers expandable blocks — a text-only conversation
   left it at 0, so one down-notch clamped scroll to 0 with follow=true and
   the next draw snapped to the real bottom. The limit now uses
   content_lines (the full rendered line count, stored per draw).

2. tool request and result are now one block ('requested where it returns'):
   UiBlock::ToolCall/ToolResult/ToolExec are unified into UiBlock::Tool, built
   by merging each assistant tool call with its matching tool_result message
   (looked up by call_id). Collapsed shows the concrete summary + duration +
   status; expanded shows human-readable args, then '── 结果 ──', then the
   output (two-level: partial preview with remaining-line hint, then full).
   Live running executions reuse the same block. ToolResult messages are no
   longer rendered standalone.

tests updated for the merged model; wheel test now drives content_lines.
2026-08-09 22:43:01 +08:00
DaiChaoXiong acddf7be1b fix(tui): correct wheel/PageUp scroll direction and line-step
The scroll offset semantics were inverted for the wheel and PageUp:
is the content offset (larger = newer), so scrolling up must DECREASE it, but
ScrollUp and PageUp both INCREASED it — every wheel event (either direction)
moved toward the bottom, and at the bottom (follow=true) an up-notch added 3
then got clamped back to max, making the wheel dead until keyboard-scrolling
away. Now:

- wheel ScrollUp/ScrollLeft: offset -1, follow cleared (works from the bottom)
- wheel ScrollDown/ScrollRight: offset +1, follow restored at the bottom
- PageUp: offset - view_height; PageDown: offset + view_height
- one line per wheel notch (was 3) for line-by-line scrolling

regression test drives handle_mouse with synthetic events (direction,
line-step, no underflow, follow transitions).
2026-08-09 22:34:51 +08:00
DaiChaoXiong 4b5f4cf52b feat(tui): cross-platform wheel scroll and two-level expansion
1. wheel: scrolling up now clears follow (the real bug — with follow=true every
   draw snapped back to the bottom, so the wheel appeared dead on both Linux
   and Windows); crossterm already normalizes xterm (Linux) and ConPTY
   (Windows) wheel events to ScrollUp/ScrollDown, and ScrollLeft/ScrollRight
   are handled too
2. expansion becomes three-state (collapsed → partial → full → collapsed):
   the first expand shows the first 20 lines with a '… 还有 N 行(再次点击/Tab
   展开全部)' hint; the second expand reveals everything. Applies to thinking,
   tool-execution output and tool-result content. ToolCall args stay
   single-level human-readable.

tests: three-state cycle, full-flag propagation
2026-08-09 22:30:04 +08:00
DaiChaoXiong c6660e0b84 feat(tui): markdown rendering, real-time transcript, scrolling, human args
1. scrolling: ↑/↓ scroll the messages when the input is empty (otherwise the
   caret moves), Ctrl+U scrolls up line-by-line, PageUp/PageDown page, wheel
   scrolls; follow-to-bottom restored on reaching the end
2. markdown: hand-rolled renderer (headers, bold/italic, inline code, fenced
   code blocks, lists, quotes, links, rules) applied to assistant/user text
3. ordering/refresh: messages are now committed into the transcript in real
   time at MessageEnd (assistant + tool results), so earlier turns never
   vanish mid-run and the latest content stays at the bottom; the live view
   shows only the streaming partial plus running tool executions (finished
   ones are presented by their tool_result messages, with duration)
4/5. expanded tool calls show human-readable arguments (command:/path:/edits
   as key-value lines) instead of raw JSON; tool results show duration

new tests: markdown renderer, format_args_human, commit_partial, live
running-only execs, tool-duration propagation
2026-08-09 22:21:30 +08:00
DaiChaoXiong a9de6fe24e fix(tui): surface concrete run errors instead of a bare error header
StreamEvent::Error is encoded by the agent loop as an assistant message with
stop_reason=error and error_message, but the UI only rendered the header and
the (empty) content, hiding the actual message (e.g. a provider HTTP 400
detail). Now:

- UiBlock::Error renders error_message in red, for both committed and live
  assistant messages
- on job Done, the error is also surfaced into the status bar and a note,
  even when the job itself reported no error (it was encoded as a message)
- regression test: an error message renders its concrete text
2026-08-09 21:48:29 +08:00
DaiChaoXiong 4ffd82b096 feat(tui): animated working indicator with phase detection
- Braille spinner driven by a per-frame counter, shown in both the status
  bar (bottom) and at the bottom of the message area while a job runs
- phase detection (RunState::working_phase): running tool > thinking >
  streaming > generic, so the hint reads e.g. "⠹ shell: cargo test…"
- testable via the injected clock (tests/state_tests.rs)
2026-08-09 21:43:28 +08:00
DaiChaoXiong 676dc1503e feat(tui): add focus-tui terminal UI
- ratatui + crossterm chat UI: streaming assistant text, multi-line input,
  bottom status bar (model / protocol / session id / context usage)
- thinking blocks collapse to a summary with duration + estimated tokens;
  tool calls collapse to a concrete summary (path / command) + duration;
  click or Tab expands the details
- config modal (/config): provider, OpenAI protocol, baseUrl, apiKey, model,
  contextWindow — persisted to ~/.focus/config.json; session list (/sessions)
  on the harness JSONL tree; /new, /compact, /help
- auto-compaction (80% threshold, no confirmation) + dynamic system prompt
  with usage info run in a background job (fake-provider-testable)
- Esc aborts a run (abandons the job thread; bounded by the provider timeout)
- tests: config codec, summaries/durations/wrap/caret, UI state machine with
  an injected clock, job flow with a fake provider, session persistence
2026-08-09 21:35:58 +08:00