Skip to content

Commit 178a605

Browse files
committed
feat(gui): add token usage progress bar to chat panel
Add visual progress bar showing token usage ratio against model's context window limit, with color-coded indicators (yellow/orange/red) for usage thresholds and detailed usage statistics display.
1 parent 9478115 commit 178a605

File tree

1 file changed

+109
-2
lines changed

1 file changed

+109
-2
lines changed

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

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package cc.unitmesh.devti.gui.chat.ui
33
import cc.unitmesh.devti.llm2.TokenUsageEvent
44
import cc.unitmesh.devti.llm2.TokenUsageListener
55
import cc.unitmesh.devti.llms.custom.Usage
6+
import cc.unitmesh.devti.settings.AutoDevSettingsState
7+
import cc.unitmesh.devti.settings.model.LLMModelManager
68
import com.intellij.openapi.application.ApplicationManager
9+
import com.intellij.openapi.components.service
710
import com.intellij.openapi.project.Project
811
import com.intellij.ui.components.JBLabel
912
import com.intellij.util.ui.JBUI
@@ -12,8 +15,11 @@ import com.intellij.util.ui.components.BorderLayoutPanel
1215
import java.awt.BorderLayout
1316
import java.awt.Color
1417
import java.awt.Font
18+
import java.awt.GridBagConstraints
19+
import java.awt.GridBagLayout
1520
import javax.swing.Box
1621
import javax.swing.JPanel
22+
import javax.swing.JProgressBar
1723
import javax.swing.SwingConstants
1824

1925
/**
@@ -25,9 +31,12 @@ class TokenUsagePanel(private val project: Project) : BorderLayoutPanel() {
2531
private val completionTokensLabel = JBLabel("0", SwingConstants.RIGHT)
2632
private val totalTokensLabel = JBLabel("0", SwingConstants.RIGHT)
2733
private val modelLabel = JBLabel("", SwingConstants.LEFT)
34+
private val progressBar = JProgressBar(0, 100)
35+
private val usageRatioLabel = JBLabel("", SwingConstants.CENTER)
2836

2937
private var currentUsage = Usage()
3038
private var currentModel: String? = null
39+
private var maxContextWindowTokens: Long = 0
3140

3241
init {
3342
setupUI()
@@ -38,13 +47,55 @@ class TokenUsagePanel(private val project: Project) : BorderLayoutPanel() {
3847
isOpaque = false
3948
border = JBUI.Borders.empty(4, 8)
4049

50+
// Setup progress bar
51+
progressBar.apply {
52+
isStringPainted = false
53+
preferredSize = java.awt.Dimension(150, 16)
54+
minimumSize = java.awt.Dimension(100, 16)
55+
font = font.deriveFont(Font.PLAIN, 10f)
56+
isOpaque = false
57+
}
58+
59+
// Setup usage ratio label
60+
usageRatioLabel.apply {
61+
font = font.deriveFont(Font.PLAIN, 10f)
62+
foreground = UIUtil.getContextHelpForeground()
63+
}
64+
65+
// Create main layout
66+
val mainPanel = JPanel(GridBagLayout())
67+
mainPanel.isOpaque = false
68+
69+
val gbc = GridBagConstraints()
70+
71+
// Top row: Model info and progress bar
72+
gbc.gridx = 0
73+
gbc.gridy = 0
74+
gbc.anchor = GridBagConstraints.WEST
75+
gbc.fill = GridBagConstraints.NONE
4176
// Create left panel for model info
4277
val leftPanel = JPanel(BorderLayout())
4378
leftPanel.isOpaque = false
4479
modelLabel.font = modelLabel.font.deriveFont(Font.PLAIN, 11f)
4580
modelLabel.foreground = UIUtil.getContextHelpForeground()
4681
leftPanel.add(modelLabel, BorderLayout.WEST)
4782

83+
mainPanel.add(leftPanel, gbc)
84+
85+
// Progress bar and ratio in the middle
86+
gbc.gridx = 1
87+
gbc.weightx = 1.0
88+
gbc.fill = GridBagConstraints.HORIZONTAL
89+
gbc.insets = JBUI.insets(0, 8, 0, 8)
90+
91+
val progressPanel = JPanel(BorderLayout())
92+
progressPanel.isOpaque = false
93+
progressPanel.add(progressBar, BorderLayout.CENTER)
94+
progressPanel.add(usageRatioLabel, BorderLayout.SOUTH)
95+
96+
mainPanel.add(progressPanel, gbc)
97+
98+
// Bottom row: Token stats
4899
// Create right panel for token stats
49100
val rightPanel = JPanel()
50101
rightPanel.isOpaque = false
@@ -77,9 +128,14 @@ class TokenUsagePanel(private val project: Project) : BorderLayoutPanel() {
77128
})
78129
rightPanel.add(totalTokensLabel)
79130

131+
gbc.gridx = 2
132+
gbc.weightx = 0.0
133+
gbc.fill = GridBagConstraints.NONE
134+
gbc.insets = JBUI.insets(0, 0, 0, 0)
135+
mainPanel.add(rightPanel, gbc)
136+
80137
// Add panels to main layout
81-
addToLeft(leftPanel)
82-
addToRight(rightPanel)
138+
addToCenter(mainPanel)
83139

84140
// Initially hidden
85141
isVisible = false
@@ -99,10 +155,17 @@ class TokenUsagePanel(private val project: Project) : BorderLayoutPanel() {
99155
currentUsage = event.usage
100156
currentModel = event.model
101157

158+
// Get max tokens for current model
159+
updateMaxTokens()
160+
161+
// Update token displays
102162
promptTokensLabel.text = formatTokenCount(event.usage.promptTokens ?: 0)
103163
completionTokensLabel.text = formatTokenCount(event.usage.completionTokens ?: 0)
104164
totalTokensLabel.text = formatTokenCount(event.usage.totalTokens ?: 0)
105165

166+
// Update progress bar
167+
updateProgressBar(event.usage.totalTokens ?: 0)
168+
106169
if (!event.model.isNullOrBlank()) {
107170
modelLabel.text = "Model: ${event.model}"
108171
}
@@ -114,6 +177,45 @@ class TokenUsagePanel(private val project: Project) : BorderLayoutPanel() {
114177
}
115178
}
116179

180+
private fun updateMaxTokens() {
181+
try {
182+
val settings = AutoDevSettingsState.getInstance()
183+
val modelManager = LLMModelManager(project, settings) {}
184+
val limits = modelManager.getUsedMaxToken()
185+
maxContextWindowTokens = limits.maxContextWindowTokens?.toLong() ?: 0
186+
} catch (e: Exception) {
187+
// Fallback to default if unable to get limits
188+
maxContextWindowTokens = 4096
189+
}
190+
}
191+
192+
private fun updateProgressBar(totalTokens: Long) {
193+
if (maxContextWindowTokens <= 0) {
194+
progressBar.isVisible = false
195+
usageRatioLabel.isVisible = false
196+
return
197+
}
198+
199+
val usageRatio = (totalTokens.toDouble() / maxContextWindowTokens * 100).toInt()
200+
progressBar.value = usageRatio.coerceIn(0, 100)
201+
202+
// Update color based on usage ratio
203+
progressBar.foreground = when {
204+
usageRatio >= 90 -> Color.RED
205+
usageRatio >= 75 -> Color.ORANGE
206+
usageRatio >= 50 -> Color.YELLOW
207+
else -> UIUtil.getPanelBackground().brighter()
208+
}
209+
210+
// Update ratio label
211+
usageRatioLabel.text = "${formatTokenCount(totalTokens)}/${formatTokenCount(maxContextWindowTokens)} (${usageRatio}%)"
212+
213+
progressBar.isVisible = true
214+
usageRatioLabel.isVisible = true
215+
216+
progressBar.toolTipText = "Token usage: $usageRatio% of context window"
217+
}
218+
117219
private fun formatTokenCount(count: Long): String {
118220
return when {
119221
count >= 1_000_000 -> String.format("%.1fM", count / 1_000_000.0)
@@ -129,10 +231,15 @@ class TokenUsagePanel(private val project: Project) : BorderLayoutPanel() {
129231
ApplicationManager.getApplication().invokeLater {
130232
currentUsage = Usage()
131233
currentModel = null
234+
maxContextWindowTokens = 0
132235
promptTokensLabel.text = "0"
133236
completionTokensLabel.text = "0"
134237
totalTokensLabel.text = "0"
135238
modelLabel.text = ""
239+
progressBar.value = 0
240+
progressBar.isVisible = false
241+
usageRatioLabel.text = ""
242+
usageRatioLabel.isVisible = false
136243
isVisible = false
137244
revalidate()
138245
repaint()

0 commit comments

Comments
 (0)