diff --git a/contributions.json b/contributions.json index f0e097c082d83..e0dff9d872359 100644 --- a/contributions.json +++ b/contributions.json @@ -233,17 +233,17 @@ } }, "gitlens.ai.generateCommits": { - "label": "Generate Commits with AI (Experimental)...", + "label": "Generate Commits with AI (Preview)...", "icon": "$(sparkle)", - "commandPalette": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateCommits.enabled" + "commandPalette": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled" }, "gitlens.ai.generateCommits:graph": { - "label": "Generate Commits with AI (Experimental)", + "label": "Generate Commits with AI (Preview)", "icon": "$(sparkle)", "menus": { "webview/context": [ { - "when": "webviewItem == gitlens:wip && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateCommits.enabled", + "when": "webviewItem == gitlens:wip && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled", "group": "1_gitlens", "order": 2 } @@ -251,12 +251,12 @@ } }, "gitlens.ai.generateCommits:views": { - "label": "Generate Commits with AI (Experimental)", + "label": "Generate Commits with AI (Preview)", "icon": "$(sparkle)", "menus": { "view/item/context": [ { - "when": "viewItem =~ /gitlens:(worktree\\b(?=.*?\\b\\+working\\b)|uncommitted)\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateCommits.enabled", + "when": "viewItem =~ /gitlens:(worktree\\b(?=.*?\\b\\+working\\b)|uncommitted)\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled", "group": "3_gitlens_ai", "order": 1 } @@ -264,18 +264,18 @@ } }, "gitlens.ai.generateRebase": { - "label": "Rebase with AI (Experimental)...", + "label": "Rebase with AI (Preview)...", "icon": "$(sparkle)", - "commandPalette": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled" + "commandPalette": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled" }, "gitlens.ai.rebaseOntoCommit:graph": { - "label": "AI Rebase Current Branch onto Commit...", + "label": "AI Rebase Current Branch onto Commit (Preview)...", "icon": "$(sparkle)", "enablement": "!operationInProgress", "menus": { "webview/context": [ { - "when": "webviewItem =~ /gitlens:commit\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled", + "when": "webviewItem =~ /gitlens:commit\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled", "group": "1_gitlens_actions", "order": 6 } @@ -283,20 +283,20 @@ } }, "gitlens.ai.rebaseOntoCommit:views": { - "label": "AI Rebase Current Branch onto Commit...", + "label": "AI Rebase Current Branch onto Commit (Preview)...", "icon": "$(sparkle)", "enablement": "!operationInProgress", "menus": { "gitlens/commit/file/commit": [ { - "when": "viewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)(?=.*?\\b\\+current\\b)/ && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled", + "when": "viewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)(?=.*?\\b\\+current\\b)/ && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled", "group": "1_gitlens_actions", "order": 6 } ], "view/item/context": [ { - "when": "viewItem =~ /gitlens:commit\\b(?!.*?\\b\\+rebase\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled", + "when": "viewItem =~ /gitlens:commit\\b(?!.*?\\b\\+rebase\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled", "group": "1_gitlens_actions", "order": 6 } diff --git a/package.json b/package.json index 12d4823e71930..001bcd86c623c 100644 --- a/package.json +++ b/package.json @@ -4261,26 +4261,6 @@ "preview" ] }, - "gitlens.ai.experimental.generateCommits.enabled": { - "type": "boolean", - "default": false, - "markdownDescription": "Specifies whether to enable GitLens' experimental AI Commit Generation features", - "scope": "window", - "order": 1, - "tags": [ - "experimental" - ] - }, - "gitlens.ai.experimental.generateRebase.enabled": { - "type": "boolean", - "default": false, - "markdownDescription": "Specifies whether to enable GitLens' experimental AI Rebase features", - "scope": "window", - "order": 1, - "tags": [ - "experimental" - ] - }, "gitlens.ai.generateStashMessage.customInstructions": { "type": "string", "default": null, @@ -6247,35 +6227,35 @@ }, { "command": "gitlens.ai.generateCommits", - "title": "Generate Commits with AI (Experimental)...", + "title": "Generate Commits with AI (Preview)...", "category": "GitLens", "icon": "$(sparkle)" }, { "command": "gitlens.ai.generateCommits:graph", - "title": "Generate Commits with AI (Experimental)", + "title": "Generate Commits with AI (Preview)", "icon": "$(sparkle)" }, { "command": "gitlens.ai.generateCommits:views", - "title": "Generate Commits with AI (Experimental)", + "title": "Generate Commits with AI (Preview)", "icon": "$(sparkle)" }, { "command": "gitlens.ai.generateRebase", - "title": "Rebase with AI (Experimental)...", + "title": "Rebase with AI (Preview)...", "category": "GitLens", "icon": "$(sparkle)" }, { "command": "gitlens.ai.rebaseOntoCommit:graph", - "title": "AI Rebase Current Branch onto Commit...", + "title": "AI Rebase Current Branch onto Commit (Preview)...", "icon": "$(sparkle)", "enablement": "!operationInProgress" }, { "command": "gitlens.ai.rebaseOntoCommit:views", - "title": "AI Rebase Current Branch onto Commit...", + "title": "AI Rebase Current Branch onto Commit (Preview)...", "icon": "$(sparkle)", "enablement": "!operationInProgress" }, @@ -10766,7 +10746,7 @@ }, { "command": "gitlens.ai.generateCommits", - "when": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateCommits.enabled" + "when": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled" }, { "command": "gitlens.ai.generateCommits:graph", @@ -10778,7 +10758,7 @@ }, { "command": "gitlens.ai.generateRebase", - "when": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled" + "when": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled" }, { "command": "gitlens.ai.rebaseOntoCommit:graph", @@ -14403,7 +14383,7 @@ }, { "command": "gitlens.ai.rebaseOntoCommit:views", - "when": "viewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)(?=.*?\\b\\+current\\b)/ && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled", + "when": "viewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)(?=.*?\\b\\+current\\b)/ && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled", "group": "1_gitlens_actions@6" }, { @@ -17097,7 +17077,7 @@ }, { "command": "gitlens.ai.rebaseOntoCommit:views", - "when": "viewItem =~ /gitlens:commit\\b(?!.*?\\b\\+rebase\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled", + "when": "viewItem =~ /gitlens:commit\\b(?!.*?\\b\\+rebase\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled", "group": "1_gitlens_actions@6" }, { @@ -18380,7 +18360,7 @@ }, { "command": "gitlens.ai.generateCommits:views", - "when": "viewItem =~ /gitlens:(worktree\\b(?=.*?\\b\\+working\\b)|uncommitted)\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateCommits.enabled", + "when": "viewItem =~ /gitlens:(worktree\\b(?=.*?\\b\\+working\\b)|uncommitted)\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled", "group": "3_gitlens_ai@1" }, { @@ -22728,7 +22708,7 @@ }, { "command": "gitlens.ai.rebaseOntoCommit:graph", - "when": "webviewItem =~ /gitlens:commit\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateRebase.enabled", + "when": "webviewItem =~ /gitlens:commit\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:ai:enabled", "group": "1_gitlens_actions@6" }, { @@ -22988,7 +22968,7 @@ }, { "command": "gitlens.ai.generateCommits:graph", - "when": "webviewItem == gitlens:wip && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.experimental.generateCommits.enabled", + "when": "webviewItem == gitlens:wip && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled", "group": "1_gitlens@2" }, { diff --git a/src/commands/generateRebase.ts b/src/commands/generateRebase.ts index 27ce58d5e02dd..6629356488ae8 100644 --- a/src/commands/generateRebase.ts +++ b/src/commands/generateRebase.ts @@ -85,7 +85,11 @@ export class GenerateCommitsCommand extends GlCommandBase { createReference(uncommitted, svc.path, { refType: 'revision' }), createReference('HEAD', svc.path, { refType: 'revision' }), args?.source ?? { source: 'commandPalette' }, - { title: 'Generate Commits', progress: { location: ProgressLocation.Notification } }, + { + title: 'Generate Commits', + progress: { location: ProgressLocation.Notification }, + generateCommits: true, + }, ); } catch (ex) { Logger.error(ex, 'GenerateCommitsCommand', 'execute'); @@ -165,7 +169,12 @@ export async function generateRebase( head: GitReference, base: GitReference, source: Source, - options?: { title?: string; cancellation?: CancellationToken; progress?: ProgressOptions }, + options?: { + title?: string; + cancellation?: CancellationToken; + progress?: ProgressOptions; + generateCommits?: boolean; + }, ): Promise { const { title, ...aiOptions } = options ?? {}; diff --git a/src/config.ts b/src/config.ts index 1009f2e934dea..2b7f37a4b63b8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -217,14 +217,6 @@ interface AIConfig { readonly explainChanges: { readonly customInstructions: string; }; - readonly experimental: { - readonly generateCommits: { - readonly enabled: boolean; - }; - readonly generateRebase: { - readonly enabled: boolean; - }; - }; readonly generateChangelog: { readonly customInstructions: string; }; diff --git a/src/constants.storage.ts b/src/constants.storage.ts index f9f4691dd74ed..24e86f77986ad 100644 --- a/src/constants.storage.ts +++ b/src/constants.storage.ts @@ -63,6 +63,8 @@ export type DeprecatedGlobalStorage = { export type GlobalStorage = { avatars: [string, StoredAvatar][]; + 'confirm:ai:generateCommits': boolean; + 'confirm:ai:generateRebase': boolean; 'confirm:ai:tos': boolean; repoVisibility: [string, StoredRepoVisibilityInfo][]; pendingWhatsNewOnFocus: boolean; diff --git a/src/plus/ai/aiProviderService.ts b/src/plus/ai/aiProviderService.ts index 33a8b8b5398cf..4fa9291268104 100644 --- a/src/plus/ai/aiProviderService.ts +++ b/src/plus/ai/aiProviderService.ts @@ -871,6 +871,7 @@ export class AIProviderService implements Disposable { context?: string; generating?: Deferred; progress?: ProgressOptions; + generateCommits?: boolean; }, ): Promise { const result: Mutable = { @@ -880,12 +881,43 @@ export class AIProviderService implements Disposable { commits: [], } as unknown as AIRebaseResult; + const confirmed = this.container.storage.get( + options?.generateCommits ? 'confirm:ai:generateCommits' : 'confirm:ai:generateRebase', + false, + ); + if (!confirmed) { + const accept: MessageItem = { title: 'Continue' }; + const cancel: MessageItem = { title: 'Cancel', isCloseAffordance: true }; + + const result = await window.showInformationMessage( + `This will ${ + options?.generateCommits + ? 'stash all of your changes and commit directly to your current branch' + : 'create a new branch at the chosen commit and commit directly to that branch' + }.`, + { modal: true }, + accept, + cancel, + ); + + if (result === cancel) { + return undefined; + } else if (result === accept) { + await this.container.storage.store( + options?.generateCommits ? 'confirm:ai:generateCommits' : 'confirm:ai:generateRebase', + true, + ); + } + } + const rq = await this.sendRequest( 'generate-rebase', async (model, reporting, cancellation, maxInputTokens, retries) => { const diff = await repo.git.diff.getDiff?.(headRef, baseRef, { notation: '...' }); if (!diff?.contents) { - throw new AINoRequestDataError('No changes found to generate a rebase from.'); + throw new AINoRequestDataError( + `No changes found to generate ${options?.generateCommits ? 'commits' : 'a rebase'} from.`, + ); } if (cancellation.isCancellationRequested) throw new CancellationError(); @@ -937,7 +969,7 @@ export class AIProviderService implements Disposable { const messages: AIChatMessage[] = [{ role: 'user', content: prompt }]; return messages; }, - m => `Generating rebase with ${m.name}...`, + m => `Generating ${options?.generateCommits ? 'commits' : 'rebase'} with ${m.name}...`, source, m => ({ key: 'ai/generate', @@ -959,7 +991,7 @@ export class AIProviderService implements Disposable { result.commits = JSON.parse(content) as AIRebaseResult['commits']; } catch { debugger; - throw new Error('Unable to parse rebase result'); + throw new Error(`Unable to parse ${options?.generateCommits ? 'commits' : 'rebase'} result`); } return { @@ -1408,6 +1440,8 @@ export class AIProviderService implements Disposable { resetConfirmations(): void { void this.container.storage.deleteWithPrefix(`confirm:ai:tos`); void this.container.storage.deleteWorkspaceWithPrefix(`confirm:ai:tos`); + void this.container.storage.deleteWithPrefix(`confirm:ai:generateCommits`); + void this.container.storage.deleteWithPrefix(`confirm:ai:generateRebase`); } resetProviderKey(provider: AIProviders, silent?: boolean): void { diff --git a/src/webviews/apps/home/stateProvider.ts b/src/webviews/apps/home/stateProvider.ts index af4b286c6f114..2cd915b994b7a 100644 --- a/src/webviews/apps/home/stateProvider.ts +++ b/src/webviews/apps/home/stateProvider.ts @@ -80,7 +80,6 @@ export class HomeStateProvider implements StateProvider { this._state.previewEnabled = msg.params.previewEnabled; this._state.previewCollapsed = msg.params.previewCollapsed; this._state.aiEnabled = msg.params.aiEnabled; - this._state.aiGenerateCommitsEnabled = msg.params.aiGenerateCommitsEnabled; this._state.timestamp = Date.now(); this.provider.setValue(this._state, true); diff --git a/src/webviews/apps/plus/home/components/active-work.ts b/src/webviews/apps/plus/home/components/active-work.ts index ce3f6a92db47b..692900e52d104 100644 --- a/src/webviews/apps/plus/home/components/active-work.ts +++ b/src/webviews/apps/plus/home/components/active-work.ts @@ -269,15 +269,13 @@ export class GlActiveBranchCard extends GlBranchCardBase { const actions = []; if (aiEnabled) { if (hasWip) { - if (this._homeState.aiGenerateCommitsEnabled) { - actions.push( - html`Generate Commits with AI (Experimental)`, - ); - } + actions.push( + html`Generate Commits with AI (Preview)`, + ); actions.push( html`Explain Working Changes (Preview)= new Date('2025-02-13T13:00:00-05:00').getTime()) return true; @@ -807,7 +801,6 @@ export class HomeWebviewProvider implements WebviewProvider( scope,