11package cc.unitmesh.devins.idea.editor
22
3+ import cc.unitmesh.devins.idea.toolwindow.IdeaEngine
34import cc.unitmesh.devti.llm2.GithubCopilotDetector
45import cc.unitmesh.devins.idea.editor.multimodal.IdeaMultimodalState
56import 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 */
2228class 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