Skip to content

Commit c75ab9d

Browse files
authored
chat - status tweaks (microsoft#242110)
1 parent 7e5536d commit c75ab9d

File tree

3 files changed

+121
-40
lines changed

3 files changed

+121
-40
lines changed

build/lib/stylelint/vscode-known-variables.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,11 @@
353353
"--vscode-foreground",
354354
"--vscode-gauge-background",
355355
"--vscode-gauge-border",
356+
"--vscode-gauge-errorBackground",
357+
"--vscode-gauge-errorForeground",
356358
"--vscode-gauge-foreground",
359+
"--vscode-gauge-warningBackground",
360+
"--vscode-gauge-warningForeground",
357361
"--vscode-icon-foreground",
358362
"--vscode-inlineChat-background",
359363
"--vscode-inlineChat-border",

src/vs/workbench/contrib/chat/browser/chatStatus.ts

Lines changed: 92 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import './media/chatStatus.css';
77
import { safeIntl } from '../../../../base/common/date.js';
8-
import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
8+
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
99
import { language, OS } from '../../../../base/common/platform.js';
1010
import { localize } from '../../../../nls.js';
1111
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
@@ -17,16 +17,15 @@ import { IChatQuotasService } from '../common/chatQuotasService.js';
1717
import { quotaToButtonMessage, OPEN_CHAT_QUOTA_EXCEEDED_DIALOG, CHAT_SETUP_ACTION_LABEL, TOGGLE_CHAT_ACTION_ID, CHAT_OPEN_ACTION_ID } from './actions/chatActions.js';
1818
import { $, addDisposableListener, append, EventHelper, EventLike, EventType } from '../../../../base/browser/dom.js';
1919
import { IChatEntitlementsService } from '../common/chatEntitlementsService.js';
20-
import { CancellationToken } from '../../../../base/common/cancellation.js';
20+
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
2121
import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
2222
import { defaultCheckboxStyles, defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js';
2323
import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js';
2424
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
2525
import { Command } from '../../../../editor/common/languages.js';
2626
import { ICommandService } from '../../../../platform/commands/common/commands.js';
2727
import { Lazy } from '../../../../base/common/lazy.js';
28-
import { contrastBorder, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js';
29-
import { ACTIVITY_BAR_BADGE_BACKGROUND } from '../../../common/theme.js';
28+
import { contrastBorder, inputValidationErrorBorder, inputValidationInfoBorder, inputValidationWarningBorder, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js';
3029
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
3130
import { coalesce } from '../../../../base/common/arrays.js';
3231
import { CTX_INLINE_CHAT_POSSIBLE } from '../../inlineChat/common/inlineChat.js';
@@ -38,9 +37,11 @@ import { Gesture, EventType as TouchEventType } from '../../../../base/browser/t
3837
import { IEditorService } from '../../../services/editor/common/editorService.js';
3938
import { IProductService } from '../../../../platform/product/common/productService.js';
4039

40+
//#region --- colors
41+
4142
const gaugeBackground = registerColor('gauge.background', {
42-
dark: ACTIVITY_BAR_BADGE_BACKGROUND,
43-
light: ACTIVITY_BAR_BADGE_BACKGROUND,
43+
dark: inputValidationInfoBorder,
44+
light: inputValidationInfoBorder,
4445
hcDark: contrastBorder,
4546
hcLight: contrastBorder
4647
}, localize('gaugeBackground', "Gauge background color."));
@@ -59,6 +60,36 @@ registerColor('gauge.border', {
5960
hcLight: contrastBorder
6061
}, localize('gaugeBorder', "Gauge border color."));
6162

63+
const gaugeWarningBackground = registerColor('gauge.warningBackground', {
64+
dark: inputValidationWarningBorder,
65+
light: inputValidationWarningBorder,
66+
hcDark: contrastBorder,
67+
hcLight: contrastBorder
68+
}, localize('gaugeWarningBackground', "Gauge warning background color."));
69+
70+
registerColor('gauge.warningForeground', {
71+
dark: transparent(gaugeWarningBackground, 0.3),
72+
light: transparent(gaugeWarningBackground, 0.3),
73+
hcDark: Color.white,
74+
hcLight: Color.white
75+
}, localize('gaugeWarningForeground', "Gauge warning foreground color."));
76+
77+
const gaugeErrorBackground = registerColor('gauge.errorBackground', {
78+
dark: inputValidationErrorBorder,
79+
light: inputValidationErrorBorder,
80+
hcDark: contrastBorder,
81+
hcLight: contrastBorder
82+
}, localize('gaugeErrorBackground', "Gauge error background color."));
83+
84+
registerColor('gauge.errorForeground', {
85+
dark: transparent(gaugeErrorBackground, 0.3),
86+
light: transparent(gaugeErrorBackground, 0.3),
87+
hcDark: Color.white,
88+
hcLight: Color.white
89+
}, localize('gaugeErrorForeground', "Gauge error foreground color."));
90+
91+
//#endregion
92+
6293
export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribution {
6394

6495
static readonly ID = 'chat.statusBarEntry';
@@ -170,30 +201,43 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
170201
const container = $('div.chat-status-bar-entry-tooltip');
171202

172203
// Quota Indicator
173-
const { chatTotal, chatRemaining, completionsTotal, completionsRemaining, quotaResetDate } = this.chatQuotasService.quotas;
204+
{
205+
const { chatTotal, chatRemaining, completionsTotal, completionsRemaining, quotaResetDate } = this.chatQuotasService.quotas;
174206

175-
container.appendChild($('div', undefined, localize('limitTitle', "You are using Copilot Free")));
176-
container.appendChild($('hr'));
207+
container.appendChild($('div.header', undefined, localize('usageTitle', "Copilot Free Usage")));
177208

178-
const chatQuotaIndicator = this.createQuotaIndicator(container, chatTotal, chatRemaining, localize('chatsLabel', "Chats messages remaining"));
179-
const completionsQuotaIndicator = this.createQuotaIndicator(container, completionsTotal, completionsRemaining, localize('completionsLabel', "Code completions remaining"));
209+
const chatQuotaIndicator = this.createQuotaIndicator(container, chatTotal, chatRemaining, localize('chatsLabel', "Chats messages"));
210+
const completionsQuotaIndicator = this.createQuotaIndicator(container, completionsTotal, completionsRemaining, localize('completionsLabel', "Code completions"));
180211

181-
this.chatEntitlementsService.resolve(CancellationToken.None).then(() => {
182-
const { chatTotal, chatRemaining, completionsTotal, completionsRemaining } = this.chatQuotasService.quotas;
212+
container.appendChild($('div.description', undefined, localize('limitQuota', "Limits will reset on {0}.", this.dateFormatter.value.format(quotaResetDate))));
183213

184-
chatQuotaIndicator(chatTotal, chatRemaining);
185-
completionsQuotaIndicator(completionsTotal, completionsRemaining);
186-
});
214+
const cts = new CancellationTokenSource();
215+
disposables.add(toDisposable(() => cts.dispose(true)));
216+
this.chatEntitlementsService.resolve(cts.token).then(() => {
217+
if (cts.token.isCancellationRequested) {
218+
return;
219+
}
187220

188-
container.appendChild($('div.description', undefined, localize('limitQuota', "Limits will reset on {0}.", this.dateFormatter.value.format(quotaResetDate))));
221+
const { chatTotal, chatRemaining, completionsTotal, completionsRemaining } = this.chatQuotasService.quotas;
222+
223+
chatQuotaIndicator(chatTotal, chatRemaining);
224+
completionsQuotaIndicator(completionsTotal, completionsRemaining);
225+
});
226+
}
189227

190228
// Settings
191-
container.appendChild($('hr'));
192-
this.createSettings(container, disposables);
229+
{
230+
container.appendChild($('hr'));
231+
container.appendChild($('div.header', undefined, localize('settingsTitle', "Settings")));
232+
this.createSettings(container, disposables);
233+
}
193234

194235
// Shortcuts
195-
container.appendChild($('hr'));
196-
this.createShortcuts(container, disposables);
236+
{
237+
container.appendChild($('hr'));
238+
container.appendChild($('div.header', undefined, localize('keybindingsTitle', "Keybindings")));
239+
this.createShortcuts(container, disposables);
240+
}
197241

198242
return container;
199243
};
@@ -205,17 +249,18 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
205249
tooltip = () => {
206250
const container = $('div.chat-status-bar-entry-tooltip');
207251

208-
if (this.contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.pro.key) === true) {
209-
container.appendChild($('div', undefined, localize('proTitle', "You are using Copilot Pro")));
210-
container.appendChild($('hr'));
211-
}
212-
213252
// Settings
214-
this.createSettings(container, disposables);
253+
{
254+
container.appendChild($('div.header', undefined, localize('settingsTitle', "Settings")));
255+
this.createSettings(container, disposables);
256+
}
215257

216258
// Shortcuts
217-
container.appendChild($('hr'));
218-
this.createShortcuts(container, disposables);
259+
{
260+
container.appendChild($('hr'));
261+
container.appendChild($('div.header', undefined, localize('keybindingsTitle', "Keybindings")));
262+
this.createShortcuts(container, disposables);
263+
}
219264

220265
return container;
221266
};
@@ -237,7 +282,7 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
237282
const quotaText = $('span');
238283
const quotaBit = $('div.quota-bit');
239284

240-
container.appendChild($('div.quota-indicator', undefined,
285+
const quotaIndicator = container.appendChild($('div.quota-indicator', undefined,
241286
$('div.quota-label', undefined,
242287
$('span', undefined, label),
243288
quotaText
@@ -248,10 +293,23 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
248293
));
249294

250295
const update = (total: number | undefined, remaining: number | undefined) => {
296+
quotaIndicator.classList.remove('error');
297+
quotaIndicator.classList.remove('warning');
298+
251299
if (typeof total === 'number' && typeof remaining === 'number') {
252-
// TODO@bpasero: enable this label when we can track this better
253-
//quotaText.textContent = localize('quotaDisplay', "{0} / {1}", remaining, total);
254-
quotaBit.style.width = `${(remaining / total) * 100}%`;
300+
let usedPercentage = Math.round(((total - remaining) / total) * 100);
301+
if (total !== remaining && usedPercentage === 0) {
302+
usedPercentage = 1; // indicate minimal usage as 1%
303+
}
304+
305+
quotaText.textContent = localize('quotaDisplay', "{0}%", usedPercentage);
306+
quotaBit.style.width = `${usedPercentage}%`;
307+
308+
if (usedPercentage >= 90) {
309+
quotaIndicator.classList.add('error');
310+
} else if (usedPercentage >= 75) {
311+
quotaIndicator.classList.add('warning');
312+
}
255313
}
256314
};
257315

@@ -328,7 +386,7 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
328386
return settings;
329387
}
330388

331-
private createSetting(container: HTMLElement, label: string, setting: { key: string; override: string | undefined }, disposables: DisposableStore): Checkbox {
389+
private createSetting(container: HTMLElement, label: string, setting: { key: string; override: string | undefined }, disposables: DisposableStore): void {
332390
const readSetting = () => Boolean(this.configurationService.getValue<boolean>(setting.key, { overrideIdentifier: setting.override }));
333391
const writeSetting = (checkbox: Checkbox) => this.configurationService.updateValue(setting.key, checkbox.checked, { overrideIdentifier: setting.override });
334392

@@ -358,8 +416,6 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
358416
settingCheckbox.checked = readSetting();
359417
}
360418
}));
361-
362-
return settingCheckbox;
363419
}
364420

365421
override dispose(): void {

src/vs/workbench/contrib/chat/browser/media/chatStatus.css

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,21 @@
66
/* Overall */
77

88
.chat-status-bar-entry-tooltip {
9-
margin-top: 6px;
10-
margin-bottom: 6px;
9+
margin-top: 4px;
10+
margin-bottom: 4px;
1111
}
1212

1313
.chat-status-bar-entry-tooltip hr {
1414
margin-top: 8px;
1515
margin-bottom: 8px;
1616
}
1717

18+
.chat-status-bar-entry-tooltip div.header {
19+
color: var(--vscode-descriptionForeground);
20+
margin-bottom: 4px;
21+
font-weight: 600;
22+
}
23+
1824
.chat-status-bar-entry-tooltip div.description {
1925
color: var(--vscode-descriptionForeground);
2026
}
@@ -93,8 +99,7 @@
9399
/* Quota Indicator */
94100

95101
.chat-status-bar-entry-tooltip .quota-indicator {
96-
margin-top: 8px;
97-
margin-bottom: 8px;
102+
margin-bottom: 6px;
98103
}
99104

100105
.chat-status-bar-entry-tooltip .quota-indicator .quota-label {
@@ -116,3 +121,19 @@
116121
background-color: var(--vscode-gauge-background);
117122
border-radius: 4px;
118123
}
124+
125+
.chat-status-bar-entry-tooltip .quota-indicator.warning .quota-bar {
126+
background-color: var(--vscode-gauge-warningForeground);
127+
}
128+
129+
.chat-status-bar-entry-tooltip .quota-indicator.warning .quota-bar .quota-bit {
130+
background-color: var(--vscode-gauge-warningBackground);
131+
}
132+
133+
.chat-status-bar-entry-tooltip .quota-indicator.error .quota-bar {
134+
background-color: var(--vscode-gauge-errorForeground);
135+
}
136+
137+
.chat-status-bar-entry-tooltip .quota-indicator.error .quota-bar .quota-bit {
138+
background-color: var(--vscode-gauge-errorBackground);
139+
}

0 commit comments

Comments
 (0)