Skip to content

会话页:AI 过程折叠改为按正文分段的过程卡片 - #10

Merged
tall-1997 merged 13 commits into
mainfrom
feature/chat-process-card
Oct 14, 2026
Merged

tall-1997 merged 13 commits into
mainfrom
feature/chat-process-card

Conversation

@tall-1997

Copy link
Copy Markdown
Owner

做了什么

AI 过程折叠从「一轮一条居中发丝线、保留最后两步」改成「一段过程一张描边卡片」。

  • 一张卡片 = 两次正文之间的全部思考和工具。正文一出现,当前卡片结束,之后的步骤归下一张。没有正文的纯工具轮,整轮结束时收成一张。
  • 步骤不再留在列表里,展开后渲染在卡片内部。
  • 文案分三态:
    • 运行中且折叠:「思考中 · 思考 N · 工具 M · 时长」或「调用工具 ×× · 思考 N · 工具 M · 时长」,时长随运行走动;
    • 运行中且展开:顶栏只留当前状态(思考中 / 调用工具 ××);
    • 结束:「✅ · 思考 N · 工具 M · 时长」。
  • 「输出中」去掉了:正文一出现卡片就结束,这个状态只会闪一帧。
  • 两层开关各管各的。卡片层:运行中自动展开,手动折叠只压住这一张,下一张照常自动展开。思考层:卡片展开时正在输出的思考自动展开,输出完 500ms 后收起;手动收过或已经收起的思考,不跟着卡片重新展开。
  • 思考内容默认贴底跟随,用户一滑动就停住,滑回底部才恢复(沿用现有思考块的行为)。

测试

  • ChatFlatItemsProcessFoldTest、ChatFlatItemsToolOnlyFoldTest 按新契约重写,并新增卡片三态文案测试。
  • 本机沙箱无 JDK,编译与全量单测交由 CI(pr-check)验证。

tall-1997 and others added 13 commits October 14, 2026 00:22
A process card now covers one run — the thinking and tool steps between two
reply texts — instead of one hairline bar per round that kept its last two
steps. Reply text ends the current card on arrival and the next steps start a
new one; a tool-only round settles into a single card when the round ends.

Copy splits by state. While running and collapsed the card leads with what it
is doing ("思考中" / "调用工具 ××") plus the step counts and a ticking
duration; expanded, the header is only the current state because the steps are
on screen; settled, it reads "✅ · 思考 N · 工具 M · 时长". "输出中" is
intentionally absent — reply text closes the card immediately, so that state
would flash for one frame.

Expansion is two independent layers. A running card auto-expands, and a manual
collapse suppresses that for the rest of THIS card only; the next card
auto-expands again. A streaming thinking block auto-expands while its card is
open and collapses 500ms after it finishes, and a block the user collapsed (or
that already settled) does not reopen when the card does.
…r onto the process-card branch

The two branches only collided in ChatScreen.kt, where each added a block
right after expandedProcessIds. Both survive: the process card's auto-expand
(running cards open unless the user collapsed that card by hand) followed by
the pill's floatingOverlayBlocks mount gate and its 34dp reserve. The stale
hasFloatingTools predicate the merge duplicated is dropped — the pill's gate
is the one that honours the floating-bar pref and the streaming state.
…Class package, positional args on function-type call

- ChatScreen.kt: add the MinisMenuSectionHeader import (4 call sites
  referenced it with only MinisMenu/MinisMenuDivider imported).
- ChatScreenHelpers.kt: WindowSizeClass lives in
  androidx.window.core.layout, not androidx.compose.material3.adaptive
  (matches the import ChatSplitScaffold already uses).
- ChatSplitScaffold.kt: the listPane slot is a function TYPE; named
  arguments are prohibited on its call sites. Pass `embedded`
  positionally (true in the drawer, false in the pane/sidebar).
…r through the detailPane slot

- ChatAssistantMessageUI.kt: TextOverflow was imported twice (lines 25 and
  92) — ambiguous import error.
- ChatSplitScaffold.kt: openSessionDrawer is a LOCAL of
  ChatSplitScaffold, but the Route-level detailPane lambda referenced it
  (unresolved). Add onOpenSessionDrawer as the 7th parameter of the
  detailPane function TYPE; the scaffold passes
  `if (twoPane) null else openSessionDrawer` at its internal call site,
  and the Route lambda forwards it to ChatScreen unchanged.
…returns FlatChatItem list, outer expects ChatMessage list)
The per-segment process card built its messageId as
"${round.key}#${segment.firstBlockId}", but FlatKeys.of requires the
messageId to be ':'/'#'-free (parse() treats a trailing '#...' as the
dedupe suffix). Every folded-session build threw
IllegalArgumentException at the card construction site.

Join the segment discriminator with '--' instead. Consumers (ChatScreen's
toggle, runningCardKeys auto-expand) read item.messageId verbatim, so the
separator is internal to the key.
splitRounds treated RUNNING/PENDING/STREAMING tools as non-process, so a
live run of "thinking + the tool it just started" stayed under
SEGMENT_MIN_STEPS. The card never formed and the steps leaked back into
the list, which is what the 11 process-fold tests assert against.

A running tool is part of its run: stepRunning already records it and the
row loop drops every id the segment owns. shouldShowProcessToolRow now
only keeps an in-flight tool inline when folding produced no card.
ask_user_question stays outside the card.
The card model (one card per run of ≥ 2 steps, closed the moment reply
text arrives, expanded by the card's own key) landed with tests still
asserting the old round rules: ≤ 2 steps never fold, a streaming message
keeps its card running after the reply, and expansion is keyed by the
bare message id. Those assertions could not pass against the card
projection, which is why pr-check stayed red.

Pin the card rules instead: a two-step run cards and a single step stays
inline, reply text settles the card even while the message streams, and
expansion uses the card key the toggle actually stores.
…t on reply text

Two contract gaps behind the remaining pr-check failures:

1. A card was emitted only where the run's FIRST message met its LAST
   block, so a run spanning two messages (resume continuation, tool-only
   round) produced no card at all — its steps stayed folded out of the
   list and anything keyed by the card (expansion) broke with it.

2. A run closed by reply text still reported running when one of its
   steps carried an in-flight status. The card contract says the reply
   text ends the card on arrival ("输出中" was dropped for exactly this
   reason), so the card settles there; a run the round closed instead
   stays live while the round does.

Tests now pin the card contract and name it. pr-check prints full
assertion output (Gradle's condensed console hides expected/actual), so
the next red build is actionable from the job log alone.
Co-Authored-By: Minis Ultra <noreply@minis.app>
The fixture mixed a running tool with reply text in the same run, then
asserted the card stays live — the opposite of the card contract the
same file pins (reply text ends the card). The test only needs to prove
the running tool lives in the card and not in the list twice.

Co-Authored-By: Minis Ultra <noreply@minis.app>
@tall-1997
tall-1997 merged commit e2cc1fe into main Oct 14, 2026
1 check passed
@tall-1997
tall-1997 deleted the feature/chat-process-card branch October 16, 2026 06:18
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