Skip to content

Commit 21722af

Browse files
committed
feat(mcp): add detailed request/response panels #371
Replace simple text area with card layout panels for displaying request parameters and response details. The new implementation includes: - Separate panels for request (with parameter parsing) and response (with duration) - JSON parameter formatting - Improved UI layout and styling
1 parent 7fad1de commit 21722af

File tree

1 file changed

+215
-10
lines changed

1 file changed

+215
-10
lines changed

core/src/main/kotlin/cc/unitmesh/devti/mcp/ui/McpMessageLogPanel.kt

Lines changed: 215 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@ package cc.unitmesh.devti.mcp.ui
33
import cc.unitmesh.devti.mcp.ui.model.McpMessage
44
import cc.unitmesh.devti.mcp.ui.model.MessageType
55
import com.intellij.ui.JBColor
6+
import com.intellij.ui.components.JBLabel
67
import com.intellij.ui.components.JBScrollPane
78
import com.intellij.ui.table.JBTable
89
import com.intellij.util.ui.JBUI
10+
import kotlinx.serialization.json.Json
11+
import kotlinx.serialization.json.JsonElement
12+
import kotlinx.serialization.json.jsonObject
913
import java.awt.BorderLayout
14+
import java.awt.CardLayout
1015
import java.awt.Component
1116
import java.awt.Dimension
17+
import java.awt.FlowLayout
18+
import java.awt.Font
19+
import java.awt.GridBagConstraints
20+
import java.awt.GridBagLayout
1221
import java.time.format.DateTimeFormatter
1322
import javax.swing.*
1423
import javax.swing.table.DefaultTableCellRenderer
@@ -27,20 +36,29 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
2736
autoCreateRowSorter = true
2837
}
2938

30-
private val detailTextArea = JTextArea().apply {
31-
isEditable = false
32-
wrapStyleWord = true
33-
lineWrap = true
34-
border = JBUI.Borders.empty(10)
39+
private val detailCardLayout = CardLayout()
40+
private val detailPanel = JPanel(detailCardLayout)
41+
42+
private val requestDetailPanel = RequestDetailPanel()
43+
44+
private val responseDetailPanel = ResponseDetailPanel()
45+
46+
companion object {
47+
private const val REQUEST_PANEL = "REQUEST_PANEL"
48+
private const val RESPONSE_PANEL = "RESPONSE_PANEL"
49+
private const val EMPTY_PANEL = "EMPTY_PANEL"
3550
}
3651

3752
init {
3853
table.getColumnModel().getColumn(0).cellRenderer = TypeColumnRenderer()
54+
55+
detailPanel.add(JPanel(), EMPTY_PANEL)
56+
detailPanel.add(requestDetailPanel, REQUEST_PANEL)
57+
detailPanel.add(responseDetailPanel, RESPONSE_PANEL)
3958

40-
// Create split pane
4159
val splitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT).apply {
4260
leftComponent = JBScrollPane(table)
43-
rightComponent = JBScrollPane(detailTextArea)
61+
rightComponent = JBScrollPane(detailPanel)
4462
dividerLocation = 600
4563
resizeWeight = 0.5
4664
}
@@ -50,8 +68,19 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
5068
if (!e.valueIsAdjusting && table.selectedRow >= 0) {
5169
val selectedIndex = table.convertRowIndexToModel(table.selectedRow)
5270
if (selectedIndex >= 0 && selectedIndex < messages.size) {
53-
detailTextArea.text = messages[selectedIndex].content
54-
detailTextArea.caretPosition = 0
71+
val message = messages[selectedIndex]
72+
when (message.type) {
73+
MessageType.REQUEST -> {
74+
requestDetailPanel.displayMessage(message)
75+
detailCardLayout.show(detailPanel, REQUEST_PANEL)
76+
}
77+
MessageType.RESPONSE -> {
78+
responseDetailPanel.displayMessage(message)
79+
detailCardLayout.show(detailPanel, RESPONSE_PANEL)
80+
}
81+
}
82+
} else {
83+
detailCardLayout.show(detailPanel, EMPTY_PANEL)
5584
}
5685
}
5786
}
@@ -68,7 +97,7 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
6897
fun clear() {
6998
messages.clear()
7099
tableModel.fireTableDataChanged()
71-
detailTextArea.text = ""
100+
detailCardLayout.show(detailPanel, EMPTY_PANEL)
72101
}
73102

74103
private inner class MessageTableModel : DefaultTableModel() {
@@ -135,4 +164,180 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
135164
return label
136165
}
137166
}
167+
168+
private class RequestDetailPanel : JPanel(BorderLayout()) {
169+
private val headerPanel = JPanel(BorderLayout()).apply {
170+
border = JBUI.Borders.empty(10, 10, 5, 10)
171+
background = JBColor(0xF8F9FA, 0x2B2D30)
172+
}
173+
174+
private val toolLabel = JBLabel().apply {
175+
font = font.deriveFont(Font.BOLD, font.size + 2f)
176+
}
177+
178+
private val parametersPanel = JPanel(GridBagLayout()).apply {
179+
border = JBUI.Borders.empty(10)
180+
background = JBColor(0xFFFFFF, 0x2B2D30)
181+
}
182+
183+
init {
184+
headerPanel.add(JBLabel("Tool:").apply {
185+
font = font.deriveFont(Font.BOLD)
186+
border = JBUI.Borders.emptyRight(8)
187+
}, BorderLayout.WEST)
188+
headerPanel.add(toolLabel, BorderLayout.CENTER)
189+
190+
add(headerPanel, BorderLayout.NORTH)
191+
add(JBScrollPane(parametersPanel), BorderLayout.CENTER)
192+
}
193+
194+
fun displayMessage(message: McpMessage) {
195+
toolLabel.text = message.toolName ?: "Unknown Tool"
196+
197+
parametersPanel.removeAll()
198+
199+
val paramJson = message.parameters
200+
if (paramJson != null && paramJson != "{}" && paramJson.isNotBlank()) {
201+
try {
202+
val json = Json { ignoreUnknownKeys = true }
203+
val parsedJson = json.parseToJsonElement(paramJson).jsonObject
204+
205+
val headerConstraints = GridBagConstraints().apply {
206+
gridx = 0
207+
gridy = 0
208+
gridwidth = 2
209+
fill = GridBagConstraints.HORIZONTAL
210+
anchor = GridBagConstraints.NORTHWEST
211+
insets = JBUI.insetsBottom(10)
212+
}
213+
214+
parametersPanel.add(JBLabel("Parameters:").apply {
215+
font = font.deriveFont(Font.BOLD, font.size + 1f)
216+
}, headerConstraints)
217+
218+
var row = 1
219+
parsedJson.entries.forEach { (key, value) ->
220+
// Parameter name
221+
val nameConstraints = GridBagConstraints().apply {
222+
gridx = 0
223+
gridy = row
224+
anchor = GridBagConstraints.NORTHWEST
225+
insets = JBUI.insets(5, 0, 5, 10)
226+
}
227+
228+
parametersPanel.add(JBLabel("$key:").apply {
229+
font = font.deriveFont(Font.BOLD)
230+
}, nameConstraints)
231+
232+
// Parameter value
233+
val valueConstraints = GridBagConstraints().apply {
234+
gridx = 1
235+
gridy = row++
236+
weightx = 1.0
237+
fill = GridBagConstraints.HORIZONTAL
238+
anchor = GridBagConstraints.NORTHWEST
239+
insets = JBUI.insets(5, 0)
240+
}
241+
242+
val valueText = formatJsonValue(value)
243+
val valueTextArea = JTextArea(valueText).apply {
244+
lineWrap = true
245+
wrapStyleWord = true
246+
isEditable = false
247+
border = null
248+
background = parametersPanel.background
249+
}
250+
251+
parametersPanel.add(valueTextArea, valueConstraints)
252+
}
253+
254+
// Add filler to push everything to the top
255+
val fillerConstraints = GridBagConstraints().apply {
256+
gridx = 0
257+
gridy = row
258+
gridwidth = 2
259+
weighty = 1.0
260+
fill = GridBagConstraints.BOTH
261+
}
262+
parametersPanel.add(JPanel().apply { background = parametersPanel.background }, fillerConstraints)
263+
264+
} catch (e: Exception) {
265+
// If parsing fails, fall back to displaying raw JSON
266+
val rawParamConstraints = GridBagConstraints().apply {
267+
gridx = 0
268+
gridy = 0
269+
weightx = 1.0
270+
weighty = 1.0
271+
fill = GridBagConstraints.BOTH
272+
}
273+
274+
parametersPanel.add(JTextArea(paramJson).apply {
275+
lineWrap = true
276+
wrapStyleWord = true
277+
isEditable = false
278+
}, rawParamConstraints)
279+
}
280+
} else {
281+
val noParamsConstraints = GridBagConstraints().apply {
282+
gridx = 0
283+
gridy = 0
284+
weightx = 1.0
285+
fill = GridBagConstraints.HORIZONTAL
286+
}
287+
288+
parametersPanel.add(JBLabel("No parameters").apply {
289+
foreground = JBColor.GRAY
290+
}, noParamsConstraints)
291+
292+
val fillerConstraints = GridBagConstraints().apply {
293+
gridx = 0
294+
gridy = 1
295+
weightx = 1.0
296+
weighty = 1.0
297+
fill = GridBagConstraints.BOTH
298+
}
299+
parametersPanel.add(JPanel().apply { background = parametersPanel.background }, fillerConstraints)
300+
}
301+
302+
parametersPanel.revalidate()
303+
parametersPanel.repaint()
304+
}
305+
306+
private fun formatJsonValue(element: JsonElement): String {
307+
// Convert JsonElement to a nicely formatted string
308+
return element.toString()
309+
}
310+
}
311+
312+
private class ResponseDetailPanel : JPanel(BorderLayout()) {
313+
private val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply {
314+
border = JBUI.Borders.empty(10)
315+
background = JBColor(0xF8F9FA, 0x2B2D30)
316+
}
317+
318+
private val durationLabel = JBLabel().apply {
319+
font = font.deriveFont(Font.BOLD)
320+
}
321+
322+
private val contentTextArea = JTextArea().apply {
323+
isEditable = false
324+
wrapStyleWord = true
325+
lineWrap = true
326+
border = JBUI.Borders.empty(10)
327+
}
328+
329+
init {
330+
headerPanel.add(JBLabel("Response Duration:"))
331+
headerPanel.add(durationLabel)
332+
333+
add(headerPanel, BorderLayout.NORTH)
334+
add(JBScrollPane(contentTextArea), BorderLayout.CENTER)
335+
}
336+
337+
fun displayMessage(message: McpMessage) {
338+
durationLabel.text = message.duration?.toString()?.plus(" ms") ?: "N/A"
339+
contentTextArea.text = message.content
340+
contentTextArea.caretPosition = 0
341+
}
342+
}
138343
}

0 commit comments

Comments
 (0)