From 35a0729e0700fe591ea72e7712933e526ee55e2a Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Mon, 1 Dec 2025 17:19:36 -0800 Subject: [PATCH] update status widget configuration to support SKU-based visibility (#280478) --- .../contrib/chat/browser/chat.contribution.ts | 11 ++- .../contrib/chat/browser/chatStatusWidget.ts | 68 +++++++------------ 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index a8a857c8da2..466c01e8cd2 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -536,10 +536,15 @@ configurationRegistry.registerConfiguration({ default: true, tags: ['experimental'], }, - ['chat.statusWidget.enabled']: { - type: 'boolean', + ['chat.statusWidget.sku']: { + type: 'string', + enum: ['free', 'anonymous'], + enumDescriptions: [ + nls.localize('chat.statusWidget.sku.free', "Show status widget for free tier users."), + nls.localize('chat.statusWidget.sku.anonymous', "Show status widget for anonymous users.") + ], description: nls.localize('chat.statusWidget.enabled.description', "Show the status widget in new chat sessions when quota is exceeded."), - default: false, + default: undefined, tags: ['experimental'], included: false, experiment: { diff --git a/src/vs/workbench/contrib/chat/browser/chatStatusWidget.ts b/src/vs/workbench/contrib/chat/browser/chatStatusWidget.ts index 2e21cd6cfea..fac7e6e755b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatusWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatusWidget.ts @@ -8,17 +8,15 @@ import * as dom from '../../../../base/browser/dom.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { localize } from '../../../../nls.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; -import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js'; -import { ChatEntitlement, ChatEntitlementContextKeys, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { ChatInputPartWidgetsRegistry, IChatInputPartWidget } from './chatInputPartWidgets.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { CHAT_SETUP_ACTION_ID } from './actions/chatActions.js'; const $ = dom.$; @@ -37,7 +35,6 @@ export class ChatStatusWidget extends Disposable implements IChatInputPartWidget private messageElement: HTMLElement | undefined; private actionButton: Button | undefined; - private _isEnabled = false; constructor( @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, @@ -52,32 +49,28 @@ export class ChatStatusWidget extends Disposable implements IChatInputPartWidget } private initializeIfEnabled(): void { - const isEnabled = this.configurationService.getValue('chat.statusWidget.enabled'); - if (!isEnabled) { + const enabledSku = this.configurationService.getValue('chat.statusWidget.sku'); + if (enabledSku !== 'free' && enabledSku !== 'anonymous') { return; } - this._isEnabled = true; - if (!this.chatEntitlementService.isInternal) { - return; - } - this.createWidgetContent(); - this.updateContent(); + this.createWidgetContent(enabledSku); + this.updateContent(enabledSku); this.domNode.style.display = ''; this._register(this.chatEntitlementService.onDidChangeEntitlement(() => { - this.updateContent(); + this.updateContent(enabledSku); })); this._onDidChangeHeight.fire(); } get height(): number { - return this._isEnabled ? this.domNode.offsetHeight : 0; + return this.domNode.offsetHeight; } - private createWidgetContent(): void { + private createWidgetContent(testSku: 'free' | 'anonymous'): void { const contentContainer = $('.chat-status-content'); this.messageElement = $('.chat-status-message'); contentContainer.appendChild(this.messageElement); @@ -90,9 +83,9 @@ export class ChatStatusWidget extends Disposable implements IChatInputPartWidget this.actionButton.element.classList.add('chat-status-button'); this._register(this.actionButton.onDidClick(async () => { - const commandId = this.chatEntitlementService.entitlement === ChatEntitlement.Free - ? 'workbench.action.chat.upgradePlan' - : 'workbench.action.chat.manageOverages'; + const commandId = this.chatEntitlementService.anonymous + ? CHAT_SETUP_ACTION_ID + : 'workbench.action.chat.upgradePlan'; await this.commandService.executeCommand(commandId); })); @@ -100,37 +93,26 @@ export class ChatStatusWidget extends Disposable implements IChatInputPartWidget this.domNode.appendChild(actionContainer); } - private updateContent(): void { + private updateContent(enabledSku: 'free' | 'anonymous'): void { if (!this.messageElement || !this.actionButton) { return; } - this.messageElement.textContent = localize('chat.quotaExceeded.message', "Free tier chat message limit reached."); - this.actionButton.label = localize('chat.quotaExceeded.increaseLimit', "Increase Limit"); + const entitlement = this.chatEntitlementService.entitlement; + const isAnonymous = this.chatEntitlementService.anonymous; + + if (enabledSku === 'anonymous' && (isAnonymous || entitlement === ChatEntitlement.Unknown)) { + this.messageElement.textContent = localize('chat.anonymousRateLimited.message', "You've reached the limit for chat messages. Try Copilot Pro for free."); + this.actionButton.label = localize('chat.anonymousRateLimited.signIn', "Sign In"); + } else if (enabledSku === 'free' && entitlement === ChatEntitlement.Free) { + this.messageElement.textContent = localize('chat.freeQuotaExceeded.message', "You've reached the limit for chat messages."); + this.actionButton.label = localize('chat.freeQuotaExceeded.upgrade', "Upgrade"); + } this._onDidChangeHeight.fire(); } } -// TODO@bhavyaus remove this command after testing complete with team -registerAction2(class ToggleChatQuotaExceededAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.toggleStatusWidget', - title: localize2('chat.toggleStatusWidget.label', "Toggle Chat Status Widget State"), - f1: true, - category: Categories.Developer, - precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatEntitlementContextKeys.Entitlement.internal), - }); - } - - run(accessor: ServicesAccessor): void { - const contextKeyService = accessor.get(IContextKeyService); - const currentValue = ChatEntitlementContextKeys.chatQuotaExceeded.getValue(contextKeyService) ?? false; - ChatEntitlementContextKeys.chatQuotaExceeded.bindTo(contextKeyService).set(!currentValue); - } -}); - ChatInputPartWidgetsRegistry.register( ChatStatusWidget.ID, ChatStatusWidget,