Skip to content

Commit 79c4ef1

Browse files
committed
Fixes #3344 exposes reset on ai model picker
Renames "Reset Stored AI Key" command & adds confirmation
1 parent c97d5a3 commit 79c4ef1

File tree

5 files changed

+113
-25
lines changed

5 files changed

+113
-25
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
- Renames `Reset Stored AI Key` command to `Reset Stored AI Keys...` and adds confirmation prompt with options to reset only the current or all AI keys
12+
13+
### Fixed
14+
15+
- Fixes [#3344](https://github.com/gitkraken/vscode-gitlens/issues/3344) - Make changing the AI key easier
16+
917
## [15.1.0] - 2024-06-05
1018

1119
### Added

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5429,7 +5429,7 @@
54295429
},
54305430
{
54315431
"command": "gitlens.resetAIKey",
5432-
"title": "Reset Stored AI Key",
5432+
"title": "Reset Stored AI Keys...",
54335433
"category": "GitLens"
54345434
},
54355435
{

src/ai/aiProviderService.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class AIProviderService implements Disposable {
7777
this._provider?.dispose();
7878
}
7979

80-
get providerId() {
80+
get currentProviderId() {
8181
return this._provider?.id;
8282
}
8383

@@ -108,13 +108,15 @@ export class AIProviderService implements Disposable {
108108
return models.flatMap(m => getSettledValue(m, []));
109109
}
110110

111-
private async getOrChooseModel(force?: boolean): Promise<AIModel | undefined> {
111+
private async getModel(options?: { force?: boolean; silent?: boolean }): Promise<AIModel | undefined> {
112112
const cfg = this.getConfiguredModel();
113-
if (!force && cfg?.provider != null && cfg?.model != null) {
113+
if (!options?.force && cfg?.provider != null && cfg?.model != null) {
114114
const model = await this.getOrUpdateModel(cfg.provider, cfg.model);
115115
if (model != null) return model;
116116
}
117117

118+
if (options?.silent) return undefined;
119+
118120
const pick = await showAIModelPicker(this.container, cfg);
119121
if (pick == null) return undefined;
120122

@@ -221,7 +223,7 @@ export class AIProviderService implements Disposable {
221223
changes = diff.contents;
222224
}
223225

224-
const model = await this.getOrChooseModel();
226+
const model = await this.getModel();
225227
if (model == null) return undefined;
226228

227229
const provider = this._provider!;
@@ -276,7 +278,7 @@ export class AIProviderService implements Disposable {
276278
const diff = await this.container.git.getDiff(commit.repoPath, commit.sha);
277279
if (!diff?.contents) throw new Error('No changes found to explain.');
278280

279-
const model = await this.getOrChooseModel();
281+
const model = await this.getModel();
280282
if (model == null) return undefined;
281283

282284
const provider = this._provider!;
@@ -301,22 +303,64 @@ export class AIProviderService implements Disposable {
301303
});
302304
}
303305

304-
reset() {
305-
const { providerId } = this;
306-
if (providerId == null) return;
306+
async reset() {
307+
let { _provider: provider } = this;
308+
if (provider == null) {
309+
// If we have no provider, try to get the current model (which will load the provider)
310+
await this.getModel({ silent: true });
311+
provider = this._provider;
312+
}
313+
314+
const resetCurrent: MessageItem = { title: `Reset Current` };
315+
const resetAll: MessageItem = { title: 'Reset All' };
316+
const cancel: MessageItem = { title: 'Cancel', isCloseAffordance: true };
317+
318+
let result;
319+
if (provider == null) {
320+
result = await window.showInformationMessage(
321+
`Do you want to reset all of the stored AI keys?`,
322+
{ modal: true },
323+
resetAll,
324+
cancel,
325+
);
326+
} else {
327+
result = await window.showInformationMessage(
328+
`Do you want to reset the stored key for the current provider (${provider.name}) or reset all of the stored AI keys?`,
329+
{ modal: true },
330+
resetCurrent,
331+
resetAll,
332+
cancel,
333+
);
334+
}
335+
336+
if (provider != null && result === resetCurrent) {
337+
void env.clipboard.writeText((await this.container.storage.getSecret(`gitlens.${provider.id}.key`)) ?? '');
338+
void this.container.storage.deleteSecret(`gitlens.${provider.id}.key`);
339+
340+
void this.container.storage.delete(`confirm:ai:tos:${provider.id}`);
341+
void this.container.storage.deleteWorkspace(`confirm:ai:tos:${provider.id}`);
342+
} else if (result === resetAll) {
343+
const keys = [];
344+
for (const [providerId] of _supportedProviderTypes) {
345+
keys.push(await this.container.storage.getSecret(`gitlens.${providerId}.key`));
346+
}
347+
void env.clipboard.writeText(keys.join('\n'));
307348

308-
void this.container.storage.deleteSecret(`gitlens.${providerId}.key`);
349+
for (const [providerId] of _supportedProviderTypes) {
350+
void this.container.storage.deleteSecret(`gitlens.${providerId}.key`);
351+
}
309352

310-
void this.container.storage.delete(`confirm:ai:tos:${providerId}`);
311-
void this.container.storage.deleteWorkspace(`confirm:ai:tos:${providerId}`);
353+
void this.container.storage.deleteWithPrefix(`confirm:ai:tos`);
354+
void this.container.storage.deleteWorkspaceWithPrefix(`confirm:ai:tos`);
355+
}
312356
}
313357

314358
supports(provider: AIProviders | string) {
315359
return _supportedProviderTypes.has(provider as AIProviders);
316360
}
317361

318362
async switchModel() {
319-
void (await this.getOrChooseModel(true));
363+
void (await this.getModel({ force: true }));
320364
}
321365
}
322366

src/commands/resets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class ResetAIKeyCommand extends Command {
1313
}
1414

1515
async execute() {
16-
(await this.container.ai)?.reset();
16+
await (await this.container.ai)?.reset();
1717
}
1818
}
1919

src/quickpicks/aiModelPicker.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import type { QuickPickItem } from 'vscode';
1+
import type { Disposable, QuickInputButton, QuickPickItem } from 'vscode';
22
import { QuickPickItemKind, ThemeIcon, window } from 'vscode';
33
import type { AIModel } from '../ai/aiProviderService';
44
import type { AIModels, AIProviders } from '../constants';
5+
import { Commands } from '../constants';
56
import type { Container } from '../container';
7+
import { executeCommand } from '../system/command';
8+
import { getQuickPickIgnoreFocusOut } from '../system/utils';
69

710
export interface ModelQuickPickItem extends QuickPickItem {
811
model: AIModel;
@@ -14,16 +17,15 @@ export async function showAIModelPicker(
1417
): Promise<ModelQuickPickItem | undefined> {
1518
const models = (await (await container.ai)?.getModels()) ?? [];
1619

17-
type QuickPickSeparator = { label: string; kind: QuickPickItemKind.Separator };
18-
const items: (ModelQuickPickItem | QuickPickSeparator)[] = [];
20+
const items: ModelQuickPickItem[] = [];
1921

2022
let lastProvider: AIProviders | undefined;
2123
for (const m of models) {
2224
if (m.hidden) continue;
2325

2426
if (lastProvider !== m.provider.id) {
2527
lastProvider = m.provider.id;
26-
items.push({ label: m.provider.name, kind: QuickPickItemKind.Separator });
28+
items.push({ label: m.provider.name, kind: QuickPickItemKind.Separator } as unknown as ModelQuickPickItem);
2729
}
2830

2931
const picked = m.provider.id === current?.provider && m.id === current?.model;
@@ -34,14 +36,48 @@ export async function showAIModelPicker(
3436
// description: m.provider.name,
3537
model: m,
3638
picked: picked,
37-
});
39+
} satisfies ModelQuickPickItem);
3840
}
3941

40-
const pick = (await window.showQuickPick(items, {
41-
title: 'Choose AI Model',
42-
placeHolder: 'Select an AI model to use for experimental AI features',
43-
matchOnDescription: true,
44-
})) as ModelQuickPickItem | undefined;
42+
const quickpick = window.createQuickPick<ModelQuickPickItem>();
43+
quickpick.ignoreFocusOut = getQuickPickIgnoreFocusOut();
44+
45+
const disposables: Disposable[] = [];
46+
47+
const ResetAIKeyButton: QuickInputButton = {
48+
iconPath: new ThemeIcon('clear-all'),
49+
tooltip: 'Reset AI Keys...',
50+
};
51+
52+
try {
53+
const pick = await new Promise<ModelQuickPickItem | undefined>(resolve => {
54+
disposables.push(
55+
quickpick.onDidHide(() => resolve(undefined)),
56+
quickpick.onDidAccept(() => {
57+
if (quickpick.activeItems.length !== 0) {
58+
resolve(quickpick.activeItems[0]);
59+
}
60+
}),
61+
quickpick.onDidTriggerButton(e => {
62+
if (e === ResetAIKeyButton) {
63+
void executeCommand(Commands.ResetAIKey);
64+
}
65+
}),
66+
);
4567

46-
return pick;
68+
quickpick.title = 'Choose AI Model';
69+
quickpick.placeholder = 'Select an AI model to use for experimental AI features';
70+
quickpick.matchOnDescription = true;
71+
quickpick.matchOnDetail = true;
72+
quickpick.buttons = [ResetAIKeyButton];
73+
quickpick.items = items;
74+
75+
quickpick.show();
76+
});
77+
78+
return pick;
79+
} finally {
80+
quickpick.dispose();
81+
disposables.forEach(d => void d.dispose());
82+
}
4783
}

0 commit comments

Comments
 (0)