Skip to content

Commit b04be6e

Browse files
committed
feat(chat): extract LoadingSpinner component and enhance loading UI #379
Extracted loading animation into reusable LoadingSpinner component and improved loading message display. Also moved AutoDevChatPanel interface to separate file for better code organization.
1 parent d4a2243 commit b04be6e

File tree

4 files changed

+114
-74
lines changed

4 files changed

+114
-74
lines changed

core/src/main/kotlin/cc/unitmesh/devti/agent/custom/CustomAgentChatProcessor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class CustomAgentChatProcessor(val project: Project) {
3636

3737
selectedAgent.state = CustomAgentState.HANDLING
3838

39-
ui.showInitLoading()
39+
ui.showInitLoading("Custom Agent (${selectedAgent.name}) Working")
4040

4141
val response: Flow<String>? = customAgentExecutor.execute(request, selectedAgent, displayMessage)
4242
if (response == null) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package cc.unitmesh.devti.gui.chat
2+
3+
import cc.unitmesh.devti.agent.custom.model.CustomAgentConfig
4+
import com.intellij.openapi.project.Project
5+
import javax.swing.JProgressBar
6+
7+
interface AutoDevChatPanel {
8+
val progressBar: JProgressBar get() = JProgressBar()
9+
fun resetChatSession()
10+
11+
/**
12+
* Custom Agent Event
13+
*/
14+
fun resetAgent()
15+
fun hasSelectedCustomAgent(): Boolean
16+
fun getSelectedCustomAgent(): CustomAgentConfig
17+
fun selectAgent(config: CustomAgentConfig)
18+
19+
/**
20+
* Progress Bar
21+
*/
22+
fun hiddenProgressBar()
23+
fun showProgressBar()
24+
25+
/**
26+
* append custom view
27+
*/
28+
fun appendWebView(content: String, project: Project)
29+
}

core/src/main/kotlin/cc/unitmesh/devti/gui/chat/NormalChatCodingPanel.kt

Lines changed: 4 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import cc.unitmesh.devti.gui.chat.ui.AutoDevInputListener
1313
import cc.unitmesh.devti.gui.chat.ui.AutoDevInputSection
1414
import cc.unitmesh.devti.gui.chat.ui.AutoDevInputTrigger
1515
import cc.unitmesh.devti.gui.chat.view.MessageView
16+
import cc.unitmesh.devti.gui.component.LoadingSpinner
1617
import cc.unitmesh.devti.gui.toolbar.CopyAllMessagesAction
1718
import cc.unitmesh.devti.gui.toolbar.NewChatAction
1819
import cc.unitmesh.devti.provider.TextContextPrompter
@@ -32,7 +33,6 @@ import com.intellij.openapi.ui.DialogPanel
3233
import com.intellij.openapi.ui.NullableComponent
3334
import com.intellij.openapi.ui.SimpleToolWindowPanel
3435
import com.intellij.openapi.wm.IdeFocusManager
35-
import com.intellij.ui.JBColor
3636
import com.intellij.ui.JBColor.PanelBackground
3737
import com.intellij.ui.components.JBScrollPane
3838
import com.intellij.ui.components.panels.VerticalLayout
@@ -50,30 +50,6 @@ import java.awt.event.MouseAdapter
5050
import java.awt.event.MouseEvent
5151
import javax.swing.*
5252

53-
interface AutoDevChatPanel {
54-
val progressBar: JProgressBar get() = JProgressBar()
55-
fun resetChatSession()
56-
57-
/**
58-
* Custom Agent Event
59-
*/
60-
fun resetAgent()
61-
fun hasSelectedCustomAgent(): Boolean
62-
fun getSelectedCustomAgent(): CustomAgentConfig
63-
fun selectAgent(config: CustomAgentConfig)
64-
65-
/**
66-
* Progress Bar
67-
*/
68-
fun hiddenProgressBar()
69-
fun showProgressBar()
70-
71-
/**
72-
* append custom view
73-
*/
74-
fun appendWebView(content: String, project: Project)
75-
}
76-
7753
class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, val disposable: Disposable?) :
7854
SimpleToolWindowPanel(true, true), NullableComponent, AutoDevChatPanel {
7955
private val myList = JPanel(VerticalLayout(JBUI.scale(4)))
@@ -203,7 +179,7 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
203179
return messageView
204180
}
205181

206-
fun showInitLoading() {
182+
fun showInitLoading(string: String) {
207183
clearLoadingView()
208184
loadingPanel = JPanel(BorderLayout())
209185
loadingPanel!!.background = UIUtil.getListBackground()
@@ -212,51 +188,10 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
212188
val spinnerPanel = JPanel()
213189
spinnerPanel.isOpaque = false
214190

215-
val spinner = object: JPanel() {
216-
override fun paintComponent(g: Graphics) {
217-
super.paintComponent(g)
218-
219-
val g2d = g as Graphics2D
220-
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
221-
222-
val width = width.toFloat()
223-
val height = height.toFloat()
224-
val centerX = width / 2
225-
val centerY = height / 2
226-
val radius = minOf(width, height) / 2 - 5
227-
228-
val oldStroke = g2d.stroke
229-
g2d.stroke = BasicStroke(3f)
230-
231-
val step = if (loadingStep >= 12) 0 else loadingStep
232-
for (i in 0 until 12) {
233-
g2d.color = JBColor(
234-
Color(47, 99, 162, 255 - ((i + 12 - step) % 12) * 20),
235-
Color(88, 157, 246, 255 - ((i + 12 - step) % 12) * 20)
236-
)
237-
238-
val startAngle = i * 30
239-
g2d.drawArc(
240-
(centerX - radius).toInt(),
241-
(centerY - radius).toInt(),
242-
(radius * 2).toInt(),
243-
(radius * 2).toInt(),
244-
startAngle,
245-
15
246-
)
247-
}
248-
249-
g2d.stroke = oldStroke
250-
}
251-
252-
override fun getPreferredSize(): Dimension {
253-
return Dimension(40, 40)
254-
}
255-
}
256-
191+
val spinner = LoadingSpinner()
257192
spinnerPanel.add(spinner)
258193

259-
val loadingLabel = JLabel("Loading", SwingConstants.CENTER)
194+
val loadingLabel = JLabel(string, SwingConstants.CENTER)
260195
loadingPanel!!.add(spinnerPanel, BorderLayout.CENTER)
261196
loadingPanel!!.add(loadingLabel, BorderLayout.SOUTH)
262197

@@ -278,15 +213,12 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
278213
}
279214

280215
private fun clearLoadingView() {
281-
// Stop the timer first
282216
loadingTimer?.stop()
283217
loadingTimer = null
284218

285-
// Safely remove the loading panel if it exists
286219
if (loadingPanel != null) {
287220
val panelToRemove = loadingPanel
288221
runInEdt {
289-
// Check if the panel is still in the component hierarchy
290222
if (panelToRemove != null && panelToRemove.parent === myList) {
291223
try {
292224
myList.remove(panelToRemove)
@@ -453,4 +385,3 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
453385
inputSection.moveCursorToStart()
454386
}
455387
}
456-
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package cc.unitmesh.devti.gui.component
2+
3+
import com.intellij.ui.JBColor
4+
import java.awt.*
5+
import javax.swing.JPanel
6+
import javax.swing.Timer
7+
8+
9+
class LoadingSpinner : JPanel() {
10+
private var step = 0
11+
private var timer: Timer? = null
12+
13+
init {
14+
isOpaque = false
15+
startAnimation()
16+
}
17+
18+
override fun paintComponent(g: Graphics) {
19+
super.paintComponent(g)
20+
21+
val g2d = g as Graphics2D
22+
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
23+
24+
val width = width.toFloat()
25+
val height = height.toFloat()
26+
val centerX = width / 2
27+
val centerY = height / 2
28+
val radius = minOf(width, height) / 2 - 5
29+
30+
val oldStroke = g2d.stroke
31+
g2d.stroke = BasicStroke(3f)
32+
33+
val currentStep = if (step >= 12) 0 else step
34+
for (i in 0 until 12) {
35+
g2d.color = JBColor(
36+
Color(47, 99, 162, 255 - ((i + 12 - currentStep) % 12) * 20),
37+
Color(88, 157, 246, 255 - ((i + 12 - currentStep) % 12) * 20)
38+
)
39+
40+
val startAngle = i * 30
41+
g2d.drawArc(
42+
(centerX - radius).toInt(),
43+
(centerY - radius).toInt(),
44+
(radius * 2).toInt(),
45+
(radius * 2).toInt(),
46+
startAngle,
47+
15
48+
)
49+
}
50+
51+
g2d.stroke = oldStroke
52+
}
53+
54+
override fun getPreferredSize(): Dimension {
55+
return Dimension(40, 40)
56+
}
57+
58+
fun startAnimation() {
59+
stopAnimation()
60+
61+
step = 0
62+
timer = Timer(100, null) // 100ms interval for smooth animation
63+
timer!!.addActionListener {
64+
step = (step + 1) % 12
65+
repaint()
66+
}
67+
68+
timer!!.start()
69+
}
70+
71+
fun stopAnimation() {
72+
timer?.stop()
73+
timer = null
74+
}
75+
76+
override fun removeNotify() {
77+
stopAnimation()
78+
super.removeNotify()
79+
}
80+
}

0 commit comments

Comments
 (0)