Skip to content

Commit d4a2243

Browse files
committed
feat(chat): enhance loading animation and cleanup UI
#379 Add smooth loading animation with spinner and improve UI cleanup. Remove unused imports and refactor loading state management. Clear loading view when resetting chat session.
1 parent 8dbd22e commit d4a2243

File tree

3 files changed

+110
-7
lines changed

3 files changed

+110
-7
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.showLoading()
39+
ui.showInitLoading()
4040

4141
val response: Flow<String>? = customAgentExecutor.execute(request, selectedAgent, displayMessage)
4242
if (response == null) {

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

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import com.intellij.openapi.ui.DialogPanel
3232
import com.intellij.openapi.ui.NullableComponent
3333
import com.intellij.openapi.ui.SimpleToolWindowPanel
3434
import com.intellij.openapi.wm.IdeFocusManager
35+
import com.intellij.ui.JBColor
3536
import com.intellij.ui.JBColor.PanelBackground
3637
import com.intellij.ui.components.JBScrollPane
3738
import com.intellij.ui.components.panels.VerticalLayout
@@ -44,6 +45,7 @@ import kotlinx.coroutines.flow.Flow
4445
import kotlinx.coroutines.flow.buffer
4546
import kotlinx.coroutines.runBlocking
4647
import kotlinx.coroutines.withContext
48+
import java.awt.*
4749
import java.awt.event.MouseAdapter
4850
import java.awt.event.MouseEvent
4951
import javax.swing.*
@@ -80,6 +82,15 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
8082
private var panelContent: DialogPanel
8183
private val myScrollPane: JBScrollPane
8284
private val delaySeconds: String get() = AutoDevSettingsState.getInstance().delaySeconds
85+
86+
// Add a field to hold the loading panel
87+
private var loadingPanel: JPanel? = null
88+
89+
// Add a field to track if loading animation timer is running
90+
private var loadingTimer: Timer? = null
91+
92+
// Add a field to track loading animation step
93+
private var loadingStep = 0
8394

8495
init {
8596
focusMouseListener = object : MouseAdapter() {
@@ -176,6 +187,8 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
176187
* Add a message to the chat panel and update ui
177188
*/
178189
fun addMessage(message: String, isMe: Boolean = false, displayPrompt: String = ""): MessageView {
190+
clearLoadingView()
191+
179192
val role = if (isMe) ChatRole.User else ChatRole.Assistant
180193
val displayText = displayPrompt.ifEmpty { message }
181194

@@ -190,8 +203,101 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
190203
return messageView
191204
}
192205

193-
fun showLoading() {
194-
206+
fun showInitLoading() {
207+
clearLoadingView()
208+
loadingPanel = JPanel(BorderLayout())
209+
loadingPanel!!.background = UIUtil.getListBackground()
210+
loadingPanel!!.border = JBUI.Borders.empty(10)
211+
212+
val spinnerPanel = JPanel()
213+
spinnerPanel.isOpaque = false
214+
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+
257+
spinnerPanel.add(spinner)
258+
259+
val loadingLabel = JLabel("Loading", SwingConstants.CENTER)
260+
loadingPanel!!.add(spinnerPanel, BorderLayout.CENTER)
261+
loadingPanel!!.add(loadingLabel, BorderLayout.SOUTH)
262+
263+
runInEdt {
264+
myList.add(loadingPanel!!)
265+
updateUI()
266+
scrollToBottom()
267+
}
268+
269+
loadingStep = 0
270+
271+
loadingTimer = Timer(100, null) // 100ms interval for smooth animation
272+
loadingTimer!!.addActionListener {
273+
loadingStep = (loadingStep + 1) % 12
274+
spinner.repaint()
275+
}
276+
277+
loadingTimer!!.start()
278+
}
279+
280+
private fun clearLoadingView() {
281+
// Stop the timer first
282+
loadingTimer?.stop()
283+
loadingTimer = null
284+
285+
// Safely remove the loading panel if it exists
286+
if (loadingPanel != null) {
287+
val panelToRemove = loadingPanel
288+
runInEdt {
289+
// Check if the panel is still in the component hierarchy
290+
if (panelToRemove != null && panelToRemove.parent === myList) {
291+
try {
292+
myList.remove(panelToRemove)
293+
updateUI()
294+
} catch (e: Exception) {
295+
// Log or handle any exceptions that might occur during removal
296+
}
297+
}
298+
}
299+
loadingPanel = null
300+
}
195301
}
196302

197303
fun getHistoryMessages(): List<LlmMsg.ChatMessage> {
@@ -297,6 +403,7 @@ class NormalChatCodingPanel(private val chatCodingService: ChatCodingService, va
297403
override fun resetChatSession() {
298404
chatCodingService.stop()
299405
chatCodingService.clearSession()
406+
clearLoadingView() // Clear loading view when resetting chat
300407
myList.removeAll()
301408
this.hiddenProgressBar()
302409
this.resetAgent()

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import cc.unitmesh.devti.sketch.ui.code.CodeHighlightSketch
1111
import cc.unitmesh.devti.util.parser.CodeFence
1212
import com.intellij.icons.AllIcons
1313
import com.intellij.openapi.actionSystem.ActionManager
14-
import com.intellij.openapi.actionSystem.ActionToolbar
1514
import com.intellij.openapi.actionSystem.AnAction
1615
import com.intellij.openapi.actionSystem.AnActionEvent
1716
import com.intellij.openapi.actionSystem.DefaultActionGroup
@@ -23,18 +22,15 @@ import com.intellij.openapi.ui.DialogPanel
2322
import com.intellij.ui.JBColor
2423
import com.intellij.ui.components.JBPanel
2524
import com.intellij.ui.components.panels.VerticalLayout
26-
import com.intellij.ui.components.panels.Wrapper
2725
import com.intellij.ui.dsl.builder.panel
2826
import com.intellij.util.ui.JBFont
2927
import com.intellij.util.ui.JBUI
3028
import com.intellij.util.ui.UIUtil
3129
import java.awt.BorderLayout
32-
import java.awt.Color
3330
import java.awt.Toolkit
3431
import java.awt.datatransfer.StringSelection
3532
import javax.swing.JLabel
3633
import javax.swing.JPanel
37-
import javax.swing.border.EmptyBorder
3834

3935
class MessageView(val project: Project, val message: String, val role: ChatRole, private var displayText: String) :
4036
JBPanel<MessageView>() {

0 commit comments

Comments
 (0)