Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupGrowthSession.ts
13406 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 { Codicon } from '../../../../../base/common/codicons.js';6import { Emitter, Event } from '../../../../../base/common/event.js';7import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';8import { URI } from '../../../../../base/common/uri.js';9import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';10import { localize, localize2 } from '../../../../../nls.js';11import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js';12import { ICommandService } from '../../../../../platform/commands/common/commands.js';13import { ILogService } from '../../../../../platform/log/common/log.js';14import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';15import { ILifecycleService, LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js';16import { ChatSessionStatus, IChatSessionItem, IChatSessionItemController, IChatSessionItemsDelta, IChatSessionsService } from '../../common/chatSessionsService.js';17import { AgentSessionProviders } from '../agentSessions/agentSessions.js';18import { IAgentSession } from '../agentSessions/agentSessionsModel.js';19import { ISessionOpenerParticipant, ISessionOpenOptions, sessionOpenerRegistry } from '../agentSessions/agentSessionsOpener.js';20import { IChatWidgetService } from '../chat.js';21import { CHAT_OPEN_ACTION_ID, IChatViewOpenOptions } from '../actions/chatActions.js';2223/**24* Core-side growth session controller that shows a single "attention needed"25* session item in the agent sessions view for anonymous/new users.26*27* When the user clicks the session, we open the chat panel (which triggers the28* anonymous setup flow). When the user opens chat at all, the badge is cleared.29*30* The session is shown at most once, tracked via a storage flag.31*/32export class GrowthSessionController extends Disposable implements IChatSessionItemController {3334static readonly STORAGE_KEY = 'chat.growthSession.dismissed';3536private static readonly SESSION_URI = URI.from({ scheme: AgentSessionProviders.Growth, path: '/growth-welcome' });3738private readonly _onDidChangeChatSessionItems = this._register(new Emitter<IChatSessionItemsDelta>());39readonly onDidChangeChatSessionItems = this._onDidChangeChatSessionItems.event;4041private readonly _onDidDismiss = this._register(new Emitter<void>());42readonly onDidDismiss: Event<void> = this._onDidDismiss.event;4344private readonly _created = Date.now();4546private _dismissed: boolean;47get isDismissed(): boolean { return this._dismissed; }4849constructor(50@IStorageService private readonly storageService: IStorageService,51@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,52@ILifecycleService private readonly lifecycleService: ILifecycleService,53@ILogService private readonly logService: ILogService,54) {55super();5657this._dismissed = this.storageService.getBoolean(GrowthSessionController.STORAGE_KEY, StorageScope.APPLICATION, false);5859// Dismiss the growth session when the user opens chat.60// Wait until the workbench is fully restored so we skip widgets61// that were restored from a previous session at startup.62this.lifecycleService.when(LifecyclePhase.Restored).then(() => {63if (this._store.isDisposed || this._dismissed) {64return;65}66this._register(this.chatWidgetService.onDidAddWidget(() => {67this.dismiss();68}));69});70}7172get items(): readonly IChatSessionItem[] {73if (this._dismissed) {74return [];75}7677return [{78resource: GrowthSessionController.SESSION_URI,79label: localize('growthSession.label', "Try Copilot"),80description: localize('growthSession.description', "GitHub Copilot is available. Try it for free."),81status: ChatSessionStatus.NeedsInput,82iconPath: Codicon.lightbulb,83timing: {84created: this._created,85lastRequestStarted: undefined,86lastRequestEnded: undefined,87},88}];89}9091async refresh(): Promise<void> {92// Nothing to refresh -- this is a static, local-only session item93}9495private dismiss(): void {96if (this._dismissed) {97return;98}99100this.logService.trace('[GrowthSession] Dismissing growth session');101this._dismissed = true;102this.storageService.store(GrowthSessionController.STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER);103104// Fire change event first so that listeners (like the model) see empty items105this._onDidChangeChatSessionItems.fire({106removed: [GrowthSessionController.SESSION_URI],107});108// Then fire dismiss event which triggers unregistration of the controller.109this._onDidDismiss.fire();110}111}112113/**114* Handles clicks on the growth session item in the agent sessions view.115* Opens a new local chat session with a pre-seeded welcome message.116* The user can then send messages that go through the normal agent.117*/118export class GrowthSessionOpenerParticipant implements ISessionOpenerParticipant {119120async handleOpenSession(accessor: ServicesAccessor, session: IAgentSession, _openOptions?: ISessionOpenOptions): Promise<boolean> {121if (session.providerType !== AgentSessionProviders.Growth) {122return false;123}124125const commandService = accessor.get(ICommandService);126const opts: IChatViewOpenOptions = {127query: '',128isPartialQuery: true,129previousRequests: [{130request: localize('growthSession.previousRequest', "Tell me about GitHub Copilot!"),131// allow-any-unicode-next-line132response: localize('growthSession.previousResponse', "Welcome to GitHub Copilot, your AI coding assistant! Here are some things you can try:\n\n- ๐ *\"Help me debug this error\"* โ paste an error message and get a fix\n- ๐งช *\"Write tests for my function\"* โ select code and ask for unit tests\n- ๐ก *\"Explain this code\"* โ highlight something unfamiliar and ask what it does\n- ๐ *\"Scaffold a REST API\"* โ describe what you want and let Agent mode build it\n- ๐จ *\"Refactor this to be more readable\"* โ select messy code and clean it up\n\nType anything below to get started!"),133}],134};135await commandService.executeCommand(CHAT_OPEN_ACTION_ID, opts);136return true;137}138}139140/**141* Registers the growth session controller and opener participant.142* Returns a disposable that cleans up all registrations.143*/144export function registerGrowthSession(chatSessionsService: IChatSessionsService, growthController: GrowthSessionController): IDisposable {145const disposables = new DisposableStore();146147// Register as session item controller so it appears in the sessions view148disposables.add(chatSessionsService.registerChatSessionItemController(AgentSessionProviders.Growth, growthController));149150// Register opener participant so clicking the growth session opens chat151disposables.add(sessionOpenerRegistry.registerParticipant(new GrowthSessionOpenerParticipant()));152153return disposables;154}155156// #region Developer Actions157158registerAction2(class ResetGrowthSessionAction extends Action2 {159constructor() {160super({161id: 'workbench.action.chat.resetGrowthSession',162title: localize2('resetGrowthSession', "Reset Growth Session Notification"),163category: localize2('developer', "Developer"),164f1: true,165});166}167168run(accessor: ServicesAccessor): void {169const storageService = accessor.get(IStorageService);170storageService.remove(GrowthSessionController.STORAGE_KEY, StorageScope.APPLICATION);171}172});173174// #endregion175176177