chat - polish chat status after move (#279140)

* chat - polish chat status after move

* .
This commit is contained in:
Benjamin Pasero
2025-11-24 13:27:44 +01:00
committed by GitHub
parent 21ca4eb7b3
commit e1d07bbeaf
6 changed files with 308 additions and 318 deletions

View File

@@ -114,7 +114,7 @@ import { ChatTerminalOutputAccessibleView } from './chatTerminalOutputAccessible
import { LocalChatSessionsProvider } from './chatSessions/localChatSessionsProvider.js';
import { ChatSessionsView, ChatSessionsViewContrib } from './chatSessions/view/chatSessionsView.js';
import { ChatSetupContribution, ChatTeardownContribution } from './chatSetup.js';
import { ChatStatusBarEntry } from './chatStatus/chatStatus.js';
import { ChatStatusBarEntry } from './chatStatus/chatStatusEntry.js';
import { ChatVariablesService } from './chatVariables.js';
import { ChatWidget } from './chatWidget.js';
import { ChatCodeBlockContextProviderService } from './codeBlockContextProviderService.js';

View File

@@ -3,259 +3,25 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './media/chatStatus.css';
import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js';
import { localize } from '../../../../../nls.js';
import { IWorkbenchContribution } from '../../../../common/contributions.js';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind } from '../../../../services/statusbar/browser/statusbar.js';
import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService, isProUser } from '../../../../services/chat/common/chatEntitlementService.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { contrastBorder, inputValidationErrorBorder, inputValidationInfoBorder, inputValidationWarningBorder, registerColor, transparent } from '../../../../../platform/theme/common/colorRegistry.js';
import { Color } from '../../../../../base/common/color.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js';
import { IInlineCompletionsService } from '../../../../../editor/browser/services/inlineCompletionsService.js';
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { ChatStatusDashboard } from './chatStatusDashboard.js';
import { mainWindow } from '../../../../../base/browser/window.js';
import { disposableWindowInterval } from '../../../../../base/browser/dom.js';
import { defaultChat, isNewUser, isCompletionsEnabled } from './common.js';
import product from '../../../../../platform/product/common/product.js';
import { isObject } from '../../../../../base/common/types.js';
const gaugeForeground = registerColor('gauge.foreground', {
dark: inputValidationInfoBorder,
light: inputValidationInfoBorder,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeForeground', "Gauge foreground color."));
registerColor('gauge.background', {
dark: transparent(gaugeForeground, 0.3),
light: transparent(gaugeForeground, 0.3),
hcDark: Color.white,
hcLight: Color.white
}, localize('gaugeBackground', "Gauge background color."));
registerColor('gauge.border', {
dark: null,
light: null,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeBorder', "Gauge border color."));
const gaugeWarningForeground = registerColor('gauge.warningForeground', {
dark: inputValidationWarningBorder,
light: inputValidationWarningBorder,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeWarningForeground', "Gauge warning foreground color."));
registerColor('gauge.warningBackground', {
dark: transparent(gaugeWarningForeground, 0.3),
light: transparent(gaugeWarningForeground, 0.3),
hcDark: Color.white,
hcLight: Color.white
}, localize('gaugeWarningBackground', "Gauge warning background color."));
const gaugeErrorForeground = registerColor('gauge.errorForeground', {
dark: inputValidationErrorBorder,
light: inputValidationErrorBorder,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeErrorForeground', "Gauge error foreground color."));
registerColor('gauge.errorBackground', {
dark: transparent(gaugeErrorForeground, 0.3),
light: transparent(gaugeErrorForeground, 0.3),
hcDark: Color.white,
hcLight: Color.white
}, localize('gaugeErrorBackground', "Gauge error background color."));
//#endregion
export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.chatStatusBarEntry';
private entry: IStatusbarEntryAccessor | undefined = undefined;
private readonly activeCodeEditorListener = this._register(new MutableDisposable());
constructor(
@IChatEntitlementService private readonly chatEntitlementService: ChatEntitlementService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IInlineCompletionsService private readonly completionsService: IInlineCompletionsService,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
) {
super();
this.update();
this.registerListeners();
}
private update(): void {
const sentiment = this.chatEntitlementService.sentiment;
if (!sentiment.hidden) {
const props = this.getEntryProps();
if (this.entry) {
this.entry.update(props);
} else {
this.entry = this.statusbarService.addEntry(props, 'chat.statusBarEntry', StatusbarAlignment.RIGHT, { location: { id: 'status.editor.mode', priority: 100.1 }, alignment: StatusbarAlignment.RIGHT });
}
} else {
this.entry?.dispose();
this.entry = undefined;
}
}
private registerListeners(): void {
this._register(this.chatEntitlementService.onDidChangeQuotaExceeded(() => this.update()));
this._register(this.chatEntitlementService.onDidChangeSentiment(() => this.update()));
this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.update()));
this._register(this.completionsService.onDidChangeIsSnoozing(() => this.update()));
this._register(this.chatSessionsService.onDidChangeInProgress(() => this.update()));
this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange()));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(defaultChat.completionsEnablementSetting)) {
this.update();
}
}));
}
private onDidActiveEditorChange(): void {
this.update();
this.activeCodeEditorListener.clear();
// Listen to language changes in the active code editor
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);
if (activeCodeEditor) {
this.activeCodeEditorListener.value = activeCodeEditor.onDidChangeModelLanguage(() => {
this.update();
});
}
}
private getEntryProps(): IStatusbarEntry {
let text = '$(copilot)';
let ariaLabel = localize('chatStatusAria', "Copilot status");
let kind: StatusbarEntryKind | undefined;
if (isNewUser(this.chatEntitlementService)) {
const entitlement = this.chatEntitlementService.entitlement;
// Finish Setup
if (
this.chatEntitlementService.sentiment.later || // user skipped setup
entitlement === ChatEntitlement.Available || // user is entitled
isProUser(entitlement) || // user is already pro
entitlement === ChatEntitlement.Free // user is already free
) {
const finishSetup = localize('finishSetup', "Finish Setup");
text = `$(copilot) ${finishSetup}`;
ariaLabel = finishSetup;
kind = 'prominent';
}
} else {
const chatQuotaExceeded = this.chatEntitlementService.quotas.chat?.percentRemaining === 0;
const completionsQuotaExceeded = this.chatEntitlementService.quotas.completions?.percentRemaining === 0;
const chatSessionsInProgressCount = this.chatSessionsService.getInProgress().reduce((total, item) => total + item.count, 0);
// Disabled
if (this.chatEntitlementService.sentiment.disabled || this.chatEntitlementService.sentiment.untrusted) {
text = '$(copilot-unavailable)';
ariaLabel = localize('copilotDisabledStatus', "Copilot disabled");
}
// Sessions in progress
else if (chatSessionsInProgressCount > 0) {
text = '$(copilot-in-progress)';
if (chatSessionsInProgressCount > 1) {
ariaLabel = localize('chatSessionsInProgressStatus', "{0} agent sessions in progress", chatSessionsInProgressCount);
} else {
ariaLabel = localize('chatSessionInProgressStatus', "1 agent session in progress");
}
}
// Signed out
else if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) {
const signedOutWarning = localize('notSignedIn', "Signed out");
text = `${this.chatEntitlementService.anonymous ? '$(copilot)' : '$(copilot-not-connected)'} ${signedOutWarning}`;
ariaLabel = signedOutWarning;
kind = 'prominent';
}
// Free Quota Exceeded
else if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && (chatQuotaExceeded || completionsQuotaExceeded)) {
let quotaWarning: string;
if (chatQuotaExceeded && !completionsQuotaExceeded) {
quotaWarning = localize('chatQuotaExceededStatus', "Chat quota reached");
} else if (completionsQuotaExceeded && !chatQuotaExceeded) {
quotaWarning = localize('completionsQuotaExceededStatus', "Inline suggestions quota reached");
} else {
quotaWarning = localize('chatAndCompletionsQuotaExceededStatus', "Quota reached");
}
text = `$(copilot-warning) ${quotaWarning}`;
ariaLabel = quotaWarning;
kind = 'prominent';
}
// Completions Disabled
else if (this.editorService.activeTextEditorLanguageId && !isCompletionsEnabled(this.configurationService, this.editorService.activeTextEditorLanguageId)) {
text = '$(copilot-unavailable)';
ariaLabel = localize('completionsDisabledStatus', "Inline suggestions disabled");
}
// Completions Snoozed
else if (this.completionsService.isSnoozing()) {
text = '$(copilot-snooze)';
ariaLabel = localize('completionsSnoozedStatus', "Inline suggestions snoozed");
}
}
const baseResult = {
name: localize('chatStatus', "Copilot Status"),
text,
ariaLabel,
command: ShowTooltipCommand,
showInAllWindows: true,
kind,
tooltip: {
element: (token: CancellationToken) => {
const store = new DisposableStore();
store.add(token.onCancellationRequested(() => {
store.dispose();
}));
const elem = ChatStatusDashboard.instantiateInContents(this.instantiationService, store);
// todo@connor4312/@benibenj: workaround for #257923
store.add(disposableWindowInterval(mainWindow, () => {
if (!elem.isConnected) {
store.dispose();
}
}, 2000));
return elem;
}
}
} satisfies IStatusbarEntry;
return baseResult;
}
override dispose(): void {
super.dispose();
this.entry?.dispose();
this.entry = undefined;
}
export function isNewUser(chatEntitlementService: IChatEntitlementService): boolean {
return !chatEntitlementService.sentiment.installed || // chat not installed
chatEntitlementService.entitlement === ChatEntitlement.Available; // not yet signed up to chat
}
export function isCompletionsEnabled(configurationService: IConfigurationService, modeId: string = '*'): boolean {
const result = configurationService.getValue<Record<string, boolean>>(product.defaultChatAgent.completionsEnablementSetting);
if (!isObject(result)) {
return false;
}
if (typeof result[modeId] !== 'undefined') {
return Boolean(result[modeId]); // go with setting if explicitly defined
}
return Boolean(result['*']); // fallback to global setting otherwise
}

View File

@@ -39,8 +39,13 @@ import { IEditorService } from '../../../../services/editor/common/editorService
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { LEGACY_AGENT_SESSIONS_VIEW_ID } from '../../common/constants.js';
import { AGENT_SESSIONS_VIEW_ID } from '../agentSessions/agentSessions.js';
import { defaultChat, canUseChat, isNewUser, isCompletionsEnabled } from './common.js';
import { isNewUser, isCompletionsEnabled } from './chatStatus.js';
import { IChatStatusItemService, ChatStatusEntry } from './chatStatusItemService.js';
import product from '../../../../../platform/product/common/product.js';
import { contrastBorder, inputValidationErrorBorder, inputValidationInfoBorder, inputValidationWarningBorder, registerColor, transparent } from '../../../../../platform/theme/common/colorRegistry.js';
import { Color } from '../../../../../base/common/color.js';
const defaultChat = product.defaultChatAgent;
interface ISettingsAccessor {
readSetting: () => boolean;
@@ -59,7 +64,57 @@ type ChatSettingChangedEvent = {
settingEnablement: 'enabled' | 'disabled';
};
const gaugeForeground = registerColor('gauge.foreground', {
dark: inputValidationInfoBorder,
light: inputValidationInfoBorder,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeForeground', "Gauge foreground color."));
registerColor('gauge.background', {
dark: transparent(gaugeForeground, 0.3),
light: transparent(gaugeForeground, 0.3),
hcDark: Color.white,
hcLight: Color.white
}, localize('gaugeBackground', "Gauge background color."));
registerColor('gauge.border', {
dark: null,
light: null,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeBorder', "Gauge border color."));
const gaugeWarningForeground = registerColor('gauge.warningForeground', {
dark: inputValidationWarningBorder,
light: inputValidationWarningBorder,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeWarningForeground', "Gauge warning foreground color."));
registerColor('gauge.warningBackground', {
dark: transparent(gaugeWarningForeground, 0.3),
light: transparent(gaugeWarningForeground, 0.3),
hcDark: Color.white,
hcLight: Color.white
}, localize('gaugeWarningBackground', "Gauge warning background color."));
const gaugeErrorForeground = registerColor('gauge.errorForeground', {
dark: inputValidationErrorBorder,
light: inputValidationErrorBorder,
hcDark: contrastBorder,
hcLight: contrastBorder
}, localize('gaugeErrorForeground', "Gauge error foreground color."));
registerColor('gauge.errorBackground', {
dark: transparent(gaugeErrorForeground, 0.3),
light: transparent(gaugeErrorForeground, 0.3),
hcDark: Color.white,
hcLight: Color.white
}, localize('gaugeErrorBackground', "Gauge error background color."));
export class ChatStatusDashboard extends DomWidget {
readonly element = $('div.chat-status-bar-entry-tooltip');
private readonly dateFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric' });
@@ -84,10 +139,10 @@ export class ChatStatusDashboard extends DomWidget {
) {
super();
this._render();
this.render();
}
private _render(): void {
private render(): void {
const token = cancelOnDispose(this._store);
let needsSeparator = false;
@@ -123,7 +178,7 @@ export class ChatStatusDashboard extends DomWidget {
}
if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && (Number(chatQuota?.percentRemaining) <= 25 || Number(completionsQuota?.percentRemaining) <= 25)) {
const upgradeProButton = this._store.add(new Button(this.element, { ...defaultButtonStyles, hoverDelegate: nativeHoverDelegate, secondary: canUseChat(this.chatEntitlementService) /* use secondary color when chat can still be used */ }));
const upgradeProButton = this._store.add(new Button(this.element, { ...defaultButtonStyles, hoverDelegate: nativeHoverDelegate, secondary: this.canUseChat() /* use secondary color when chat can still be used */ }));
upgradeProButton.label = localize('upgradeToCopilotPro', "Upgrade to GitHub Copilot Pro");
this._store.add(upgradeProButton.onDidClick(() => this.runCommandAndClose('workbench.action.chat.upgradePlan')));
}
@@ -235,7 +290,7 @@ export class ChatStatusDashboard extends DomWidget {
}
// Completions Snooze
if (canUseChat(this.chatEntitlementService)) {
if (this.canUseChat()) {
const snooze = append(this.element, $('div.snooze-completions'));
this.createCompletionsSnooze(snooze, localize('settings.snooze', "Snooze"), this._store);
}
@@ -295,6 +350,22 @@ export class ChatStatusDashboard extends DomWidget {
}
}
private canUseChat(): boolean {
if (!this.chatEntitlementService.sentiment.installed || this.chatEntitlementService.sentiment.disabled || this.chatEntitlementService.sentiment.untrusted) {
return false; // chat not installed or not enabled
}
if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown || this.chatEntitlementService.entitlement === ChatEntitlement.Available) {
return this.chatEntitlementService.anonymous; // signed out or not-yet-signed-up users can only use Chat if anonymous access is allowed
}
if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && this.chatEntitlementService.quotas.chat?.percentRemaining === 0 && this.chatEntitlementService.quotas.completions?.percentRemaining === 0) {
return false; // free user with no quota left
}
return true;
}
private renderHeader(container: HTMLElement, disposables: DisposableStore, label: string, action?: IAction): void {
const header = container.appendChild($('div.header', undefined, label ?? ''));
@@ -475,7 +546,7 @@ export class ChatStatusDashboard extends DomWidget {
}
}));
if (!canUseChat(this.chatEntitlementService)) {
if (!this.canUseChat()) {
container.classList.add('disabled');
checkbox.disable();
checkbox.checked = false;
@@ -536,7 +607,7 @@ export class ChatStatusDashboard extends DomWidget {
disposables.add(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(completionsSettingId)) {
if (completionsSettingAccessor.readSetting() && canUseChat(this.chatEntitlementService)) {
if (completionsSettingAccessor.readSetting() && this.canUseChat()) {
checkbox.enable();
container.classList.remove('disabled');
} else {

View File

@@ -0,0 +1,209 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './media/chatStatus.css';
import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js';
import { localize } from '../../../../../nls.js';
import { IWorkbenchContribution } from '../../../../common/contributions.js';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind } from '../../../../services/statusbar/browser/statusbar.js';
import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService, isProUser } from '../../../../services/chat/common/chatEntitlementService.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js';
import { IInlineCompletionsService } from '../../../../../editor/browser/services/inlineCompletionsService.js';
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { ChatStatusDashboard } from './chatStatusDashboard.js';
import { mainWindow } from '../../../../../base/browser/window.js';
import { disposableWindowInterval } from '../../../../../base/browser/dom.js';
import { isNewUser, isCompletionsEnabled } from './chatStatus.js';
import product from '../../../../../platform/product/common/product.js';
export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.chatStatusBarEntry';
private entry: IStatusbarEntryAccessor | undefined = undefined;
private readonly activeCodeEditorListener = this._register(new MutableDisposable());
constructor(
@IChatEntitlementService private readonly chatEntitlementService: ChatEntitlementService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IInlineCompletionsService private readonly completionsService: IInlineCompletionsService,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
) {
super();
this.update();
this.registerListeners();
}
private update(): void {
const sentiment = this.chatEntitlementService.sentiment;
if (!sentiment.hidden) {
const props = this.getEntryProps();
if (this.entry) {
this.entry.update(props);
} else {
this.entry = this.statusbarService.addEntry(props, 'chat.statusBarEntry', StatusbarAlignment.RIGHT, { location: { id: 'status.editor.mode', priority: 100.1 }, alignment: StatusbarAlignment.RIGHT });
}
} else {
this.entry?.dispose();
this.entry = undefined;
}
}
private registerListeners(): void {
this._register(this.chatEntitlementService.onDidChangeQuotaExceeded(() => this.update()));
this._register(this.chatEntitlementService.onDidChangeSentiment(() => this.update()));
this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.update()));
this._register(this.completionsService.onDidChangeIsSnoozing(() => this.update()));
this._register(this.chatSessionsService.onDidChangeInProgress(() => this.update()));
this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange()));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(product.defaultChatAgent.completionsEnablementSetting)) {
this.update();
}
}));
}
private onDidActiveEditorChange(): void {
this.update();
this.activeCodeEditorListener.clear();
// Listen to language changes in the active code editor
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);
if (activeCodeEditor) {
this.activeCodeEditorListener.value = activeCodeEditor.onDidChangeModelLanguage(() => {
this.update();
});
}
}
private getEntryProps(): IStatusbarEntry {
let text = '$(copilot)';
let ariaLabel = localize('chatStatusAria', "Copilot status");
let kind: StatusbarEntryKind | undefined;
if (isNewUser(this.chatEntitlementService)) {
const entitlement = this.chatEntitlementService.entitlement;
// Finish Setup
if (
this.chatEntitlementService.sentiment.later || // user skipped setup
entitlement === ChatEntitlement.Available || // user is entitled
isProUser(entitlement) || // user is already pro
entitlement === ChatEntitlement.Free // user is already free
) {
const finishSetup = localize('finishSetup', "Finish Setup");
text = `$(copilot) ${finishSetup}`;
ariaLabel = finishSetup;
kind = 'prominent';
}
} else {
const chatQuotaExceeded = this.chatEntitlementService.quotas.chat?.percentRemaining === 0;
const completionsQuotaExceeded = this.chatEntitlementService.quotas.completions?.percentRemaining === 0;
const chatSessionsInProgressCount = this.chatSessionsService.getInProgress().reduce((total, item) => total + item.count, 0);
// Disabled
if (this.chatEntitlementService.sentiment.disabled || this.chatEntitlementService.sentiment.untrusted) {
text = '$(copilot-unavailable)';
ariaLabel = localize('copilotDisabledStatus', "Copilot disabled");
}
// Sessions in progress
else if (chatSessionsInProgressCount > 0) {
text = '$(copilot-in-progress)';
if (chatSessionsInProgressCount > 1) {
ariaLabel = localize('chatSessionsInProgressStatus', "{0} agent sessions in progress", chatSessionsInProgressCount);
} else {
ariaLabel = localize('chatSessionInProgressStatus', "1 agent session in progress");
}
}
// Signed out
else if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) {
const signedOutWarning = localize('notSignedIn', "Signed out");
text = `${this.chatEntitlementService.anonymous ? '$(copilot)' : '$(copilot-not-connected)'} ${signedOutWarning}`;
ariaLabel = signedOutWarning;
kind = 'prominent';
}
// Free Quota Exceeded
else if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && (chatQuotaExceeded || completionsQuotaExceeded)) {
let quotaWarning: string;
if (chatQuotaExceeded && !completionsQuotaExceeded) {
quotaWarning = localize('chatQuotaExceededStatus', "Chat quota reached");
} else if (completionsQuotaExceeded && !chatQuotaExceeded) {
quotaWarning = localize('completionsQuotaExceededStatus', "Inline suggestions quota reached");
} else {
quotaWarning = localize('chatAndCompletionsQuotaExceededStatus', "Quota reached");
}
text = `$(copilot-warning) ${quotaWarning}`;
ariaLabel = quotaWarning;
kind = 'prominent';
}
// Completions Disabled
else if (this.editorService.activeTextEditorLanguageId && !isCompletionsEnabled(this.configurationService, this.editorService.activeTextEditorLanguageId)) {
text = '$(copilot-unavailable)';
ariaLabel = localize('completionsDisabledStatus', "Inline suggestions disabled");
}
// Completions Snoozed
else if (this.completionsService.isSnoozing()) {
text = '$(copilot-snooze)';
ariaLabel = localize('completionsSnoozedStatus', "Inline suggestions snoozed");
}
}
const baseResult = {
name: localize('chatStatus', "Copilot Status"),
text,
ariaLabel,
command: ShowTooltipCommand,
showInAllWindows: true,
kind,
tooltip: {
element: (token: CancellationToken) => {
const store = new DisposableStore();
store.add(token.onCancellationRequested(() => {
store.dispose();
}));
const elem = ChatStatusDashboard.instantiateInContents(this.instantiationService, store);
// todo@connor4312/@benibenj: workaround for #257923
store.add(disposableWindowInterval(mainWindow, () => {
if (!elem.isConnected) {
store.dispose();
}
}, 2000));
return elem;
}
}
} satisfies IStatusbarEntry;
return baseResult;
}
override dispose(): void {
super.dispose();
this.entry?.dispose();
this.entry = undefined;
}
}

View File

@@ -7,7 +7,7 @@ import { Emitter, Event } from '../../../../../base/common/event.js';
import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
export const IChatStatusItemService = createDecorator<IChatStatusItemService>('IChatStatusItemService');
export const IChatStatusItemService = createDecorator<IChatStatusItemService>('chatStatusItemService');
export interface IChatStatusItemService {
readonly _serviceBrand: undefined;
@@ -21,7 +21,6 @@ export interface IChatStatusItemService {
getEntries(): Iterable<ChatStatusEntry>;
}
export interface IChatStatusItemChangeEvent {
readonly entry: ChatStatusEntry;
}
@@ -33,7 +32,6 @@ export type ChatStatusEntry = {
detail: string | undefined;
};
class ChatStatusItemService implements IChatStatusItemService {
readonly _serviceBrand: undefined;

View File

@@ -1,54 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import product from '../../../../../platform/product/common/product.js';
import { isObject } from '../../../../../base/common/types.js';
export const defaultChat = {
completionsEnablementSetting: product.defaultChatAgent?.completionsEnablementSetting ?? '',
nextEditSuggestionsSetting: product.defaultChatAgent?.nextEditSuggestionsSetting ?? '',
manageSettingsUrl: product.defaultChatAgent?.manageSettingsUrl ?? '',
manageOverageUrl: product.defaultChatAgent?.manageOverageUrl ?? '',
provider: product.defaultChatAgent?.provider ?? { default: { id: '', name: '' }, enterprise: { id: '', name: '' }, apple: { id: '', name: '' }, google: { id: '', name: '' } },
termsStatementUrl: product.defaultChatAgent?.termsStatementUrl ?? '',
privacyStatementUrl: product.defaultChatAgent?.privacyStatementUrl ?? ''
};
export function isNewUser(chatEntitlementService: IChatEntitlementService): boolean {
return !chatEntitlementService.sentiment.installed || // chat not installed
chatEntitlementService.entitlement === ChatEntitlement.Available; // not yet signed up to chat
}
export function canUseChat(chatEntitlementService: IChatEntitlementService): boolean {
if (!chatEntitlementService.sentiment.installed || chatEntitlementService.sentiment.disabled || chatEntitlementService.sentiment.untrusted) {
return false; // chat not installed or not enabled
}
if (chatEntitlementService.entitlement === ChatEntitlement.Unknown || chatEntitlementService.entitlement === ChatEntitlement.Available) {
return chatEntitlementService.anonymous; // signed out or not-yet-signed-up users can only use Chat if anonymous access is allowed
}
if (chatEntitlementService.entitlement === ChatEntitlement.Free && chatEntitlementService.quotas.chat?.percentRemaining === 0 && chatEntitlementService.quotas.completions?.percentRemaining === 0) {
return false; // free user with no quota left
}
return true;
}
export function isCompletionsEnabled(configurationService: IConfigurationService, modeId: string = '*'): boolean {
const result = configurationService.getValue<Record<string, boolean>>(defaultChat.completionsEnablementSetting);
if (!isObject(result)) {
return false;
}
if (typeof result[modeId] !== 'undefined') {
return Boolean(result[modeId]); // go with setting if explicitly defined
}
return Boolean(result['*']); // fallback to global setting otherwise
}