Files
twenty/packages
Etienne 8f9f2f390e fix(ai-chat): show streaming activity during and between steps (#23581)
https://github.com/user-attachments/assets/e72e313c-66f8-40af-bf48-9225422ffa78




## Problem

During a streaming turn with tool calls, the chat goes completely static
in two places:

- **Between two steps**: once a tool's output arrives, its row flips to
past tense and nothing animates until the model's next chunk arrives (a
full LLM round trip, often several seconds). This window is defined by
the absence of parts, so no part-driven component can fill it — and the
pre-turn "…" indicator can't either, since it's cleared on the turn's
first chunk and never comes back.
- **During tool execution**: the active tool row in
`ThinkingStepsDisplay` is a static icon + label; the only animated
element there is the orbit loader on an actively-streaming reasoning
part.

Users can't tell whether the AI chat is still thinking or blocked.

## Fix

- **Pending thinking row between steps.** The renderer flags the
trailing thinking-steps group of a streaming, error-free message
(`showPendingThinkingRow`), and `ThinkingStepsDisplay` appends the
thinking row (orbit loader + "Thinking") inside its rows container when
none of its own steps is active (`isThinking`, which it already
computes). The row occupies the exact slot where the next real step row
materializes, so the handoff happens in place with no layout shift.
- **One shared row component.** `AiChatThinkingRow` renders the orbit
loader + "Thinking" and is used both for an actively-streaming reasoning
step and for the pending row.
- **Shimmer on executing tools.** Active tool rows wrap their label
("Searching the web for…") in the existing `ShimmeringText` while
awaiting output, with the text as a direct child of the background-clip
element so the effect applies reliably.
- **Activity derived from the tool lifecycle state.**
`isThinkingStepPartActive` now checks `input-streaming` /
`input-available` instead of output presence, so a tool completing with
a legitimate `null` output is no longer classified as still running.

Why the trailing-group check is sufficient: anything in progress outside
the group — streaming answer text, a running code execution card, a
pending question — is itself a later render item, so the group isn't
last and never gets flagged. No message-wide part scanning needed.

## Notes

- The row renders only while `agentChatIsStreaming`, which the existing
keepalive watchdog force-clears (with a visible connection-lost error)
after ~5s of subscription silence — it cannot spin forever on a dead
stream.
- It never shows while waiting on the user: `ask_questions` renders as
its own item after the group, and the server ends the stream on that
tool anyway (`stopWhen`).
- Consciously not covered, for simplicity: a pause right after a
mid-turn text part or right after the routing row.

## Tests

- Renderer: trailing group flagged as pending while streaming; not
flagged when answer text follows or when not streaming
- `ThinkingStepsDisplay`: pending row appended after completed steps,
suppressed while a tool step runs, loading label shown on a running tool
- `isThinkingStepPartActive`: lifecycle-state cases, including a
completed tool with `null` output

Lint, format, and `typecheck twenty-front` are clean.
2026-07-31 09:07:29 +00:00
..