perf(chat): add contentType to transcript list items (#1314) (#1344)

This commit is contained in:
m57 authored and GitHub committed 2026-09-28 00:49:33 +03:00
1 parent 673c53503f
commit 6a00696e68
1 file changed
+37 -7
@@ -53,6 +53,14 @@ import com.m57.hermescontrol.ui.chat.components.VaultUnlockCard
import com.m57.hermescontrol.ui.chat.toolCallMilestones
import com.m57.hermescontrol.ui.common.EmptyState
private object FullBleedContentType {
const val USER: String = "user"
const val REASONING: String = "reasoning"
const val PROSE: String = "prose"
const val TOOL: String = "tool"
const val SYSTEM_EVENT: String = "system_event"
}
/**
* The chat message list for FULL-BLEED style (issue #866) — the single chat
* surface since the bubble renderer was removed. User messages keep their
@@ -321,7 +329,10 @@ fun FullBleedChatList(
// Eager captures: item lambda reads these at
// composition time (lazy), so capture now.
val userMessage = turn.message
item(key = "user-${userMessage.id}") {
item(
key = "user-${userMessage.id}",
contentType = FullBleedContentType.USER,
) {
Column(modifier = Modifier.padding(bottom = 12.dp)) {
renderChatBubble(
message = userMessage,
@@ -352,7 +363,10 @@ fun FullBleedChatList(
.firstOrNull { it.message.reasoningText.isNotBlank() }
if (turnReasoning != null) {
val reasoning = turnReasoning.message
item(key = "reasoning-${reasoning.id}") {
item(
key = "reasoning-${reasoning.id}",
contentType = FullBleedContentType.REASONING,
) {
Column(modifier = Modifier.padding(bottom = 6.dp)) {
ReasoningCard(
reasoningText = reasoning.reasoningText,
@@ -371,7 +385,10 @@ fun FullBleedChatList(
if (!proseMessage.hasVisibleAgentContent() && !hoistedReasoning &&
proseMessage.reasoningText.isNotBlank()
) {
item(key = "reasoning-${proseMessage.id}") {
item(
key = "reasoning-${proseMessage.id}",
contentType = FullBleedContentType.REASONING,
) {
ReasoningCard(
reasoningText = proseMessage.reasoningText,
isStreaming = proseMessage.isStreaming,
@@ -379,7 +396,10 @@ fun FullBleedChatList(
}
}
if (proseMessage.hasVisibleAgentContent()) {
item(key = "prose-${proseMessage.id}") {
item(
key = "prose-${proseMessage.id}",
contentType = FullBleedContentType.PROSE,
) {
Column(modifier = Modifier.padding(bottom = 12.dp)) {
if (proseMessage.isStreaming && typingEffectEnabled) {
StreamingFullBleedWithTypingEffect(
@@ -434,7 +454,10 @@ fun FullBleedChatList(
is AgentEntry.ToolRow -> {
val toolMessage = entry.message
val milestone = toolMilestones[toolMessage.id]
item(key = "tool-${toolMessage.id}") {
item(
key = "tool-${toolMessage.id}",
contentType = FullBleedContentType.TOOL,
) {
Column(modifier = Modifier.padding(bottom = 6.dp)) {
FullBleedToolRow(toolMessage)
milestone?.let { count ->
@@ -446,7 +469,10 @@ fun FullBleedChatList(
is AgentEntry.SystemEvent -> {
val sysMessage = entry.message
item(key = "sys-${sysMessage.id}") {
item(
key = "sys-${sysMessage.id}",
contentType = FullBleedContentType.SYSTEM_EVENT,
) {
Column(modifier = Modifier.padding(bottom = 6.dp)) {
if (sysMessage.displayKind != null) {
// Timeline marker (issue #904):
@@ -469,7 +495,11 @@ fun FullBleedChatList(
}
tailItems.forEach { (key, content) ->
item(key = key) { content() }
item(
key = key,
// Tail keys are fixed per kind, so the key doubles as the slot type.
contentType = key,
) { content() }
}
}
// Loading history must never become the list's first visible anchor.