1
1
package cc.unitmesh.devti.gui.chat.ui
2
2
3
- import cc.unitmesh.devti.llm2.TokenUsageEvent
4
- import cc.unitmesh.devti.llm2.TokenUsageListener
3
+ import cc.unitmesh.devti.gui.chat.ui.TokenUsageViewModel.TokenUsageData
5
4
import 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
9
5
import com.intellij.openapi.project.Project
10
6
import com.intellij.ui.JBColor
11
7
import com.intellij.ui.components.JBLabel
@@ -22,177 +18,202 @@ import javax.swing.SwingConstants
22
18
23
19
/* *
24
20
* Panel that displays token usage statistics for the current session
21
+ * Refactored to separate UI concerns from business logic
25
22
*/
26
23
class 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
+
35
29
init {
36
30
setupUI()
37
- setupTokenUsageListener ()
31
+ setupViewModel ()
38
32
}
39
-
33
+
40
34
private fun setupUI () {
41
35
isOpaque = false
42
36
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 {
57
45
val mainPanel = JPanel (GridBagLayout ())
58
46
mainPanel.isOpaque = false
59
-
47
+
60
48
val gbc = GridBagConstraints ()
61
-
49
+
50
+ // Model label (left)
62
51
gbc.gridx = 0
63
52
gbc.gridy = 0
64
53
gbc.anchor = GridBagConstraints .WEST
65
54
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)
76
58
gbc.gridx = 1
77
59
gbc.weightx = 0.9
78
60
gbc.fill = GridBagConstraints .HORIZONTAL
79
61
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)
95
65
gbc.gridx = 2
96
66
gbc.weightx = 0.1
97
67
gbc.fill = GridBagConstraints .NONE
98
68
gbc.anchor = GridBagConstraints .EAST
99
69
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
107
73
}
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)
133
78
}
134
79
}
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
+ " "
144
89
}
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 )
152
109
}
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 {
158
119
usageRatio >= 90 -> JBColor .RED
159
120
usageRatio >= 75 -> JBColor .ORANGE
160
121
usageRatio >= 50 -> JBColor .YELLOW
161
122
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
176
124
}
177
125
}
178
126
127
+ private fun createUsageRatioText (totalTokens : Long , maxTokens : Long , usageRatio : Int ): String {
128
+ return " ${TokenUsageViewModel .formatTokenCount(totalTokens)} /${TokenUsageViewModel .formatTokenCount(maxTokens)} (${usageRatio} %)"
129
+ }
130
+
179
131
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()
192
174
}
193
175
}
194
176
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
+ }
196
210
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
+ }
198
219
}
0 commit comments