Path: blob/main/src/vs/workbench/contrib/chat/browser/chatStatus/chatStatusEntry.ts
4780 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, isCompletionsEnabled } from './chatStatus.js';22import product from '../../../../../platform/product/common/product.js';2324export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribution {2526static readonly ID = 'workbench.contrib.chatStatusBarEntry';2728private entry: IStatusbarEntryAccessor | undefined = undefined;2930private readonly activeCodeEditorListener = this._register(new MutableDisposable());3132private runningSessionsCount: number;3334constructor(35@IChatEntitlementService private readonly chatEntitlementService: ChatEntitlementService,36@IInstantiationService private readonly instantiationService: IInstantiationService,37@IStatusbarService private readonly statusbarService: IStatusbarService,38@IEditorService private readonly editorService: IEditorService,39@IConfigurationService private readonly configurationService: IConfigurationService,40@IInlineCompletionsService private readonly completionsService: IInlineCompletionsService,41@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,42) {43super();4445this.runningSessionsCount = this.chatSessionsService.getInProgress().reduce((total, item) => total + item.count, 0);4647this.update();4849this.registerListeners();50}5152private update(): void {53const sentiment = this.chatEntitlementService.sentiment;54if (!sentiment.hidden) {55const props = this.getEntryProps();56if (this.entry) {57this.entry.update(props);58} else {59this.entry = this.statusbarService.addEntry(props, 'chat.statusBarEntry', StatusbarAlignment.RIGHT, { location: { id: 'status.editor.mode', priority: 100.1 }, alignment: StatusbarAlignment.RIGHT });60}61} else {62this.entry?.dispose();63this.entry = undefined;64}65}6667private registerListeners(): void {68this._register(this.chatEntitlementService.onDidChangeQuotaExceeded(() => this.update()));69this._register(this.chatEntitlementService.onDidChangeSentiment(() => this.update()));70this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.update()));7172this._register(this.completionsService.onDidChangeIsSnoozing(() => this.update()));7374this._register(this.chatSessionsService.onDidChangeInProgress(() => {75const oldSessionsCount = this.runningSessionsCount;76this.runningSessionsCount = this.chatSessionsService.getInProgress().reduce((total, item) => total + item.count, 0);77if (this.runningSessionsCount !== oldSessionsCount) {78this.update();79}80}));8182this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange()));8384this._register(this.configurationService.onDidChangeConfiguration(e => {85if (e.affectsConfiguration(product.defaultChatAgent?.completionsEnablementSetting)) {86this.update();87}88}));89}9091private onDidActiveEditorChange(): void {92this.update();9394this.activeCodeEditorListener.clear();9596// Listen to language changes in the active code editor97const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);98if (activeCodeEditor) {99this.activeCodeEditorListener.value = activeCodeEditor.onDidChangeModelLanguage(() => {100this.update();101});102}103}104105private getEntryProps(): IStatusbarEntry {106let text = '$(copilot)';107let ariaLabel = localize('chatStatusAria', "Copilot status");108let kind: StatusbarEntryKind | undefined;109110if (isNewUser(this.chatEntitlementService)) {111const entitlement = this.chatEntitlementService.entitlement;112113// Finish Setup114if (115this.chatEntitlementService.sentiment.later || // user skipped setup116entitlement === ChatEntitlement.Available || // user is entitled117isProUser(entitlement) || // user is already pro118entitlement === ChatEntitlement.Free // user is already free119) {120const finishSetup = localize('finishSetup', "Finish Setup");121122text = `$(copilot) ${finishSetup}`;123ariaLabel = finishSetup;124kind = 'prominent';125}126} else {127const chatQuotaExceeded = this.chatEntitlementService.quotas.chat?.percentRemaining === 0;128const completionsQuotaExceeded = this.chatEntitlementService.quotas.completions?.percentRemaining === 0;129130// Disabled131if (this.chatEntitlementService.sentiment.disabled || this.chatEntitlementService.sentiment.untrusted) {132text = '$(copilot-unavailable)';133ariaLabel = localize('copilotDisabledStatus', "Copilot disabled");134}135136// Sessions in progress137else if (this.runningSessionsCount > 0) {138text = '$(copilot-in-progress)';139if (this.runningSessionsCount > 1) {140ariaLabel = localize('chatSessionsInProgressStatus', "{0} agent sessions in progress", this.runningSessionsCount);141} else {142ariaLabel = localize('chatSessionInProgressStatus', "1 agent session in progress");143}144}145146// Signed out147else if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) {148const signedOutWarning = localize('notSignedIn', "Signed out");149150text = `${this.chatEntitlementService.anonymous ? '$(copilot)' : '$(copilot-not-connected)'} ${signedOutWarning}`;151ariaLabel = signedOutWarning;152kind = 'prominent';153}154155// Free Quota Exceeded156else if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && (chatQuotaExceeded || completionsQuotaExceeded)) {157let quotaWarning: string;158if (chatQuotaExceeded && !completionsQuotaExceeded) {159quotaWarning = localize('chatQuotaExceededStatus', "Chat quota reached");160} else if (completionsQuotaExceeded && !chatQuotaExceeded) {161quotaWarning = localize('completionsQuotaExceededStatus', "Inline suggestions quota reached");162} else {163quotaWarning = localize('chatAndCompletionsQuotaExceededStatus', "Quota reached");164}165166text = `$(copilot-warning) ${quotaWarning}`;167ariaLabel = quotaWarning;168kind = 'prominent';169}170171// Completions Disabled172else if (this.editorService.activeTextEditorLanguageId && !isCompletionsEnabled(this.configurationService, this.editorService.activeTextEditorLanguageId)) {173text = '$(copilot-unavailable)';174ariaLabel = localize('completionsDisabledStatus', "Inline suggestions disabled");175}176177// Completions Snoozed178else if (this.completionsService.isSnoozing()) {179text = '$(copilot-snooze)';180ariaLabel = localize('completionsSnoozedStatus', "Inline suggestions snoozed");181}182}183184const baseResult = {185name: localize('chatStatus', "Copilot Status"),186text,187ariaLabel,188command: ShowTooltipCommand,189showInAllWindows: true,190kind,191tooltip: {192element: (token: CancellationToken) => {193const store = new DisposableStore();194store.add(token.onCancellationRequested(() => {195store.dispose();196}));197const elem = ChatStatusDashboard.instantiateInContents(this.instantiationService, store);198199// todo@connor4312/@benibenj: workaround for #257923200store.add(disposableWindowInterval(mainWindow, () => {201if (!elem.isConnected) {202store.dispose();203}204}, 2000));205206return elem;207}208}209} satisfies IStatusbarEntry;210211return baseResult;212}213214override dispose(): void {215super.dispose();216217this.entry?.dispose();218this.entry = undefined;219}220}221222223