@@ -7,23 +7,23 @@ import './media/chatViewSetup.css';
7
7
import { $ , getActiveElement , setVisibility } from '../../../../base/browser/dom.js' ;
8
8
import { Button , ButtonWithDropdown } from '../../../../base/browser/ui/button/button.js' ;
9
9
import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js' ;
10
- import { IAction , toAction } from '../../../../base/common/actions.js' ;
10
+ import { IAction , toAction , WorkbenchActionExecutedClassification , WorkbenchActionExecutedEvent } from '../../../../base/common/actions.js' ;
11
11
import { Barrier , timeout } from '../../../../base/common/async.js' ;
12
12
import { CancellationToken , CancellationTokenSource } from '../../../../base/common/cancellation.js' ;
13
13
import { Codicon } from '../../../../base/common/codicons.js' ;
14
14
import { isCancellationError } from '../../../../base/common/errors.js' ;
15
15
import { Emitter , Event } from '../../../../base/common/event.js' ;
16
16
import { MarkdownString } from '../../../../base/common/htmlContent.js' ;
17
17
import { Lazy } from '../../../../base/common/lazy.js' ;
18
- import { Disposable } from '../../../../base/common/lifecycle.js' ;
18
+ import { Disposable , MutableDisposable } from '../../../../base/common/lifecycle.js' ;
19
19
import { IRequestContext } from '../../../../base/parts/request/common/request.js' ;
20
20
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js' ;
21
21
import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js' ;
22
22
import { localize , localize2 } from '../../../../nls.js' ;
23
23
import { Action2 , MenuId , registerAction2 } from '../../../../platform/actions/common/actions.js' ;
24
24
import { ICommandService } from '../../../../platform/commands/common/commands.js' ;
25
25
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js' ;
26
- import { ContextKeyExpr , IContextKey , IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js' ;
26
+ import { ContextKeyExpr , IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js' ;
27
27
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js' ;
28
28
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js' ;
29
29
import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js' ;
@@ -56,6 +56,9 @@ import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID } from './chatView
56
56
import { ChatViewsWelcomeExtensions , IChatViewsWelcomeContributionRegistry } from './viewsWelcome/chatViewsWelcome.js' ;
57
57
import { IChatQuotasService } from './chatQuotasService.js' ;
58
58
import { mainWindow } from '../../../../base/browser/window.js' ;
59
+ import { IOpenerService } from '../../../../platform/opener/common/opener.js' ;
60
+ import { URI } from '../../../../base/common/uri.js' ;
61
+ import { IHostService } from '../../../services/host/browser/host.js' ;
59
62
60
63
const defaultChat = {
61
64
extensionId : product . defaultChatAgent ?. extensionId ?? '' ,
@@ -64,6 +67,7 @@ const defaultChat = {
64
67
termsStatementUrl : product . defaultChatAgent ?. termsStatementUrl ?? '' ,
65
68
privacyStatementUrl : product . defaultChatAgent ?. privacyStatementUrl ?? '' ,
66
69
skusDocumentationUrl : product . defaultChatAgent ?. skusDocumentationUrl ?? '' ,
70
+ upgradePlanUrl : product . defaultChatAgent ?. upgradePlanUrl ?? '' ,
67
71
providerId : product . defaultChatAgent ?. providerId ?? '' ,
68
72
providerName : product . defaultChatAgent ?. providerName ?? '' ,
69
73
providerScopes : product . defaultChatAgent ?. providerScopes ?? [ [ ] ] ,
@@ -231,6 +235,60 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
231
235
}
232
236
}
233
237
238
+ const windowFocusListener = this . _register ( new MutableDisposable ( ) ) ;
239
+ class UpgradePlanAction extends Action2 {
240
+ constructor ( ) {
241
+ super ( {
242
+ id : 'workbench.action.chat.upgradePlan' ,
243
+ title : localize2 ( 'managePlan' , "Upgrade to Copilot Pro" ) ,
244
+ category : localize2 ( 'chat.category' , 'Chat' ) ,
245
+ f1 : true ,
246
+ precondition : ChatContextKeys . enabled ,
247
+ menu : {
248
+ id : MenuId . ChatCommandCenter ,
249
+ group : 'a_first' ,
250
+ order : 1 ,
251
+ when : ContextKeyExpr . and (
252
+ ChatContextKeys . Setup . installed ,
253
+ ContextKeyExpr . or (
254
+ ChatContextKeys . chatQuotaExceeded ,
255
+ ChatContextKeys . completionsQuotaExceeded
256
+ )
257
+ )
258
+ }
259
+ } ) ;
260
+ }
261
+
262
+ override async run ( accessor : ServicesAccessor ) : Promise < void > {
263
+ const openerService = accessor . get ( IOpenerService ) ;
264
+ const telemetryService = accessor . get ( ITelemetryService ) ;
265
+ const hostService = accessor . get ( IHostService ) ;
266
+ const commandService = accessor . get ( ICommandService ) ;
267
+
268
+ telemetryService . publicLog2 < WorkbenchActionExecutedEvent , WorkbenchActionExecutedClassification > ( 'workbenchActionExecuted' , { id : this . desc . id , from : 'chat' } ) ;
269
+
270
+ openerService . open ( URI . parse ( defaultChat . upgradePlanUrl ) ) ;
271
+
272
+ const entitlement = that . context . state . entitlement ;
273
+ if ( entitlement !== ChatEntitlement . Pro ) {
274
+ // If the user is not yet Pro, we listen to window focus to refresh the token
275
+ // when the user has come back to the window assuming the user signed up.
276
+ windowFocusListener . value = hostService . onDidChangeFocus ( focus => this . onWindowFocus ( focus , commandService ) ) ;
277
+ }
278
+ }
279
+
280
+ private async onWindowFocus ( focus : boolean , commandService : ICommandService ) : Promise < void > {
281
+ if ( focus ) {
282
+ windowFocusListener . clear ( ) ;
283
+
284
+ const entitlement = await that . requests . forceResolveEntitlement ( undefined ) ;
285
+ if ( entitlement === ChatEntitlement . Pro ) {
286
+ commandService . executeCommand ( 'github.copilot.refreshToken' ) ; // ugly, but we need to signal to the extension that entitlements changed
287
+ }
288
+ }
289
+ }
290
+ }
291
+
234
292
async function hideSetupView ( viewsDescriptorService : IViewDescriptorService , layoutService : IWorkbenchLayoutService ) : Promise < void > {
235
293
const location = viewsDescriptorService . getViewLocationById ( ChatViewId ) ;
236
294
@@ -246,6 +304,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
246
304
247
305
registerAction2 ( ChatSetupTriggerAction ) ;
248
306
registerAction2 ( ChatSetupHideAction ) ;
307
+ registerAction2 ( UpgradePlanAction ) ;
249
308
}
250
309
}
251
310
@@ -507,7 +566,15 @@ class ChatSetupRequests extends Disposable {
507
566
}
508
567
}
509
568
510
- async forceResolveEntitlement ( session : AuthenticationSession ) : Promise < ChatEntitlement | undefined > {
569
+ async forceResolveEntitlement ( session : AuthenticationSession | undefined ) : Promise < ChatEntitlement | undefined > {
570
+ if ( ! session ) {
571
+ session = await this . findMatchingProviderSession ( CancellationToken . None ) ;
572
+ }
573
+
574
+ if ( ! session ) {
575
+ return undefined ;
576
+ }
577
+
511
578
return this . resolveEntitlement ( session , CancellationToken . None ) ;
512
579
}
513
580
@@ -798,23 +865,18 @@ class ChatSetupWelcomeContent extends Disposable {
798
865
}
799
866
800
867
// Limited SKU
801
- const limitedSkuHeader = localize ( { key : 'limitedSkuHeader' , comment : [ '{Locked="[]({0})"}' ] } , "$(sparkle-filled) We now offer [Copilot for free]({0}) with 50 chat messages and 2000 code completions per month." , defaultChat . skusDocumentationUrl ) ;
868
+ const limitedSkuHeader = localize ( { key : 'limitedSkuHeader' , comment : [ '{Locked="[]({0})"}' ] } , "$(sparkle-filled) We now offer [Copilot for free]({0}) with 2,000 code completions and 50 chat messages per month." , defaultChat . skusDocumentationUrl ) ;
802
869
const limitedSkuHeaderContainer = this . element . appendChild ( $ ( 'p' ) ) ;
803
870
limitedSkuHeaderContainer . appendChild ( this . _register ( markdown . render ( new MarkdownString ( limitedSkuHeader , { isTrusted : true , supportThemeIcons : true } ) ) ) . element ) ;
804
871
805
- // Terms
806
- const terms = localize ( { key : 'termsLabel' , comment : [ '{Locked="["}' , '{Locked="]({0})"}' , '{Locked="]({1})"}' ] } , "By continuing, you agree to our [Terms]({0}) and [Privacy Policy]({1})." , defaultChat . termsStatementUrl , defaultChat . privacyStatementUrl ) ;
807
- const termsContainer = this . element . appendChild ( $ ( 'p' ) ) ;
808
- termsContainer . classList . add ( 'terms-container' ) ;
809
- termsContainer . appendChild ( this . _register ( markdown . render ( new MarkdownString ( terms , { isTrusted : true } ) ) ) . element ) ;
810
-
811
872
// Setup Button
812
873
const actions : IAction [ ] = [ ] ;
813
874
if ( this . context . state . installed ) {
814
875
actions . push ( toAction ( { id : 'chatSetup.signInGh' , label : localize ( 'signInGh' , "Sign in with a GitHub.com Account" ) , run : ( ) => this . commandService . executeCommand ( 'github.copilotChat.signIn' ) } ) ) ;
815
876
actions . push ( toAction ( { id : 'chatSetup.signInGhe' , label : localize ( 'signInGhe' , "Sign in with a GHE.com Account" ) , run : ( ) => this . commandService . executeCommand ( 'github.copilotChat.signInGHE' ) } ) ) ;
816
877
}
817
878
const buttonContainer = this . element . appendChild ( $ ( 'p' ) ) ;
879
+ buttonContainer . classList . add ( 'button-container' ) ;
818
880
const button = this . _register ( actions . length === 0 ? new Button ( buttonContainer , {
819
881
supportIcons : true ,
820
882
...defaultButtonStyles
@@ -827,6 +889,10 @@ class ChatSetupWelcomeContent extends Disposable {
827
889
} ) ) ;
828
890
this . _register ( button . onDidClick ( ( ) => this . controller . setup ( ) ) ) ;
829
891
892
+ // Terms
893
+ const terms = localize ( { key : 'termsLabel' , comment : [ '{Locked="["}' , '{Locked="]({0})"}' , '{Locked="]({1})"}' ] } , "By continuing, you agree to the [Terms]({0}) and [Privacy Policy]({1})." , defaultChat . termsStatementUrl , defaultChat . privacyStatementUrl ) ;
894
+ this . element . appendChild ( $ ( 'p' ) ) . appendChild ( this . _register ( markdown . render ( new MarkdownString ( terms , { isTrusted : true } ) ) ) . element ) ;
895
+
830
896
// Update based on model state
831
897
this . _register ( Event . runAndSubscribe ( this . controller . onDidChange , ( ) => this . update ( limitedSkuHeaderContainer , button ) ) ) ;
832
898
}
@@ -989,23 +1055,13 @@ class ChatSetupContext extends Disposable {
989
1055
this . storageService . remove ( 'interactive.sessions' , this . workspaceContextService . getWorkspace ( ) . folders . length ? StorageScope . WORKSPACE : StorageScope . APPLICATION ) ;
990
1056
}
991
1057
992
- let changed = false ;
993
- changed = this . updateContextKey ( this . signedOutContextKey , this . _state . entitlement === ChatEntitlement . Unknown ) || changed ;
994
- changed = this . updateContextKey ( this . canSignUpContextKey , this . _state . entitlement === ChatEntitlement . Available ) || changed ;
995
- changed = this . updateContextKey ( this . limitedContextKey , this . _state . entitlement === ChatEntitlement . Limited ) || changed ;
996
- changed = this . updateContextKey ( this . triggeredContext , ! ! this . _state . triggered ) || changed ;
997
- changed = this . updateContextKey ( this . installedContext , ! ! this . _state . installed ) || changed ;
1058
+ this . signedOutContextKey . set ( this . _state . entitlement === ChatEntitlement . Unknown ) ;
1059
+ this . canSignUpContextKey . set ( this . _state . entitlement === ChatEntitlement . Available ) ;
1060
+ this . limitedContextKey . set ( this . _state . entitlement === ChatEntitlement . Limited ) ;
1061
+ this . triggeredContext . set ( ! ! this . _state . triggered ) ;
1062
+ this . installedContext . set ( ! ! this . _state . installed ) ;
998
1063
999
- if ( changed ) {
1000
- this . _onDidChange . fire ( ) ;
1001
- }
1002
- }
1003
-
1004
- private updateContextKey ( contextKey : IContextKey < boolean > , value : boolean ) : boolean {
1005
- const current = contextKey . get ( ) ;
1006
- contextKey . set ( value ) ;
1007
-
1008
- return current !== value ;
1064
+ this . _onDidChange . fire ( ) ;
1009
1065
}
1010
1066
1011
1067
suspend ( ) : void {
0 commit comments