Skip to content

Commit ce2c485

Browse files
committed
feat(mcp): extract tool list panel logic to separate component #371
Refactor tool list UI into McpToolListPanel for better code organization and reusability. Removes duplicate
1 parent a1193ae commit ce2c485

File tree

3 files changed

+204
-169
lines changed

3 files changed

+204
-169
lines changed

core/src/main/kotlin/cc/unitmesh/devti/mcp/editor/McpPreviewEditor.kt

Lines changed: 11 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package cc.unitmesh.devti.mcp.editor
33
import cc.unitmesh.devti.AutoDevIcons
44
import cc.unitmesh.devti.llm2.model.LlmConfig
55
import cc.unitmesh.devti.llms.custom.CustomLLMProvider
6-
import cc.unitmesh.devti.mcp.client.CustomMcpServerManager
76
import cc.unitmesh.devti.sketch.ui.patch.readText
87
import cc.unitmesh.devti.util.AutoDevCoroutineScope
8+
import com.intellij.openapi.actionSystem.Presentation
9+
import com.intellij.openapi.actionSystem.impl.ActionButton
910
import com.intellij.openapi.application.runReadAction
1011
import com.intellij.openapi.editor.Editor
1112
import com.intellij.openapi.fileEditor.FileEditor
1213
import com.intellij.openapi.fileEditor.FileEditorState
14+
import com.intellij.openapi.project.DumbAwareAction
1315
import com.intellij.openapi.project.Project
1416
import com.intellij.openapi.util.UserDataHolder
1517
import com.intellij.openapi.util.UserDataHolderBase
@@ -23,22 +25,15 @@ import com.intellij.ui.dsl.builder.Align
2325
import com.intellij.ui.dsl.builder.panel
2426
import com.intellij.util.ui.JBUI
2527
import com.intellij.util.ui.UIUtil
26-
import com.intellij.openapi.actionSystem.Presentation
27-
import com.intellij.openapi.actionSystem.impl.ActionButton
28-
import com.intellij.openapi.project.DumbAwareAction
2928
import com.intellij.util.ui.components.BorderLayoutPanel
3029
import io.modelcontextprotocol.kotlin.sdk.Tool
31-
import kotlinx.coroutines.CoroutineScope
32-
import kotlinx.coroutines.Dispatchers
33-
import kotlinx.coroutines.Job
3430
import kotlinx.coroutines.flow.Flow
3531
import kotlinx.coroutines.flow.MutableStateFlow
3632
import kotlinx.coroutines.flow.cancellable
3733
import kotlinx.coroutines.launch
3834
import java.awt.BorderLayout
3935
import java.awt.Dimension
4036
import java.awt.FlowLayout
41-
import java.awt.GridLayout
4237
import java.beans.PropertyChangeListener
4338
import javax.swing.*
4439
import javax.swing.border.CompoundBorder
@@ -51,22 +46,15 @@ open class McpPreviewEditor(
5146
private var mainEditor = MutableStateFlow<Editor?>(null)
5247
private val mainPanel = JPanel(BorderLayout())
5348

54-
private val mcpServerManager = CustomMcpServerManager.instance(project)
55-
private val allTools = mutableMapOf<String, List<Tool>>()
56-
private var loadingJob: Job? = null
57-
private val serverLoadingStatus = mutableMapOf<String, Boolean>()
58-
private val serverPanels = mutableMapOf<String, JPanel>()
59-
60-
private lateinit var toolsContainer: JPanel
49+
private var allTools = mutableMapOf<String, List<Tool>>()
50+
private lateinit var toolListPanel: McpToolListPanel
6151
private lateinit var chatbotSelector: JComboBox<String>
6252
private lateinit var chatInput: JBTextField
6353
private lateinit var testButton: ActionButton
6454
private lateinit var configButton: JButton
6555
private lateinit var resultPanel: McpResultPanel
6656
private val config = McpLlmConfig()
6757
private val borderColor = JBColor(0xE5E7EB, 0x3C3F41) // Equivalent to Tailwind gray-200
68-
private val textGray = JBColor(0x6B7280, 0x9DA0A8) // Equivalent to Tailwind gray-500
69-
private val headerColor = JBColor(0xF3F4F6, 0x2B2D30) // Light gray for section headers
7058

7159
init {
7260
createUI()
@@ -75,150 +63,9 @@ open class McpPreviewEditor(
7563

7664
private fun loadTools() {
7765
val content = runReadAction { virtualFile.readText() }
78-
loadingJob?.cancel()
79-
serverLoadingStatus.clear()
80-
serverPanels.clear()
81-
allTools.clear()
82-
83-
SwingUtilities.invokeLater {
84-
toolsContainer.removeAll()
85-
toolsContainer.revalidate()
86-
toolsContainer.repaint()
87-
}
88-
89-
loadingJob = CoroutineScope(Dispatchers.IO).launch {
90-
val serverConfigs = mcpServerManager.getServerConfigs(content)
91-
92-
if (serverConfigs.isNullOrEmpty()) {
93-
SwingUtilities.invokeLater {
94-
showNoServersMessage()
95-
}
96-
return@launch
97-
}
98-
99-
SwingUtilities.invokeLater {
100-
serverConfigs.keys.forEach { serverName ->
101-
serverLoadingStatus[serverName] = true
102-
createServerSection(serverName)
103-
}
104-
}
105-
106-
serverConfigs.forEach { (serverName, serverConfig) ->
107-
try {
108-
val tools = mcpServerManager.collectServerInfo(serverName, serverConfig)
109-
allTools[serverName] = tools
110-
111-
SwingUtilities.invokeLater {
112-
updateServerSection(serverName, tools)
113-
serverLoadingStatus[serverName] = false
114-
}
115-
} catch (e: Exception) {
116-
SwingUtilities.invokeLater {
117-
showServerError(serverName, e.message ?: "Unknown error")
118-
serverLoadingStatus[serverName] = false
119-
}
120-
}
121-
}
122-
}
123-
}
124-
125-
private fun createServerSection(serverName: String) {
126-
val serverPanel = JPanel(BorderLayout()).apply {
127-
background = UIUtil.getPanelBackground()
128-
border = BorderFactory.createCompoundBorder(
129-
BorderFactory.createMatteBorder(0, 0, 1, 0, borderColor),
130-
JBUI.Borders.empty(4, 0)
131-
)
132-
}
133-
134-
val headerPanel = JPanel(BorderLayout()).apply {
135-
background = headerColor
136-
border = JBUI.Borders.empty(4, 8)
137-
}
138-
139-
val serverLabel = JBLabel(serverName).apply {
140-
font = JBUI.Fonts.label(14.0f).asBold()
141-
foreground = UIUtil.getLabelForeground()
142-
}
143-
144-
headerPanel.add(serverLabel, BorderLayout.WEST)
145-
serverPanel.add(headerPanel, BorderLayout.NORTH)
146-
147-
val toolsPanel = JPanel(GridLayout(0, 3, 4, 4)).apply {
148-
background = UIUtil.getPanelBackground()
149-
border = JBUI.Borders.empty()
150-
}
151-
152-
val loadingLabel = JBLabel("Loading tools from $serverName...").apply {
153-
font = JBUI.Fonts.label(12.0f)
154-
foreground = textGray
155-
horizontalAlignment = SwingConstants.LEFT
156-
icon = AutoDevIcons.LOADING
157-
iconTextGap = JBUI.scale(8)
66+
toolListPanel.loadTools(content) { tools ->
67+
this.allTools = tools
15868
}
159-
160-
toolsPanel.add(loadingLabel)
161-
serverPanel.add(toolsPanel, BorderLayout.CENTER)
162-
163-
serverPanels[serverName] = toolsPanel
164-
165-
toolsContainer.add(serverPanel)
166-
toolsContainer.revalidate()
167-
toolsContainer.repaint()
168-
}
169-
170-
private fun updateServerSection(serverName: String, tools: List<Tool>) {
171-
val toolsPanel = serverPanels[serverName] ?: return
172-
toolsPanel.removeAll()
173-
174-
if (tools.isEmpty()) {
175-
val noToolsLabel = JBLabel("No tools available for $serverName").apply {
176-
foreground = textGray
177-
horizontalAlignment = SwingConstants.LEFT
178-
}
179-
toolsPanel.add(noToolsLabel)
180-
} else {
181-
tools.forEach { tool ->
182-
val panel = McpToolDetailPanel(project, serverName, tool)
183-
toolsPanel.add(panel)
184-
}
185-
}
186-
187-
toolsPanel.revalidate()
188-
toolsPanel.repaint()
189-
}
190-
191-
private fun showServerError(serverName: String, errorMessage: String) {
192-
val toolsPanel = serverPanels[serverName] ?: return
193-
toolsPanel.removeAll()
194-
195-
val errorLabel = JBLabel("Error loading tools: $errorMessage").apply {
196-
foreground = JBColor.RED
197-
horizontalAlignment = SwingConstants.LEFT
198-
}
199-
200-
toolsPanel.add(errorLabel)
201-
toolsPanel.revalidate()
202-
toolsPanel.repaint()
203-
}
204-
205-
private fun showNoServersMessage() {
206-
toolsContainer.removeAll()
207-
208-
val noServersPanel = JPanel(BorderLayout()).apply {
209-
background = UIUtil.getPanelBackground()
210-
border = JBUI.Borders.empty(16)
211-
}
212-
213-
val noServersLabel = JBLabel("No MCP servers configured. Please check your configuration.").apply {
214-
foreground = textGray
215-
horizontalAlignment = SwingConstants.CENTER
216-
}
217-
218-
noServersPanel.add(noServersLabel, BorderLayout.CENTER)
219-
toolsContainer.add(noServersPanel)
220-
toolsContainer.revalidate()
221-
toolsContainer.repaint()
22269
}
22370

22471
fun refreshMcpTool() {
@@ -244,12 +91,9 @@ open class McpPreviewEditor(
24491
border = JBUI.Borders.empty(4)
24592
}
24693

247-
toolsContainer = JPanel().apply {
248-
layout = BoxLayout(this, BoxLayout.Y_AXIS)
249-
background = UIUtil.getPanelBackground()
250-
}
251-
252-
val toolsScrollPane = JBScrollPane(toolsContainer).apply {
94+
toolListPanel = McpToolListPanel(project)
95+
96+
val toolsScrollPane = JBScrollPane(toolListPanel).apply {
25397
border = BorderFactory.createEmptyBorder()
25498
background = UIUtil.getPanelBackground()
25599
}
@@ -401,7 +245,6 @@ open class McpPreviewEditor(
401245
override fun addPropertyChangeListener(listener: PropertyChangeListener) {}
402246
override fun removePropertyChangeListener(listener: PropertyChangeListener) {}
403247
override fun dispose() {
404-
loadingJob?.cancel()
248+
toolListPanel.dispose()
405249
}
406250
}
407-

core/src/main/kotlin/cc/unitmesh/devti/mcp/editor/McpResultPanel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class McpResultPanel : JPanel(BorderLayout()) {
8080
gridy = gridY++
8181
weightx = 1.0
8282
fill = GridBagConstraints.HORIZONTAL
83-
insets = JBUI.insets(0, 0, 10, 0)
83+
insets = JBUI.insetsBottom(10)
8484
}
8585

8686
toolsPanel.add(toolPanel, gbc)

0 commit comments

Comments
 (0)