Skip to content

Commit 20a84f9

Browse files
committed
refactor(ui): extract RenderMessageItem composable
#453 Move timeline item rendering logic into a reusable RenderMessageItem composable to reduce code duplication and improve maintainability.
1 parent 240959c commit 20a84f9

File tree

2 files changed

+74
-128
lines changed

2 files changed

+74
-128
lines changed

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentMessageList.kt

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -66,70 +66,7 @@ fun AgentMessageList(
6666
verticalArrangement = Arrangement.spacedBy(6.dp) // Reduce spacing
6767
) {
6868
items(renderer.timeline) { timelineItem ->
69-
when (timelineItem) {
70-
is ComposeRenderer.TimelineItem.MessageItem -> {
71-
MessageItem(
72-
message = timelineItem.message,
73-
tokenInfo = timelineItem.tokenInfo
74-
)
75-
}
76-
77-
is ComposeRenderer.TimelineItem.CombinedToolItem -> {
78-
CombinedToolItem(
79-
toolName = timelineItem.toolName,
80-
details = timelineItem.details,
81-
fullParams = timelineItem.fullParams,
82-
filePath = timelineItem.filePath,
83-
toolType = timelineItem.toolType,
84-
success = timelineItem.success,
85-
summary = timelineItem.summary,
86-
output = timelineItem.output,
87-
fullOutput = timelineItem.fullOutput,
88-
executionTimeMs = timelineItem.executionTimeMs,
89-
docqlStats = timelineItem.docqlStats,
90-
onOpenFileViewer = onOpenFileViewer
91-
)
92-
}
93-
94-
is ComposeRenderer.TimelineItem.ToolResultItem -> {
95-
ToolResultItem(
96-
toolName = timelineItem.toolName,
97-
success = timelineItem.success,
98-
summary = timelineItem.summary,
99-
output = timelineItem.output,
100-
fullOutput = timelineItem.fullOutput
101-
)
102-
}
103-
104-
is ComposeRenderer.TimelineItem.ToolErrorItem -> {
105-
ToolErrorItem(error = timelineItem.error, onDismiss = { renderer.clearError() })
106-
}
107-
108-
is ComposeRenderer.TimelineItem.TaskCompleteItem -> {
109-
TaskCompletedItem(
110-
success = timelineItem.success,
111-
message = timelineItem.message
112-
)
113-
}
114-
115-
is ComposeRenderer.TimelineItem.TerminalOutputItem -> {
116-
TerminalOutputItem(
117-
command = timelineItem.command,
118-
output = timelineItem.output,
119-
exitCode = timelineItem.exitCode,
120-
executionTimeMs = timelineItem.executionTimeMs
121-
)
122-
}
123-
124-
is ComposeRenderer.TimelineItem.LiveTerminalItem -> {
125-
LiveTerminalItem(
126-
sessionId = timelineItem.sessionId,
127-
command = timelineItem.command,
128-
workingDirectory = timelineItem.workingDirectory,
129-
ptyHandle = timelineItem.ptyHandle
130-
)
131-
}
132-
}
69+
RenderMessageItem(timelineItem, onOpenFileViewer, renderer)
13370
}
13471

13572
if (renderer.currentStreamingOutput.isNotEmpty()) {
@@ -146,6 +83,78 @@ fun AgentMessageList(
14683
}
14784
}
14885

86+
@Composable
87+
fun RenderMessageItem(
88+
timelineItem: ComposeRenderer.TimelineItem,
89+
onOpenFileViewer: ((String) -> Unit)?,
90+
renderer: ComposeRenderer
91+
) {
92+
when (timelineItem) {
93+
is ComposeRenderer.TimelineItem.MessageItem -> {
94+
MessageItem(
95+
message = timelineItem.message,
96+
tokenInfo = timelineItem.tokenInfo
97+
)
98+
}
99+
100+
is ComposeRenderer.TimelineItem.CombinedToolItem -> {
101+
CombinedToolItem(
102+
toolName = timelineItem.toolName,
103+
details = timelineItem.details,
104+
fullParams = timelineItem.fullParams,
105+
filePath = timelineItem.filePath,
106+
toolType = timelineItem.toolType,
107+
success = timelineItem.success,
108+
summary = timelineItem.summary,
109+
output = timelineItem.output,
110+
fullOutput = timelineItem.fullOutput,
111+
executionTimeMs = timelineItem.executionTimeMs,
112+
docqlStats = timelineItem.docqlStats,
113+
onOpenFileViewer = onOpenFileViewer
114+
)
115+
}
116+
117+
is ComposeRenderer.TimelineItem.ToolResultItem -> {
118+
ToolResultItem(
119+
toolName = timelineItem.toolName,
120+
success = timelineItem.success,
121+
summary = timelineItem.summary,
122+
output = timelineItem.output,
123+
fullOutput = timelineItem.fullOutput
124+
)
125+
}
126+
127+
is ComposeRenderer.TimelineItem.ToolErrorItem -> {
128+
ToolErrorItem(error = timelineItem.error, onDismiss = { renderer.clearError() })
129+
}
130+
131+
is ComposeRenderer.TimelineItem.TaskCompleteItem -> {
132+
TaskCompletedItem(
133+
success = timelineItem.success,
134+
message = timelineItem.message
135+
)
136+
}
137+
138+
is ComposeRenderer.TimelineItem.TerminalOutputItem -> {
139+
TerminalOutputItem(
140+
command = timelineItem.command,
141+
output = timelineItem.output,
142+
exitCode = timelineItem.exitCode,
143+
executionTimeMs = timelineItem.executionTimeMs
144+
)
145+
}
146+
147+
is ComposeRenderer.TimelineItem.LiveTerminalItem -> {
148+
LiveTerminalItem(
149+
sessionId = timelineItem.sessionId,
150+
command = timelineItem.command,
151+
workingDirectory = timelineItem.workingDirectory,
152+
ptyHandle = timelineItem.ptyHandle
153+
)
154+
}
155+
}
156+
}
157+
149158
/**
150159
* Platform-specific live terminal display.
151160
* On JVM with PTY support: Renders an interactive terminal widget
@@ -177,23 +186,20 @@ fun MessageItem(
177186
) {
178187
PlatformMessageTextContainer(text = message.content) {
179188
Column(modifier = Modifier.padding(8.dp)) {
180-
// Use SketchRenderer for assistant messages to support thinking blocks
181189
if (!isUser) {
182190
SketchRenderer.RenderResponse(
183191
content = message.content,
184192
isComplete = true,
185193
modifier = Modifier.fillMaxWidth()
186194
)
187195
} else {
188-
// For user messages, use simple text
189196
Text(
190197
text = message.content,
191198
fontFamily = if (Platform.isWasm) FontFamily(Font(Res.font.NotoSansSC_Regular)) else FontFamily.Monospace,
192199
style = MaterialTheme.typography.bodyMedium
193200
)
194201
}
195202

196-
// Display token info if available (only for assistant messages)
197203
if (!isUser && tokenInfo != null && tokenInfo.totalTokens > 0) {
198204
Spacer(modifier = Modifier.height(4.dp))
199205
Text(

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/codereview/SuggestedFixesAgentView.kt

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -144,67 +144,7 @@ fun SuggestedFixesAgentView(
144144
if (fixRenderer != null) {
145145
// Render timeline items directly (no LazyColumn to avoid nesting)
146146
fixRenderer.timeline.forEach { timelineItem ->
147-
when (timelineItem) {
148-
is ComposeRenderer.TimelineItem.MessageItem -> {
149-
MessageItem(
150-
message = timelineItem.message,
151-
tokenInfo = timelineItem.tokenInfo
152-
)
153-
}
154-
is ComposeRenderer.TimelineItem.CombinedToolItem -> {
155-
CombinedToolItem(
156-
toolName = timelineItem.toolName,
157-
details = timelineItem.details,
158-
fullParams = timelineItem.fullParams,
159-
filePath = timelineItem.filePath,
160-
toolType = timelineItem.toolType,
161-
success = timelineItem.success,
162-
summary = timelineItem.summary,
163-
output = timelineItem.output,
164-
fullOutput = timelineItem.fullOutput,
165-
executionTimeMs = timelineItem.executionTimeMs,
166-
docqlStats = timelineItem.docqlStats,
167-
onOpenFileViewer = onOpenFileViewer
168-
)
169-
}
170-
is ComposeRenderer.TimelineItem.ToolResultItem -> {
171-
ToolResultItem(
172-
toolName = timelineItem.toolName,
173-
success = timelineItem.success,
174-
summary = timelineItem.summary,
175-
output = timelineItem.output,
176-
fullOutput = timelineItem.fullOutput
177-
)
178-
}
179-
is ComposeRenderer.TimelineItem.ToolErrorItem -> {
180-
ToolErrorItem(
181-
error = timelineItem.error,
182-
onDismiss = { fixRenderer.clearError() }
183-
)
184-
}
185-
is ComposeRenderer.TimelineItem.TaskCompleteItem -> {
186-
TaskCompletedItem(
187-
success = timelineItem.success,
188-
message = timelineItem.message
189-
)
190-
}
191-
is ComposeRenderer.TimelineItem.TerminalOutputItem -> {
192-
TerminalOutputItem(
193-
command = timelineItem.command,
194-
output = timelineItem.output,
195-
exitCode = timelineItem.exitCode,
196-
executionTimeMs = timelineItem.executionTimeMs
197-
)
198-
}
199-
is ComposeRenderer.TimelineItem.LiveTerminalItem -> {
200-
LiveTerminalItem(
201-
sessionId = timelineItem.sessionId,
202-
command = timelineItem.command,
203-
workingDirectory = timelineItem.workingDirectory,
204-
ptyHandle = timelineItem.ptyHandle
205-
)
206-
}
207-
}
147+
RenderMessageItem(timelineItem, onOpenFileViewer, fixRenderer)
208148
}
209149

210150
// Render streaming content if present

0 commit comments

Comments
 (0)