Skip to content

Commit 6656c86

Browse files
committed
feat(acp): add engine selector and simplify ACP agent UI in IDEA plugin #536
- Add engine selector (AutoDev/ACP) to SwingBottomToolbar for clear mode switching - Simplify model/agent combo box to show only relevant items per engine mode - Add logging for ACP agent loading and UI state changes - Update IdeaDevInInputArea to handle engine switching and initial state setup - Fix ACP agent selection and configuration flow in UI components
1 parent b39b677 commit 6656c86

File tree

4 files changed

+209
-96
lines changed

4 files changed

+209
-96
lines changed

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/SwingBottomToolbar.kt

Lines changed: 140 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package cc.unitmesh.devins.idea.editor
22

3+
import cc.unitmesh.devins.idea.toolwindow.IdeaEngine
34
import cc.unitmesh.devti.llm2.GithubCopilotDetector
45
import cc.unitmesh.devins.idea.editor.multimodal.IdeaMultimodalState
56
import cc.unitmesh.llm.NamedModelConfig
@@ -17,16 +18,25 @@ import javax.swing.*
1718

1819
/**
1920
* Swing-based bottom toolbar for the input section.
20-
* Provides send/stop buttons, model selector, token info, and image attachment button.
21+
*
22+
* Features:
23+
* - Engine selector (AutoDev / ACP) - NEW!
24+
* - Model/Agent selector (shows LLM configs for AutoDev, ACP agents for ACP)
25+
* - Send/Stop buttons with multimodal support
26+
* - Token display, image attachment, prompt optimization
2127
*/
2228
class SwingBottomToolbar(
2329
private val project: Project?,
2430
private val onSendClick: () -> Unit,
2531
private val onStopClick: () -> Unit,
2632
private val onPromptOptimizationClick: () -> Unit,
27-
private val onImageClick: (() -> Unit)? = null // Optional image attachment callback
33+
private val onImageClick: (() -> Unit)? = null
2834
) : JPanel(BorderLayout()) {
2935

36+
// Engine selector (AutoDev / ACP)
37+
private val engineComboBox = ComboBox<String>()
38+
39+
// Model/Agent selector (content changes based on engine)
3040
private val modelComboBox = ComboBox<String>()
3141
private val tokenLabel = JBLabel()
3242
private val sendButton = JButton("Send", AllIcons.Actions.Execute)
@@ -43,33 +53,35 @@ class SwingBottomToolbar(
4353
isVisible = onImageClick != null
4454
}
4555

46-
// Image count badge label
4756
private val imageCountLabel = JBLabel().apply {
4857
isVisible = false
4958
foreground = JBColor(Color(0, 120, 212), Color(100, 180, 255))
5059
}
5160

5261
private var availableConfigs: List<NamedModelConfig> = emptyList()
62+
private var acpAgents: Map<String, cc.unitmesh.config.AcpAgentConfig> = emptyMap()
63+
private var currentEngine: IdeaEngine = IdeaEngine.AUTODEV
64+
65+
// Callbacks
5366
private var onConfigSelect: (NamedModelConfig) -> Unit = {}
5467
private var onConfigureClick: () -> Unit = {}
5568
private var onAddNewConfig: () -> Unit = {}
5669
private var onRefreshCopilot: () -> Unit = {}
57-
private var onAcpAgentSelect: (String) -> Unit = {} // ACP agent key
70+
private var onAcpAgentSelect: (String) -> Unit = {}
5871
private var onConfigureAcp: () -> Unit = {}
5972
private var onSwitchToAutodev: () -> Unit = {}
73+
private var onSwitchToAcp: () -> Unit = {}
74+
6075
private var isProcessing = false
6176
private var isEnhancing = false
6277
private var isRefreshingCopilot = false
6378
private var imageCount = 0
6479

65-
// Track ACP agent entries in the combo box
66-
// Format: list of (comboIndex, agentKey) for ACP items
67-
private var acpAgentEntries: List<Pair<Int, String>> = emptyList()
68-
private val ACP_SEPARATOR = "--- ACP Agents ---"
80+
// Track ACP agent keys (parallel to combo box items)
81+
private var acpAgentKeys: List<String> = emptyList()
6982
private val ACP_CONFIGURE = "Configure ACP..."
70-
private var isComboUpdating = false
83+
private var isUpdating = false
7184

72-
// Refresh GitHub Copilot button (only shown when Copilot is configured)
7385
private val refreshCopilotButton = JButton(AllIcons.Actions.Refresh).apply {
7486
toolTipText = "Refresh GitHub Copilot Models"
7587
preferredSize = Dimension(28, 28)
@@ -83,50 +95,86 @@ class SwingBottomToolbar(
8395
border = JBUI.Borders.empty(4)
8496
isOpaque = false
8597

86-
// Left side: Model selector and token info
98+
// Left side: Engine + Model selectors + token
8799
val leftPanel = JPanel(FlowLayout(FlowLayout.LEFT, 4, 0)).apply {
88100
isOpaque = false
89101

102+
// Engine selector (AutoDev / ACP)
103+
engineComboBox.preferredSize = Dimension(90, 28)
104+
engineComboBox.addItem("AutoDev")
105+
engineComboBox.addItem("ACP")
106+
engineComboBox.addActionListener {
107+
if (isUpdating) return@addActionListener
108+
val selected = engineComboBox.selectedItem as? String
109+
println("Engine selector changed to: $selected")
110+
when (selected) {
111+
"AutoDev" -> {
112+
currentEngine = IdeaEngine.AUTODEV
113+
onSwitchToAutodev()
114+
rebuildModelComboBox()
115+
}
116+
"ACP" -> {
117+
currentEngine = IdeaEngine.ACP
118+
onSwitchToAcp()
119+
rebuildModelComboBox()
120+
}
121+
}
122+
}
123+
add(engineComboBox)
124+
125+
// Model/Agent selector
90126
modelComboBox.preferredSize = Dimension(180, 28)
91127
modelComboBox.addActionListener {
92-
if (isComboUpdating) return@addActionListener
128+
if (isUpdating) return@addActionListener
93129
val selectedIndex = modelComboBox.selectedIndex
94130
val selectedItem = modelComboBox.selectedItem as? String
95131

96-
when {
97-
// "Configure ACP..." item
98-
selectedItem == ACP_CONFIGURE -> {
99-
onConfigureAcp()
100-
}
101-
// Separator (not selectable, reset)
102-
selectedItem == ACP_SEPARATOR -> {
103-
// Do nothing
132+
when (currentEngine) {
133+
IdeaEngine.AUTODEV -> {
134+
if (selectedIndex in availableConfigs.indices) {
135+
onConfigSelect(availableConfigs[selectedIndex])
136+
}
104137
}
105-
// ACP agent entry
106-
acpAgentEntries.any { it.first == selectedIndex } -> {
107-
val agentKey = acpAgentEntries.first { it.first == selectedIndex }.second
108-
onAcpAgentSelect(agentKey)
109-
}
110-
// Regular LLM config
111-
selectedIndex in availableConfigs.indices -> {
112-
onSwitchToAutodev()
113-
onConfigSelect(availableConfigs[selectedIndex])
138+
IdeaEngine.ACP -> {
139+
when (selectedItem) {
140+
ACP_CONFIGURE -> onConfigureAcp()
141+
else -> {
142+
if (selectedIndex in acpAgentKeys.indices) {
143+
val agentKey = acpAgentKeys[selectedIndex]
144+
onAcpAgentSelect(agentKey)
145+
}
146+
}
147+
}
114148
}
115149
}
116150
}
117151
add(modelComboBox)
118152

119-
// Add New Config button
153+
// Add button (Config+ for AutoDev, ACP+ for ACP)
120154
val addConfigButton = JButton(AllIcons.General.Add).apply {
121-
toolTipText = "Add New Config"
122155
preferredSize = Dimension(28, 28)
123156
isBorderPainted = false
124157
isContentAreaFilled = false
125-
addActionListener { onAddNewConfig() }
158+
addActionListener {
159+
if (currentEngine == IdeaEngine.AUTODEV) {
160+
onAddNewConfig()
161+
} else {
162+
onConfigureAcp()
163+
}
164+
}
126165
}
166+
167+
// Update tooltip dynamically
168+
engineComboBox.addItemListener {
169+
addConfigButton.toolTipText = if (currentEngine == IdeaEngine.AUTODEV) {
170+
"Add New LLM Config"
171+
} else {
172+
"Configure ACP Agents"
173+
}
174+
}
175+
addConfigButton.toolTipText = "Add New LLM Config"
127176
add(addConfigButton)
128177

129-
// Refresh GitHub Copilot button
130178
add(refreshCopilotButton)
131179

132180
tokenLabel.foreground = JBUI.CurrentTheme.Label.disabledForeground()
@@ -138,7 +186,6 @@ class SwingBottomToolbar(
138186
val rightPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 4, 0)).apply {
139187
isOpaque = false
140188

141-
// Image attachment button (only if callback is provided)
142189
if (onImageClick != null) {
143190
imageButton.addActionListener { onImageClick.invoke() }
144191
add(imageButton)
@@ -201,79 +248,86 @@ class SwingBottomToolbar(
201248
}
202249

203250
fun setAvailableConfigs(configs: List<NamedModelConfig>) {
251+
println("SwingBottomToolbar.setAvailableConfigs: ${configs.size} configs")
204252
availableConfigs = configs
205-
rebuildComboBox()
253+
if (currentEngine == IdeaEngine.AUTODEV) {
254+
rebuildModelComboBox()
255+
}
206256
}
207257

208-
/**
209-
* Set available ACP agents and rebuild the combo box.
210-
*/
211258
fun setAcpAgents(agents: Map<String, cc.unitmesh.config.AcpAgentConfig>) {
212-
rebuildComboBoxWithAcp(agents)
259+
println("SwingBottomToolbar.setAcpAgents: ${agents.size} agents")
260+
acpAgents = agents
261+
if (currentEngine == IdeaEngine.ACP) {
262+
rebuildModelComboBox()
263+
}
213264
}
214265

215-
private fun rebuildComboBox() {
216-
rebuildComboBoxWithAcp(emptyMap())
266+
fun setCurrentEngine(engine: IdeaEngine) {
267+
isUpdating = true
268+
try {
269+
currentEngine = engine
270+
engineComboBox.selectedIndex = when (engine) {
271+
IdeaEngine.AUTODEV -> 0
272+
IdeaEngine.ACP -> 1
273+
}
274+
rebuildModelComboBox()
275+
} finally {
276+
isUpdating = false
277+
}
217278
}
218279

219-
private fun rebuildComboBoxWithAcp(agents: Map<String, cc.unitmesh.config.AcpAgentConfig>) {
220-
isComboUpdating = true
280+
private fun rebuildModelComboBox() {
281+
isUpdating = true
221282
try {
222283
modelComboBox.removeAllItems()
223-
acpAgentEntries = emptyList()
224-
225-
// Add LLM configs
226-
availableConfigs.forEach { modelComboBox.addItem(it.name) }
227-
228-
// Add ACP agents section
229-
if (agents.isNotEmpty()) {
230-
val separatorIndex = modelComboBox.itemCount
231-
modelComboBox.addItem(ACP_SEPARATOR)
232-
233-
val entries = mutableListOf<Pair<Int, String>>()
234-
agents.forEach { (key, config) ->
235-
val displayName = config.name.ifBlank { key }
236-
val idx = modelComboBox.itemCount
237-
modelComboBox.addItem("ACP: $displayName")
238-
entries.add(idx to key)
239-
}
240-
acpAgentEntries = entries
284+
acpAgentKeys = emptyList()
241285

242-
// Add "Configure ACP..." at the end
243-
modelComboBox.addItem(ACP_CONFIGURE)
286+
when (currentEngine) {
287+
IdeaEngine.AUTODEV -> {
288+
availableConfigs.forEach { modelComboBox.addItem(it.name) }
289+
println("Rebuilt model combo: AutoDev mode, ${availableConfigs.size} items")
290+
}
291+
IdeaEngine.ACP -> {
292+
val keys = mutableListOf<String>()
293+
acpAgents.forEach { (key, config) ->
294+
val displayName = config.name.ifBlank { key }
295+
modelComboBox.addItem(displayName)
296+
keys.add(key)
297+
}
298+
acpAgentKeys = keys
299+
modelComboBox.addItem(ACP_CONFIGURE)
300+
println("Rebuilt model combo: ACP mode, ${acpAgents.size} agents + configure")
301+
}
244302
}
245303
} finally {
246-
isComboUpdating = false
304+
isUpdating = false
247305
}
248306
}
249307

250308
fun setCurrentConfigName(name: String?) {
251-
if (name != null) {
252-
isComboUpdating = true
253-
try {
254-
val index = availableConfigs.indexOfFirst { it.name == name }
255-
if (index >= 0) {
256-
modelComboBox.selectedIndex = index
257-
}
258-
} finally {
259-
isComboUpdating = false
309+
if (name == null || currentEngine != IdeaEngine.AUTODEV) return
310+
isUpdating = true
311+
try {
312+
val index = availableConfigs.indexOfFirst { it.name == name }
313+
if (index >= 0) {
314+
modelComboBox.selectedIndex = index
260315
}
316+
} finally {
317+
isUpdating = false
261318
}
262319
}
263320

264-
/**
265-
* Select an ACP agent in the combo box by key.
266-
*/
267321
fun setCurrentAcpAgent(agentKey: String?) {
268-
if (agentKey == null) return
269-
isComboUpdating = true
322+
if (agentKey == null || currentEngine != IdeaEngine.ACP) return
323+
isUpdating = true
270324
try {
271-
val entry = acpAgentEntries.firstOrNull { it.second == agentKey }
272-
if (entry != null) {
273-
modelComboBox.selectedIndex = entry.first
325+
val index = acpAgentKeys.indexOf(agentKey)
326+
if (index >= 0) {
327+
modelComboBox.selectedIndex = index
274328
}
275329
} finally {
276-
isComboUpdating = false
330+
isUpdating = false
277331
}
278332
}
279333

@@ -303,10 +357,6 @@ class SwingBottomToolbar(
303357
refreshCopilotButton.isVisible = GithubCopilotDetector.isGithubCopilotConfigured()
304358
}
305359

306-
/**
307-
* Update the toolbar based on multimodal state.
308-
* Shows image count badge when images are attached.
309-
*/
310360
fun updateMultimodalState(state: IdeaMultimodalState) {
311361
imageCount = state.imageCount
312362

@@ -320,7 +370,6 @@ class SwingBottomToolbar(
320370
else -> "Attach Image (${state.imageCount} attached)"
321371
}
322372

323-
// Change icon color based on state
324373
imageButton.foreground = when {
325374
state.hasUploadError -> JBColor.RED
326375
state.allImagesUploaded -> JBColor(Color(0, 120, 212), Color(100, 180, 255))
@@ -332,13 +381,9 @@ class SwingBottomToolbar(
332381
imageButton.foreground = null
333382
}
334383

335-
// Update send button state based on multimodal state
336384
sendButton.isEnabled = state.canSend && !isProcessing
337385
}
338386

339-
/**
340-
* Enable or disable the image button.
341-
*/
342387
fun setImageEnabled(enabled: Boolean) {
343388
imageButton.isEnabled = enabled
344389
}
@@ -354,5 +399,8 @@ class SwingBottomToolbar(
354399
fun setOnSwitchToAutodev(callback: () -> Unit) {
355400
onSwitchToAutodev = callback
356401
}
357-
}
358402

403+
fun setOnSwitchToAcp(callback: () -> Unit) {
404+
onSwitchToAcp = callback
405+
}
406+
}

0 commit comments

Comments
 (0)