chat - better flow from status when reaching quota (#242548) (#242560)

This commit is contained in:
Benjamin Pasero
2025-03-04 14:19:02 +01:00
committed by GitHub
parent 3bf4028bbd
commit 1a7a159d91
3 changed files with 53 additions and 67 deletions

View File

@@ -726,7 +726,13 @@ export class CopilotTitleBarMenuRendering extends Disposable implements IWorkben
primaryActionIcon = Codicon.copilotNotConnected;
} else if (chatQuotaExceeded || completionsQuotaExceeded) {
primaryActionId = OPEN_CHAT_QUOTA_EXCEEDED_DIALOG;
primaryActionTitle = quotaToButtonMessage({ chatQuotaExceeded, completionsQuotaExceeded });
if (chatQuotaExceeded && !completionsQuotaExceeded) {
primaryActionTitle = localize('chatQuotaExceededButton', "Monthly chat messages limit reached. Click for details.");
} else if (completionsQuotaExceeded && !chatQuotaExceeded) {
primaryActionTitle = localize('completionsQuotaExceededButton', "Monthly code completions limit reached. Click for details.");
} else {
primaryActionTitle = localize('chatAndCompletionsQuotaExceededButton', "Copilot Free plan limit reached. Click for details.");
}
primaryActionIcon = Codicon.copilotWarning;
} else {
primaryActionId = TOGGLE_CHAT_ACTION_ID;
@@ -749,13 +755,3 @@ export class CopilotTitleBarMenuRendering extends Disposable implements IWorkben
markAsSingleton(disposable);
}
}
export function quotaToButtonMessage({ chatQuotaExceeded, completionsQuotaExceeded }: { chatQuotaExceeded: boolean; completionsQuotaExceeded: boolean }): string {
if (chatQuotaExceeded && !completionsQuotaExceeded) {
return localize('chatQuotaExceededButton', "Monthly chat messages limit reached. Click for details.");
} else if (completionsQuotaExceeded && !chatQuotaExceeded) {
return localize('completionsQuotaExceededButton', "Monthly code completions limit reached. Click for details.");
} else {
return localize('chatAndCompletionsQuotaExceededButton', "Copilot Free plan limit reached. Click for details.");
}
}

View File

@@ -6,19 +6,18 @@
import './media/chatStatus.css';
import { safeIntl } from '../../../../base/common/date.js';
import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
import { language, OS } from '../../../../base/common/platform.js';
import { language } from '../../../../base/common/platform.js';
import { localize } from '../../../../nls.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { IWorkbenchContribution } from '../../../common/contributions.js';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind, TooltipContent } from '../../../services/statusbar/browser/statusbar.js';
import { ChatContextKeys } from '../common/chatContextKeys.js';
import { quotaToButtonMessage, OPEN_CHAT_QUOTA_EXCEEDED_DIALOG, CHAT_SETUP_ACTION_LABEL, TOGGLE_CHAT_ACTION_ID, CHAT_OPEN_ACTION_ID } from './actions/chatActions.js';
import { OPEN_CHAT_QUOTA_EXCEEDED_DIALOG, CHAT_SETUP_ACTION_LABEL, TOGGLE_CHAT_ACTION_ID, CHAT_OPEN_ACTION_ID } from './actions/chatActions.js';
import { $, addDisposableListener, append, clearNode, EventHelper, EventLike, EventType } from '../../../../base/browser/dom.js';
import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService } from '../common/chatEntitlementService.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
import { defaultCheckboxStyles, defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { defaultButtonStyles, defaultCheckboxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { Command } from '../../../../editor/common/languages.js';
@@ -38,6 +37,7 @@ import { IProductService } from '../../../../platform/product/common/productServ
import { isObject } from '../../../../base/common/types.js';
import { ILanguageService } from '../../../../editor/common/languages/language.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { Button } from '../../../../base/browser/ui/button/button.js';
//#region --- colors
@@ -160,13 +160,33 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
private getEntryProps(): IStatusbarEntry {
let text = '$(copilot)';
let ariaLabel = localize('chatStatus', "Copilot Status");
let command: string | Command;
let tooltip: TooltipContent;
let command: string | Command = ShowTooltipCommand;
let tooltip: TooltipContent = { element: token => this.dashboard.value.show(token) };
let kind: StatusbarEntryKind | undefined;
// Quota Exceeded
const { chatQuotaExceeded, completionsQuotaExceeded } = this.chatEntitlementService.quotas;
if (chatQuotaExceeded || completionsQuotaExceeded) {
// Copilot Not Installed
if (
this.contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.installed.key) === false ||
this.chatEntitlementService.entitlement === ChatEntitlement.Available
) {
tooltip = CHAT_SETUP_ACTION_LABEL.value;
ariaLabel = tooltip;
command = TOGGLE_CHAT_ACTION_ID;
}
// Signed out
else if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) {
const signInWarning = localize('signInToUseCopilot', "Sign in to Use Copilot...");
text = `$(copilot-warning) ${signInWarning}`;
ariaLabel = signInWarning;
command = ChatStatusBarEntry.SIGN_IN_COMMAND_ID;
kind = 'prominent';
}
// Quota Exceeded
else if (chatQuotaExceeded || completionsQuotaExceeded) {
let quotaWarning: string;
if (chatQuotaExceeded && !completionsQuotaExceeded) {
quotaWarning = localize('chatQuotaExceededStatus', "Chat limit reached");
@@ -179,38 +199,9 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu
text = `$(copilot-warning) ${quotaWarning}`;
ariaLabel = quotaWarning;
command = OPEN_CHAT_QUOTA_EXCEEDED_DIALOG;
tooltip = quotaToButtonMessage({ chatQuotaExceeded, completionsQuotaExceeded });
kind = 'prominent';
}
// Copilot Not Installed
else if (
this.contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.installed.key) === false ||
this.chatEntitlementService.entitlement === ChatEntitlement.Available
) {
tooltip = CHAT_SETUP_ACTION_LABEL.value;
command = TOGGLE_CHAT_ACTION_ID;
}
// Signed out
else if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) {
text = '$(copilot-not-connected) Sign In to Use Copilot';
ariaLabel = localize('signInToUseCopilot', "Sign in to Use Copilot...");
tooltip = {
element: token => this.dashboard.value.show(token)
};
command = ChatStatusBarEntry.SIGN_IN_COMMAND_ID;
kind = 'prominent';
}
// Any other User
else {
tooltip = {
element: token => this.dashboard.value.show(token)
};
command = ShowTooltipCommand;
}
return {
name: localize('chatStatus', "Copilot Status"),
text,
@@ -258,7 +249,7 @@ class ChatStatusDashboard extends Disposable {
// Quota Indicator
if (this.chatEntitlementService.entitlement === ChatEntitlement.Limited) {
const { chatTotal, chatRemaining, completionsTotal, completionsRemaining, quotaResetDate } = this.chatEntitlementService.quotas;
const { chatTotal, chatRemaining, completionsTotal, completionsRemaining, quotaResetDate, chatQuotaExceeded, completionsQuotaExceeded } = this.chatEntitlementService.quotas;
this.element.appendChild($('div.header', undefined, localize('usageTitle', "Copilot Free Usage")));
@@ -267,6 +258,12 @@ class ChatStatusDashboard extends Disposable {
this.element.appendChild($('div.description', undefined, localize('limitQuota', "Limits will reset on {0}.", this.dateFormatter.value.format(quotaResetDate))));
if (chatQuotaExceeded || completionsQuotaExceeded) {
const upgradePlanButton = disposables.add(new Button(this.element, { ...defaultButtonStyles }));
upgradePlanButton.label = localize('upgradeToCopilotPro', "Upgrade to Copilot Pro");
disposables.add(upgradePlanButton.onDidClick(() => this.commandService.executeCommand('workbench.action.chat.upgradePlan')));
}
(async () => {
await this.chatEntitlementService.update(token);
if (token.isCancellationRequested) {
@@ -430,17 +427,10 @@ class ChatStatusDashboard extends Disposable {
};
for (const entry of entries) {
const keys = this.keybindingService.lookupKeybinding(entry.id);
if (!keys) {
continue;
}
const shortcut = append(shortcuts, $('div.shortcut', { tabIndex: 0, role: 'button', 'aria-label': entry.text }));
append(shortcut, $('span.shortcut-label', undefined, entry.text));
const shortcutKey = disposables.add(new KeybindingLabel(shortcut, OS, { ...defaultKeybindingLabelStyles }));
shortcutKey.set(keys);
const shortcut = append(shortcuts, $('div.shortcut', { tabIndex: 0, role: 'button', 'aria-label': entry.text },
$('span.shortcut-label', undefined, entry.text),
$('span.shortcut-value', undefined, this.keybindingService.lookupKeybinding(entry.id)?.getLabel() ?? '')
));
disposables.add(Gesture.addTarget(shortcut));
[EventType.CLICK, TouchEventType.Tap].forEach(eventType => {

View File

@@ -25,6 +25,11 @@
color: var(--vscode-descriptionForeground);
}
.chat-status-bar-entry-tooltip .monaco-button {
margin-top: 4px;
margin-bottom: 4px;
}
/* Settings */
.chat-status-bar-entry-tooltip .settings {
@@ -87,13 +92,8 @@
cursor: pointer;
}
.chat-status-bar-entry-tooltip .shortcuts .shortcut .monaco-keybinding {
cursor: pointer;
}
.chat-status-bar-entry-tooltip .shortcuts .shortcut .monaco-keybinding > .monaco-keybinding-key {
padding: 2px 4px;
font-size: 10px;
.chat-status-bar-entry-tooltip .shortcuts .shortcut .shortcut-value {
color: var(--vscode-descriptionForeground);
}
/* Quota Indicator */