@@ -3,12 +3,21 @@ package cc.unitmesh.devti.mcp.ui
3
3
import cc.unitmesh.devti.mcp.ui.model.McpMessage
4
4
import cc.unitmesh.devti.mcp.ui.model.MessageType
5
5
import com.intellij.ui.JBColor
6
+ import com.intellij.ui.components.JBLabel
6
7
import com.intellij.ui.components.JBScrollPane
7
8
import com.intellij.ui.table.JBTable
8
9
import com.intellij.util.ui.JBUI
10
+ import kotlinx.serialization.json.Json
11
+ import kotlinx.serialization.json.JsonElement
12
+ import kotlinx.serialization.json.jsonObject
9
13
import java.awt.BorderLayout
14
+ import java.awt.CardLayout
10
15
import java.awt.Component
11
16
import java.awt.Dimension
17
+ import java.awt.FlowLayout
18
+ import java.awt.Font
19
+ import java.awt.GridBagConstraints
20
+ import java.awt.GridBagLayout
12
21
import java.time.format.DateTimeFormatter
13
22
import javax.swing.*
14
23
import javax.swing.table.DefaultTableCellRenderer
@@ -27,20 +36,29 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
27
36
autoCreateRowSorter = true
28
37
}
29
38
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"
35
50
}
36
51
37
52
init {
38
53
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 )
39
58
40
- // Create split pane
41
59
val splitPane = JSplitPane (JSplitPane .HORIZONTAL_SPLIT ).apply {
42
60
leftComponent = JBScrollPane (table)
43
- rightComponent = JBScrollPane (detailTextArea )
61
+ rightComponent = JBScrollPane (detailPanel )
44
62
dividerLocation = 600
45
63
resizeWeight = 0.5
46
64
}
@@ -50,8 +68,19 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
50
68
if (! e.valueIsAdjusting && table.selectedRow >= 0 ) {
51
69
val selectedIndex = table.convertRowIndexToModel(table.selectedRow)
52
70
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 )
55
84
}
56
85
}
57
86
}
@@ -68,7 +97,7 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
68
97
fun clear () {
69
98
messages.clear()
70
99
tableModel.fireTableDataChanged()
71
- detailTextArea.text = " "
100
+ detailCardLayout.show(detailPanel, EMPTY_PANEL )
72
101
}
73
102
74
103
private inner class MessageTableModel : DefaultTableModel () {
@@ -135,4 +164,180 @@ class McpMessageLogPanel : JPanel(BorderLayout()) {
135
164
return label
136
165
}
137
166
}
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
+ }
138
343
}
0 commit comments