Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupGrowthSession.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Codicon } from '../../../../../base/common/codicons.js';
7
import { Emitter, Event } from '../../../../../base/common/event.js';
8
import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
9
import { URI } from '../../../../../base/common/uri.js';
10
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
11
import { localize, localize2 } from '../../../../../nls.js';
12
import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js';
13
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
14
import { ILogService } from '../../../../../platform/log/common/log.js';
15
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
16
import { ILifecycleService, LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js';
17
import { ChatSessionStatus, IChatSessionItem, IChatSessionItemController, IChatSessionItemsDelta, IChatSessionsService } from '../../common/chatSessionsService.js';
18
import { AgentSessionProviders } from '../agentSessions/agentSessions.js';
19
import { IAgentSession } from '../agentSessions/agentSessionsModel.js';
20
import { ISessionOpenerParticipant, ISessionOpenOptions, sessionOpenerRegistry } from '../agentSessions/agentSessionsOpener.js';
21
import { IChatWidgetService } from '../chat.js';
22
import { CHAT_OPEN_ACTION_ID, IChatViewOpenOptions } from '../actions/chatActions.js';
23
24
/**
25
* Core-side growth session controller that shows a single "attention needed"
26
* session item in the agent sessions view for anonymous/new users.
27
*
28
* When the user clicks the session, we open the chat panel (which triggers the
29
* anonymous setup flow). When the user opens chat at all, the badge is cleared.
30
*
31
* The session is shown at most once, tracked via a storage flag.
32
*/
33
export class GrowthSessionController extends Disposable implements IChatSessionItemController {
34
35
static readonly STORAGE_KEY = 'chat.growthSession.dismissed';
36
37
private static readonly SESSION_URI = URI.from({ scheme: AgentSessionProviders.Growth, path: '/growth-welcome' });
38
39
private readonly _onDidChangeChatSessionItems = this._register(new Emitter<IChatSessionItemsDelta>());
40
readonly onDidChangeChatSessionItems = this._onDidChangeChatSessionItems.event;
41
42
private readonly _onDidDismiss = this._register(new Emitter<void>());
43
readonly onDidDismiss: Event<void> = this._onDidDismiss.event;
44
45
private readonly _created = Date.now();
46
47
private _dismissed: boolean;
48
get isDismissed(): boolean { return this._dismissed; }
49
50
constructor(
51
@IStorageService private readonly storageService: IStorageService,
52
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
53
@ILifecycleService private readonly lifecycleService: ILifecycleService,
54
@ILogService private readonly logService: ILogService,
55
) {
56
super();
57
58
this._dismissed = this.storageService.getBoolean(GrowthSessionController.STORAGE_KEY, StorageScope.APPLICATION, false);
59
60
// Dismiss the growth session when the user opens chat.
61
// Wait until the workbench is fully restored so we skip widgets
62
// that were restored from a previous session at startup.
63
this.lifecycleService.when(LifecyclePhase.Restored).then(() => {
64
if (this._store.isDisposed || this._dismissed) {
65
return;
66
}
67
this._register(this.chatWidgetService.onDidAddWidget(() => {
68
this.dismiss();
69
}));
70
});
71
}
72
73
get items(): readonly IChatSessionItem[] {
74
if (this._dismissed) {
75
return [];
76
}
77
78
return [{
79
resource: GrowthSessionController.SESSION_URI,
80
label: localize('growthSession.label', "Try Copilot"),
81
description: localize('growthSession.description', "GitHub Copilot is available. Try it for free."),
82
status: ChatSessionStatus.NeedsInput,
83
iconPath: Codicon.lightbulb,
84
timing: {
85
created: this._created,
86
lastRequestStarted: undefined,
87
lastRequestEnded: undefined,
88
},
89
}];
90
}
91
92
async refresh(): Promise<void> {
93
// Nothing to refresh -- this is a static, local-only session item
94
}
95
96
private dismiss(): void {
97
if (this._dismissed) {
98
return;
99
}
100
101
this.logService.trace('[GrowthSession] Dismissing growth session');
102
this._dismissed = true;
103
this.storageService.store(GrowthSessionController.STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.USER);
104
105
// Fire change event first so that listeners (like the model) see empty items
106
this._onDidChangeChatSessionItems.fire({
107
removed: [GrowthSessionController.SESSION_URI],
108
});
109
// Then fire dismiss event which triggers unregistration of the controller.
110
this._onDidDismiss.fire();
111
}
112
}
113
114
/**
115
* Handles clicks on the growth session item in the agent sessions view.
116
* Opens a new local chat session with a pre-seeded welcome message.
117
* The user can then send messages that go through the normal agent.
118
*/
119
export class GrowthSessionOpenerParticipant implements ISessionOpenerParticipant {
120
121
async handleOpenSession(accessor: ServicesAccessor, session: IAgentSession, _openOptions?: ISessionOpenOptions): Promise<boolean> {
122
if (session.providerType !== AgentSessionProviders.Growth) {
123
return false;
124
}
125
126
const commandService = accessor.get(ICommandService);
127
const opts: IChatViewOpenOptions = {
128
query: '',
129
isPartialQuery: true,
130
previousRequests: [{
131
request: localize('growthSession.previousRequest', "Tell me about GitHub Copilot!"),
132
// allow-any-unicode-next-line
133
response: 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!"),
134
}],
135
};
136
await commandService.executeCommand(CHAT_OPEN_ACTION_ID, opts);
137
return true;
138
}
139
}
140
141
/**
142
* Registers the growth session controller and opener participant.
143
* Returns a disposable that cleans up all registrations.
144
*/
145
export function registerGrowthSession(chatSessionsService: IChatSessionsService, growthController: GrowthSessionController): IDisposable {
146
const disposables = new DisposableStore();
147
148
// Register as session item controller so it appears in the sessions view
149
disposables.add(chatSessionsService.registerChatSessionItemController(AgentSessionProviders.Growth, growthController));
150
151
// Register opener participant so clicking the growth session opens chat
152
disposables.add(sessionOpenerRegistry.registerParticipant(new GrowthSessionOpenerParticipant()));
153
154
return disposables;
155
}
156
157
// #region Developer Actions
158
159
registerAction2(class ResetGrowthSessionAction extends Action2 {
160
constructor() {
161
super({
162
id: 'workbench.action.chat.resetGrowthSession',
163
title: localize2('resetGrowthSession', "Reset Growth Session Notification"),
164
category: localize2('developer', "Developer"),
165
f1: true,
166
});
167
}
168
169
run(accessor: ServicesAccessor): void {
170
const storageService = accessor.get(IStorageService);
171
storageService.remove(GrowthSessionController.STORAGE_KEY, StorageScope.APPLICATION);
172
}
173
});
174
175
// #endregion
176
177