11import type { CancellationToken , Disposable , Event , MessageItem , ProgressOptions } from 'vscode' ;
2- import { env , EventEmitter , window } from 'vscode' ;
2+ import { env , EventEmitter , ThemeIcon , window } from 'vscode' ;
33import type { AIPrimaryProviders , AIProviderAndModel , AIProviders , SupportedAIModels } from '../../constants.ai' ;
4- import { primaryAIProviders } from '../../constants.ai' ;
4+ import { aiProviderDataDisclaimer , primaryAIProviders } from '../../constants.ai' ;
55import type { AIGenerateDraftEventData , Source , TelemetryEvents } from '../../constants.telemetry' ;
66import type { Container } from '../../container' ;
77import { CancellationError } from '../../errors' ;
@@ -13,7 +13,7 @@ import type { GitRevisionReference } from '../../git/models/reference';
1313import type { Repository } from '../../git/models/repository' ;
1414import { uncommitted , uncommittedStaged } from '../../git/models/revision' ;
1515import { assertsCommitHasFullDetails } from '../../git/utils/commit.utils' ;
16- import { showAIModelPicker } from '../../quickpicks/aiModelPicker' ;
16+ import { showAIModelPicker , showAIProviderPicker } from '../../quickpicks/aiModelPicker' ;
1717import { configuration } from '../../system/-webview/configuration' ;
1818import { getContext } from '../../system/-webview/context' ;
1919import type { Storage } from '../../system/-webview/storage' ;
@@ -25,7 +25,7 @@ import { lazy } from '../../system/lazy';
2525import type { Deferred } from '../../system/promise' ;
2626import { getSettledValue } from '../../system/promise' ;
2727import type { ServerConnection } from '../gk/serverConnection' ;
28- import { ensureFeatureAccess } from '../gk/utils/-webview/acount.utils' ;
28+ import { ensureAccountQuickPick , ensureFeatureAccess } from '../gk/utils/-webview/acount.utils' ;
2929import type { AIActionType , AIModel , AIModelDescriptor } from './models/model' ;
3030import type { PromptTemplateContext } from './models/promptTemplates' ;
3131import type { AIProvider , AIRequestResult } from './models/provider' ;
@@ -130,6 +130,14 @@ export class AIProviderService implements Disposable {
130130 return this . _provider ?. id ;
131131 }
132132
133+ get supportedProviders ( ) : readonly AIProviders [ ] {
134+ return [ ..._supportedProviderTypes . keys ( ) ] ;
135+ }
136+
137+ get currentModelName ( ) : string | undefined {
138+ return this . _model ?. name ;
139+ }
140+
133141 private getConfiguredModel ( ) : AIModelDescriptor | undefined {
134142 const qualifiedModelId = configuration . get ( 'ai.model' ) ?? undefined ;
135143 if ( qualifiedModelId == null ) return undefined ;
@@ -188,10 +196,48 @@ export class AIProviderService implements Disposable {
188196
189197 if ( options ?. silent ) return undefined ;
190198
191- const pick = await showAIModelPicker ( this . container , cfg ) ;
192- if ( pick == null ) return undefined ;
199+ let chosenProvider : AIProviders | undefined = undefined ;
200+ let chosenModel : AIModel | undefined = undefined ;
201+
202+ if ( ! options ?. force ) {
203+ const vsCodeModels = await this . getModels ( 'vscode' ) ;
204+ if ( vsCodeModels . length !== 0 ) {
205+ chosenProvider = 'vscode' ;
206+ } else if ( ( await this . container . subscription . getSubscription ( ) ) . account ?. verified ) {
207+ chosenProvider = 'gitkraken' ;
208+ const gitkrakenModels = await this . getModels ( 'gitkraken' ) ;
209+ chosenModel = gitkrakenModels . find ( m => m . default ) ;
210+ }
211+ }
212+
213+ if ( chosenProvider == null ) {
214+ chosenProvider = ( await showAIProviderPicker ( this . container , cfg ) ) ?. provider ;
215+ if ( chosenProvider == null ) return ;
216+ if (
217+ ( chosenProvider === 'gitkraken' ||
218+ ( chosenProvider !== 'vscode' &&
219+ ( await this . container . storage . getSecret ( `gitlens.${ chosenProvider } .key` ) ) == null ) ) &&
220+ ! ( await ensureAccountQuickPick (
221+ this . container ,
222+ {
223+ label : 'Use AI-powered GitLens features like Generate Commit Message, Explain Commit, and more.' ,
224+ iconPath : new ThemeIcon ( 'sparkle' ) ,
225+ } ,
226+ source ,
227+ ) )
228+ ) {
229+ return ;
230+ }
231+ }
193232
194- const model = await this . getOrUpdateModel ( pick . model ) ;
233+ if ( ! ( await this . ensureProviderConfigured ( chosenProvider ) ) ) return ;
234+
235+ if ( chosenModel == null ) {
236+ chosenModel = ( await showAIModelPicker ( this . container , chosenProvider , cfg ) ) ?. model ;
237+ if ( chosenModel == null ) return ;
238+ }
239+
240+ const model = await this . getOrUpdateModel ( chosenModel ) ;
195241
196242 this . container . telemetry . sendEvent (
197243 'ai/switchModel' ,
@@ -205,9 +251,28 @@ export class AIProviderService implements Disposable {
205251 source ,
206252 ) ;
207253
254+ await showAIProviderToS ( this . container . storage ) ;
208255 return model ;
209256 }
210257
258+ private async ensureProviderConfigured ( providerId : AIProviders ) : Promise < boolean > {
259+ const key = await this . container . storage . getSecret ( `gitlens.${ providerId } .key` ) ;
260+ if ( key != null ) return true ;
261+
262+ if ( this . _provider != null && providerId === this . _provider . id ) return this . _provider . ensureConfigured ( ) ;
263+ const type = await _supportedProviderTypes . get ( providerId ) ?. value ;
264+ if ( type == null ) {
265+ return false ;
266+ }
267+
268+ const p = new type ( this . container , this . connection ) ;
269+ try {
270+ return await p . ensureConfigured ( ) ;
271+ } finally {
272+ p . dispose ( ) ;
273+ }
274+ }
275+
211276 private getOrUpdateModel ( model : AIModel ) : Promise < AIModel | undefined > ;
212277 private getOrUpdateModel < T extends AIProviders > ( providerId : T , modelId : string ) : Promise < AIModel | undefined > ;
213278 private async getOrUpdateModel (
@@ -547,12 +612,7 @@ export class AIProviderService implements Disposable {
547612 progress ?: ProgressOptions ;
548613 } ,
549614 ) : Promise < AIRequestResult | undefined > {
550- const { confirmed, model } = await getModelAndConfirmAIProviderToS (
551- 'diff' ,
552- source ,
553- this ,
554- this . container . storage ,
555- ) ;
615+ const { confirmed, model } = await getModelAndConfirmAIProviderToS ( source , this , this . container . storage ) ;
556616 if ( model == null ) {
557617 options ?. generating ?. cancel ( ) ;
558618 return undefined ;
@@ -643,6 +703,11 @@ export class AIProviderService implements Disposable {
643703 return changes ;
644704 }
645705
706+ async resetProvider ( provider : AIProviders ) : Promise < void > {
707+ void env . clipboard . writeText ( ( await this . container . storage . getSecret ( `gitlens.${ provider } .key` ) ) ?? '' ) ;
708+ void this . container . storage . deleteSecret ( `gitlens.${ provider } .key` ) ;
709+ }
710+
646711 async reset ( all ?: boolean ) : Promise < void > {
647712 let { _provider : provider } = this ;
648713 if ( provider == null ) {
@@ -676,11 +741,7 @@ export class AIProviderService implements Disposable {
676741 }
677742
678743 if ( provider != null && result === resetCurrent ) {
679- void env . clipboard . writeText ( ( await this . container . storage . getSecret ( `gitlens.${ provider . id } .key` ) ) ?? '' ) ;
680- void this . container . storage . deleteSecret ( `gitlens.${ provider . id } .key` ) ;
681-
682- void this . container . storage . delete ( `confirm:ai:tos:${ provider . id } ` ) ;
683- void this . container . storage . deleteWorkspace ( `confirm:ai:tos:${ provider . id } ` ) ;
744+ void this . resetProvider ( provider . id ) ;
684745 } else if ( result === resetAll ) {
685746 const keys = [ ] ;
686747 for ( const [ providerId ] of _supportedProviderTypes ) {
@@ -706,8 +767,30 @@ export class AIProviderService implements Disposable {
706767 }
707768}
708769
770+ async function showAIProviderToS ( storage : Storage ) : Promise < void > {
771+ const confirmed = storage . get ( `confirm:ai:tos` , false ) || storage . getWorkspace ( `confirm:ai:tos` , false ) ;
772+ if ( confirmed ) return ;
773+
774+ const acceptWorkspace : MessageItem = { title : 'Always for this Workspace' } ;
775+ const acceptAlways : MessageItem = { title : 'Always' } ;
776+
777+ const result = await window . showInformationMessage (
778+ aiProviderDataDisclaimer ,
779+ { modal : true } ,
780+ acceptWorkspace ,
781+ acceptAlways ,
782+ ) ;
783+
784+ if ( result === acceptWorkspace || result == null ) {
785+ void storage . storeWorkspace ( `confirm:ai:tos` , true ) . catch ( ) ;
786+ }
787+
788+ if ( result === acceptAlways ) {
789+ void storage . store ( `confirm:ai:tos` , true ) . catch ( ) ;
790+ }
791+ }
792+
709793async function getModelAndConfirmAIProviderToS (
710- confirmationType : 'data' | 'diff' ,
711794 source : Source ,
712795 service : AIProviderService ,
713796 storage : Storage ,
@@ -716,9 +799,7 @@ async function getModelAndConfirmAIProviderToS(
716799 while ( true ) {
717800 if ( model == null ) return { confirmed : false , model : model } ;
718801
719- const confirmed =
720- storage . get ( `confirm:ai:tos:${ model . provider . id } ` , false ) ||
721- storage . getWorkspace ( `confirm:ai:tos:${ model . provider . id } ` , false ) ;
802+ const confirmed = storage . get ( `confirm:ai:tos` , false ) || storage . getWorkspace ( `confirm:ai:tos` , false ) ;
722803 if ( confirmed ) return { confirmed : true , model : model } ;
723804
724805 const accept : MessageItem = { title : 'Continue' } ;
@@ -728,11 +809,7 @@ async function getModelAndConfirmAIProviderToS(
728809 const decline : MessageItem = { title : 'Cancel' , isCloseAffordance : true } ;
729810
730811 const result = await window . showInformationMessage (
731- `GitLens AI features require sending ${
732- confirmationType === 'data' ? 'data' : 'a diff of the code changes'
733- } to ${
734- model . provider . name
735- } for analysis. This may contain sensitive information.\n\nDo you want to continue?`,
812+ `${ aiProviderDataDisclaimer } \n\nDo you want to continue?` ,
736813 { modal : true } ,
737814 accept ,
738815 switchModel ,
@@ -749,12 +826,12 @@ async function getModelAndConfirmAIProviderToS(
749826 if ( result === accept ) return { confirmed : true , model : model } ;
750827
751828 if ( result === acceptWorkspace ) {
752- void storage . storeWorkspace ( `confirm:ai:tos: ${ model . provider . id } ` , true ) . catch ( ) ;
829+ void storage . storeWorkspace ( `confirm:ai:tos` , true ) . catch ( ) ;
753830 return { confirmed : true , model : model } ;
754831 }
755832
756833 if ( result === acceptAlways ) {
757- void storage . store ( `confirm:ai:tos: ${ model . provider . id } ` , true ) . catch ( ) ;
834+ void storage . store ( `confirm:ai:tos` , true ) . catch ( ) ;
758835 return { confirmed : true , model : model } ;
759836 }
760837
0 commit comments