Path: blob/main/src/vs/workbench/contrib/chat/browser/chatStatus/chatStatusEntry.ts
5252 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/chatStatus.css';6import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js';7import { localize } from '../../../../../nls.js';8import { IWorkbenchContribution } from '../../../../common/contributions.js';9import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind } from '../../../../services/statusbar/browser/statusbar.js';10import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService, isProUser } from '../../../../services/chat/common/chatEntitlementService.js';11import { CancellationToken } from '../../../../../base/common/cancellation.js';12import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';13import { IEditorService } from '../../../../services/editor/common/editorService.js';14import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';15import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js';16import { IInlineCompletionsService } from '../../../../../editor/browser/services/inlineCompletionsService.js';17import { IChatSessionsService } from '../../common/chatSessionsService.js';18import { ChatStatusDashboard } from './chatStatusDashboard.js';19import { mainWindow } from '../../../../../base/browser/window.js';20import { disposableWindowInterval } from '../../../../../base/browser/dom.js';21import { isNewUser } from './chatStatus.js';22import product from '../../../../../platform/product/common/product.js';23import { isCompletionsEnabled } from '../../../../../editor/common/services/completionsEnablement.js';2425export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribution {2627static readonly ID = 'workbench.contrib.chatStatusBarEntry';2829private entry: IStatusbarEntryAccessor | undefined = undefined;3031private readonly activeCodeEditorListener = this._register(new MutableDisposable());3233private runningSessionsCount: number;3435constructor(36@IChatEntitlementService private readonly chatEntitlementService: ChatEntitlementService,37@IInstantiationService private readonly instantiationService: IInstantiationService,38@IStatusbarService private readonly statusbarService: IStatusbarService,39@IEditorService private readonly editorService: IEditorService,40@IConfigurationService private readonly configurationService: IConfigurationService,41@IInlineCompletionsService private readonly completionsService: IInlineCompletionsService,42@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,43) {44super();4546this.runningSessionsCount = this.chatSessionsService.getInProgress().reduce((total, item) => total + item.count, 0);4748this.update();4950this.registerListeners();51}5253private update(): void {54const sentiment = this.chatEntitlementService.sentiment;55if (!sentiment.hidden) {56const props = this.getEntryProps();57if (this.entry) {58this.entry.update(props);59} else {60this.entry = this.statusbarService.addEntry(props, 'chat.statusBarEntry', StatusbarAlignment.RIGHT, { location: { id: 'status.editor.mode', priority: 100.1 }, alignment: StatusbarAlignment.RIGHT });61}62} else {63this.entry?.dispose();64this.entry = undefined;65}66}6768private registerListeners(): void {69this._register(this.chatEntitlementService.onDidChangeQuotaExceeded(() => this.update()));70this._register(this.chatEntitlementService.onDidChangeSentiment(() => this.update()));71this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.update()));7273this._register(this.completionsService.onDidChangeIsSnoozing(() => this.update()));7475this._register(this.chatSessionsService.onDidChangeInProgress(() => {76const oldSessionsCount = this.runningSessionsCount;77this.runningSessionsCount = this.chatSessionsService.getInProgress().reduce((total, item) => total + item.count, 0);78if (this.runningSessionsCount !== oldSessionsCount) {79this.update();80}81}));8283this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange()));8485this._register(this.configurationService.onDidChangeConfiguration(e => {86if (e.affectsConfiguration(product.defaultChatAgent?.completionsEnablementSetting)) {87this.update();88}89}));90}9192private onDidActiveEditorChange(): void {93this.update();9495this.activeCodeEditorListener.clear();9697// Listen to language changes in the active code editor98const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);99if (activeCodeEditor) {100this.activeCodeEditorListener.value = activeCodeEditor.onDidChangeModelLanguage(() => {101this.update();102});103}104}105106private getEntryProps(): IStatusbarEntry {107let text = '$(copilot)';108let ariaLabel = localize('chatStatusAria', "Copilot status");109let kind: StatusbarEntryKind | undefined;110111if (isNewUser(this.chatEntitlementService)) {112const entitlement = this.chatEntitlementService.entitlement;113114// Finish Setup115if (116this.chatEntitlementService.sentiment.later || // user skipped setup117entitlement === ChatEntitlement.Available || // user is entitled118isProUser(entitlement) || // user is already pro119entitlement === ChatEntitlement.Free // user is already free120) {121const finishSetup = localize('finishSetup', "Finish Setup");122123text = `$(copilot) ${finishSetup}`;124ariaLabel = finishSetup;125kind = 'prominent';126}127} else {128const chatQuotaExceeded = this.chatEntitlementService.quotas.chat?.percentRemaining === 0;129const completionsQuotaExceeded = this.chatEntitlementService.quotas.completions?.percentRemaining === 0;130131// Disabled132if (this.chatEntitlementService.sentiment.disabled || this.chatEntitlementService.sentiment.untrusted) {133text = '$(copilot-unavailable)';134ariaLabel = localize('copilotDisabledStatus', "Copilot disabled");135}136137// Sessions in progress138else if (this.runningSessionsCount > 0) {139text = '$(copilot-in-progress)';140if (this.runningSessionsCount > 1) {141ariaLabel = localize('chatSessionsInProgressStatus', "{0} agent sessions in progress", this.runningSessionsCount);142} else {143ariaLabel = localize('chatSessionInProgressStatus', "1 agent session in progress");144}145}146147// Signed out148else if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) {149const signedOutWarning = localize('notSignedIn', "Signed out");150151text = `${this.chatEntitlementService.anonymous ? '$(copilot)' : '$(copilot-not-connected)'} ${signedOutWarning}`;152ariaLabel = signedOutWarning;153kind = 'prominent';154}155156// Free Quota Exceeded157else if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && (chatQuotaExceeded || completionsQuotaExceeded)) {158let quotaWarning: string;159if (chatQuotaExceeded && !completionsQuotaExceeded) {160quotaWarning = localize('chatQuotaExceededStatus', "Chat quota reached");161} else if (completionsQuotaExceeded && !chatQuotaExceeded) {162quotaWarning = localize('completionsQuotaExceededStatus', "Inline suggestions quota reached");163} else {164quotaWarning = localize('chatAndCompletionsQuotaExceededStatus', "Quota reached");165}166167text = `$(copilot-warning) ${quotaWarning}`;168ariaLabel = quotaWarning;169kind = 'prominent';170}171172// Completions Disabled173else if (this.editorService.activeTextEditorLanguageId && !isCompletionsEnabled(this.configurationService, this.editorService.activeTextEditorLanguageId)) {174text = '$(copilot-unavailable)';175ariaLabel = localize('completionsDisabledStatus', "Inline suggestions disabled");176}177178// Completions Snoozed179else if (this.completionsService.isSnoozing()) {180text = '$(copilot-snooze)';181ariaLabel = localize('completionsSnoozedStatus', "Inline suggestions snoozed");182}183}184185const baseResult = {186name: localize('chatStatus', "Copilot Status"),187text,188ariaLabel,189command: ShowTooltipCommand,190showInAllWindows: true,191kind,192tooltip: {193element: (token: CancellationToken) => {194const store = new DisposableStore();195store.add(token.onCancellationRequested(() => {196store.dispose();197}));198const elem = ChatStatusDashboard.instantiateInContents(this.instantiationService, store);199200// todo@connor4312/@benibenj: workaround for #257923201store.add(disposableWindowInterval(mainWindow, () => {202if (!elem.isConnected) {203store.dispose();204}205}, 2000));206207return elem;208}209}210} satisfies IStatusbarEntry;211212return baseResult;213}214215override dispose(): void {216super.dispose();217218this.entry?.dispose();219this.entry = undefined;220}221}222223224