11package cc.unitmesh.devti.gui.chat.ui
22
3- import cc.unitmesh.devti.llm2.TokenUsageEvent
4- import cc.unitmesh.devti.llm2.TokenUsageListener
3+ import cc.unitmesh.devti.gui.chat.ui.TokenUsageViewModel.TokenUsageData
54import cc.unitmesh.devti.llms.custom.Usage
6- import cc.unitmesh.devti.settings.AutoDevSettingsState
7- import cc.unitmesh.devti.settings.model.LLMModelManager
8- import com.intellij.openapi.application.ApplicationManager
95import com.intellij.openapi.project.Project
106import com.intellij.ui.JBColor
117import com.intellij.ui.components.JBLabel
@@ -22,177 +18,202 @@ import javax.swing.SwingConstants
2218
2319/* *
2420 * Panel that displays token usage statistics for the current session
21+ * Refactored to separate UI concerns from business logic
2522 */
2623class TokenUsagePanel (private val project : Project ) : BorderLayoutPanel() {
27- private val modelLabel = JBLabel (" " , SwingConstants .LEFT )
28- private val progressBar = JProgressBar (0 , 100 )
29- private val usageRatioLabel = JBLabel (" " , SwingConstants .CENTER )
30-
31- private var currentUsage = Usage ()
32- private var currentModel: String? = null
33- private var maxContextWindowTokens: Long = 0
34-
24+ private val uiComponents = TokenUsageUIComponents ()
25+ private val viewModel = TokenUsageViewModel (project)
26+
27+ private var currentData: TokenUsageData ? = null
28+
3529 init {
3630 setupUI()
37- setupTokenUsageListener ()
31+ setupViewModel ()
3832 }
39-
33+
4034 private fun setupUI () {
4135 isOpaque = false
4236 border = JBUI .Borders .empty(4 , 8 )
43-
44- progressBar.apply {
45- isStringPainted = false
46- preferredSize = java.awt.Dimension (150 , 8 )
47- minimumSize = java.awt.Dimension (100 , 8 )
48- font = font.deriveFont(Font .PLAIN , 10f )
49- isOpaque = false
50- }
51-
52- usageRatioLabel.apply {
53- font = font.deriveFont(Font .PLAIN , 10f )
54- foreground = UIUtil .getContextHelpForeground()
55- }
56-
37+
38+ val mainPanel = createMainPanel()
39+ addToCenter(mainPanel)
40+
41+ isVisible = false
42+ }
43+
44+ private fun createMainPanel (): JPanel {
5745 val mainPanel = JPanel (GridBagLayout ())
5846 mainPanel.isOpaque = false
59-
47+
6048 val gbc = GridBagConstraints ()
61-
49+
50+ // Model label (left)
6251 gbc.gridx = 0
6352 gbc.gridy = 0
6453 gbc.anchor = GridBagConstraints .WEST
6554 gbc.fill = GridBagConstraints .NONE
66- // Create left panel for model info
67- val leftPanel = JPanel (BorderLayout ())
68- leftPanel.isOpaque = false
69- modelLabel.font = modelLabel.font.deriveFont(Font .PLAIN , 11f )
70- modelLabel.foreground = UIUtil .getContextHelpForeground()
71- leftPanel.add(modelLabel, BorderLayout .WEST )
72-
73- mainPanel.add(leftPanel, gbc)
74-
75- // Progress bar and ratio in the middle
55+ mainPanel.add(uiComponents.createModelLabelPanel(), gbc)
56+
57+ // Progress bar (center)
7658 gbc.gridx = 1
7759 gbc.weightx = 0.9
7860 gbc.fill = GridBagConstraints .HORIZONTAL
7961 gbc.insets = JBUI .insets(0 , 8 )
80-
81- val progressPanel = JPanel (BorderLayout ())
82- progressPanel.isOpaque = false
83- progressPanel.add(progressBar, BorderLayout .CENTER )
84-
85- mainPanel.add(progressPanel, gbc)
86-
87- // Right panel for token count display (10% width)
88- val rightPanel = JPanel ()
89- rightPanel.isOpaque = false
90-
91- // Add usage ratio label to the right panel
92- usageRatioLabel.horizontalAlignment = SwingConstants .RIGHT
93- rightPanel.add(usageRatioLabel)
94-
62+ mainPanel.add(uiComponents.createProgressPanel(), gbc)
63+
64+ // Usage ratio label (right)
9565 gbc.gridx = 2
9666 gbc.weightx = 0.1
9767 gbc.fill = GridBagConstraints .NONE
9868 gbc.anchor = GridBagConstraints .EAST
9969 gbc.insets = JBUI .emptyInsets()
100- mainPanel.add(rightPanel, gbc)
101-
102- // Add panels to main layout
103- addToCenter(mainPanel)
104-
105- // Initially hidden
106- isVisible = false
70+ mainPanel.add(uiComponents.createUsageRatioPanel(), gbc)
71+
72+ return mainPanel
10773 }
108-
109- private fun setupTokenUsageListener () {
110- val messageBus = ApplicationManager .getApplication().messageBus
111- messageBus.connect().subscribe(TokenUsageListener .TOPIC , object : TokenUsageListener {
112- override fun onTokenUsage (event : TokenUsageEvent ) {
113- updateTokenUsage(event)
114- }
115- })
116- }
117-
118- private fun updateTokenUsage (event : TokenUsageEvent ) {
119- ApplicationManager .getApplication().invokeLater {
120- currentUsage = event.usage
121- currentModel = event.model
122-
123- updateMaxTokens()
124- updateProgressBar(event.usage.totalTokens ? : 0 )
125-
126- if (! event.model.isNullOrBlank()) {
127- modelLabel.text = " Model: ${event.model} "
128- }
129-
130- isVisible = true
131- revalidate()
132- repaint()
74+
75+ private fun setupViewModel () {
76+ viewModel.setOnTokenUsageUpdated { data ->
77+ updateUI(data)
13378 }
13479 }
135-
136- private fun updateMaxTokens ( ) {
137- try {
138- val settings = AutoDevSettingsState .getInstance()
139- val modelManager = LLMModelManager (project, settings) {}
140- val limits = modelManager.getUsedMaxToken()
141- maxContextWindowTokens = limits.maxContextWindowTokens?.toLong() ? : 0
142- } catch (e : Exception ) {
143- maxContextWindowTokens = 4096
80+
81+ private fun updateUI ( data : TokenUsageData ) {
82+ currentData = data
83+
84+ // Update model label
85+ val modelText = if ( ! data.model.isNullOrBlank()) {
86+ " Model: ${data.model} "
87+ } else {
88+ " "
14489 }
145- }
146-
147- private fun updateProgressBar (totalTokens : Long ) {
148- if (maxContextWindowTokens <= 0 ) {
149- progressBar.isVisible = false
150- usageRatioLabel.isVisible = false
151- return
90+ uiComponents.updateModelLabel(modelText)
91+
92+ // Update progress bar and ratio
93+ if (data.maxContextWindowTokens > 0 ) {
94+ val totalTokens = data.usage.totalTokens
95+ val usageRatio = (data.usageRatio * 100 ).toInt().coerceIn(0 , 100 )
96+
97+ uiComponents.updateProgressBar(usageRatio, createProgressBarColor(usageRatio))
98+ uiComponents.updateUsageRatioLabel(
99+ createUsageRatioText(
100+ totalTokens,
101+ data.maxContextWindowTokens,
102+ usageRatio
103+ )
104+ )
105+ uiComponents.setProgressBarTooltip(" Token usage: $usageRatio % of context window" )
106+ uiComponents.setProgressComponentsVisible(true )
107+ } else {
108+ uiComponents.setProgressComponentsVisible(false )
152109 }
153-
154- val usageRatio = (totalTokens.toDouble() / maxContextWindowTokens * 100 ).toInt()
155- progressBar.value = usageRatio.coerceIn(0 , 100 )
156-
157- progressBar.foreground = when {
110+
111+ // Update panel visibility
112+ isVisible = data.isVisible
113+ revalidate()
114+ repaint()
115+ }
116+
117+ private fun createProgressBarColor (usageRatio : Int ): JBColor {
118+ return when {
158119 usageRatio >= 90 -> JBColor .RED
159120 usageRatio >= 75 -> JBColor .ORANGE
160121 usageRatio >= 50 -> JBColor .YELLOW
161122 usageRatio >= 25 -> JBColor .GREEN
162- else -> UIUtil .getPanelBackground().brighter()
163- }
164-
165- usageRatioLabel.text = " ${formatTokenCount(totalTokens)} /${formatTokenCount(maxContextWindowTokens)} (${usageRatio} %)"
166- progressBar.isVisible = true
167- usageRatioLabel.isVisible = true
168- progressBar.toolTipText = " Token usage: $usageRatio % of context window"
169- }
170-
171- private fun formatTokenCount (count : Long ): String {
172- return when {
173- count >= 1_000_000 -> String .format(" %.1fM" , count / 1_000_000.0 )
174- count >= 1_000 -> String .format(" %.1fK" , count / 1_000.0 )
175- else -> count.toString()
123+ else -> UIUtil .getPanelBackground() as JBColor
176124 }
177125 }
178126
127+ private fun createUsageRatioText (totalTokens : Long , maxTokens : Long , usageRatio : Int ): String {
128+ return " ${TokenUsageViewModel .formatTokenCount(totalTokens)} /${TokenUsageViewModel .formatTokenCount(maxTokens)} (${usageRatio} %)"
129+ }
130+
179131 fun reset () {
180- ApplicationManager .getApplication().invokeLater {
181- currentUsage = Usage ()
182- currentModel = null
183- maxContextWindowTokens = 0
184- modelLabel.text = " "
185- progressBar.value = 0
186- progressBar.isVisible = false
187- usageRatioLabel.text = " "
188- usageRatioLabel.isVisible = false
189- isVisible = false
190- revalidate()
191- repaint()
132+ viewModel.reset()
133+ }
134+
135+ fun dispose () {
136+ viewModel.dispose()
137+ }
138+ }
139+
140+ /* *
141+ * Encapsulates UI component creation and management
142+ * Separates UI component logic from main panel logic
143+ */
144+ private class TokenUsageUIComponents {
145+ val modelLabel = JBLabel (" " , SwingConstants .LEFT )
146+ val progressBar = JProgressBar (0 , 100 )
147+ val usageRatioLabel = JBLabel (" " , SwingConstants .CENTER )
148+
149+ init {
150+ setupComponents()
151+ }
152+
153+ private fun setupComponents () {
154+ // Setup progress bar
155+ progressBar.apply {
156+ isStringPainted = false
157+ preferredSize = java.awt.Dimension (150 , 8 )
158+ minimumSize = java.awt.Dimension (100 , 8 )
159+ font = font.deriveFont(Font .PLAIN , 10f )
160+ isOpaque = false
161+ }
162+
163+ // Setup usage ratio label
164+ usageRatioLabel.apply {
165+ font = font.deriveFont(Font .PLAIN , 10f )
166+ foreground = UIUtil .getContextHelpForeground()
167+ horizontalAlignment = SwingConstants .RIGHT
168+ }
169+
170+ // Setup model label
171+ modelLabel.apply {
172+ font = font.deriveFont(Font .PLAIN , 11f )
173+ foreground = UIUtil .getContextHelpForeground()
192174 }
193175 }
194176
195- fun getCurrentUsage (): Usage = currentUsage
177+ fun createModelLabelPanel (): JPanel {
178+ val leftPanel = JPanel (BorderLayout ())
179+ leftPanel.isOpaque = false
180+ leftPanel.add(modelLabel, BorderLayout .WEST )
181+ return leftPanel
182+ }
183+
184+ fun createProgressPanel (): JPanel {
185+ val progressPanel = JPanel (BorderLayout ())
186+ progressPanel.isOpaque = false
187+ progressPanel.add(progressBar, BorderLayout .CENTER )
188+ return progressPanel
189+ }
190+
191+ fun createUsageRatioPanel (): JPanel {
192+ val rightPanel = JPanel ()
193+ rightPanel.isOpaque = false
194+ rightPanel.add(usageRatioLabel)
195+ return rightPanel
196+ }
197+
198+ fun updateModelLabel (text : String ) {
199+ modelLabel.text = text
200+ }
201+
202+ fun updateProgressBar (value : Int , color : JBColor ) {
203+ progressBar.value = value
204+ progressBar.foreground = color
205+ }
206+
207+ fun updateUsageRatioLabel (text : String ) {
208+ usageRatioLabel.text = text
209+ }
196210
197- fun getCurrentModel (): String? = currentModel
211+ fun setProgressBarTooltip (tooltip : String ) {
212+ progressBar.toolTipText = tooltip
213+ }
214+
215+ fun setProgressComponentsVisible (visible : Boolean ) {
216+ progressBar.isVisible = visible
217+ usageRatioLabel.isVisible = visible
218+ }
198219}
0 commit comments