diff --git a/src/vs/workbench/contrib/chat/browser/chatTipService.ts b/src/vs/workbench/contrib/chat/browser/chatTipService.ts index 61e2ff7f0e0f72..1b2ba0102cbfb0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatTipService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatTipService.ts @@ -106,12 +106,22 @@ export interface IChatTipService { */ dismissTip(): void; + /** + * Dismisses the current tip and hides all tips for the rest of the current chat session. + */ + dismissTipForSession(): void; + /** * Hides the tip widget without permanently dismissing the tip. * The tip may be shown again in a future session. */ hideTip(): void; + /** + * Hides all tips for the rest of the current chat session. + */ + hideTipsForSession(): void; + /** * Disables tips permanently by setting the `chat.tips.enabled` configuration to false. */ @@ -185,6 +195,7 @@ export class ChatTipService extends Disposable implements IChatTipService { private readonly _createSlashCommandsUsageTracker: CreateSlashCommandsUsageTracker; private _yoloModeEverEnabled: boolean; private _thinkingPhrasesEverModified: boolean; + private _tipsHiddenForSession = false; private readonly _tipCommandListener = this._register(new MutableDisposable()); constructor( @@ -303,6 +314,7 @@ export class ChatTipService extends Disposable implements IChatTipService { this._shownTip = undefined; this._tipRequestId = undefined; this._contextKeyService = undefined; + this._tipsHiddenForSession = false; } dismissTip(): void { @@ -318,12 +330,18 @@ export class ChatTipService extends Disposable implements IChatTipService { this._onDidDismissTip.fire(); } + dismissTipForSession(): void { + this.dismissTip(); + this.hideTipsForSession(); + } + clearDismissedTips(): void { this._storageService.remove(ChatTipStorageKeys.DismissedTips, StorageScope.APPLICATION); this._storageService.remove(ChatTipStorageKeys.DismissedTips, StorageScope.PROFILE); this._shownTip = undefined; this._tipRequestId = undefined; this._contextKeyService = undefined; + this._tipsHiddenForSession = false; this._onDidDismissTip.fire(); } @@ -362,6 +380,17 @@ export class ChatTipService extends Disposable implements IChatTipService { this._onDidHideTip.fire(); } + hideTipsForSession(): void { + if (this._tipsHiddenForSession) { + return; + } + + this._tipsHiddenForSession = true; + this._shownTip = undefined; + this._tipRequestId = undefined; + this._onDidHideTip.fire(); + } + async disableTips(): Promise { if (this._shownTip) { this._logTipTelemetry(this._shownTip.id, 'disabled'); @@ -385,6 +414,10 @@ export class ChatTipService extends Disposable implements IChatTipService { return undefined; } + if (this._tipsHiddenForSession) { + return undefined; + } + // Store the scoped context key service for later navigation calls this._contextKeyService = contextKeyService; @@ -802,6 +835,7 @@ export class ChatTipService extends Disposable implements IChatTipService { if (dismissCommandSet.has(e.commandId)) { this.dismissTip(); } + this.hideTipsForSession(); } }); } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTipContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTipContentPart.ts index 75ac2bef68ec2e..3e195b32ada8ed 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTipContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTipContentPart.ts @@ -74,15 +74,7 @@ export class ChatTipContentPart extends Disposable { this._renderTip(tip); this._register(this._chatTipService.onDidDismissTip(() => { - // Use getNextEligibleTip instead of navigateToNextTip to show the next - // available tip even if it's the only one left (no multiple-tip requirement) - const nextTip = this._chatTipService.getNextEligibleTip(); - if (nextTip) { - this._renderTip(nextTip); - dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(this.domNode), () => this.focus()); - } else { - this._onDidHide.fire(); - } + this._onDidHide.fire(); })); this._register(this._chatTipService.onDidNavigateTip(tip => { @@ -230,7 +222,7 @@ registerAction2(class DismissTipToolbarAction extends Action2 { } override async run(accessor: ServicesAccessor): Promise { - accessor.get(IChatTipService).dismissTip(); + accessor.get(IChatTipService).dismissTipForSession(); } }); @@ -253,7 +245,7 @@ registerAction2(class DismissTipAction extends Action2 { } override async run(accessor: ServicesAccessor): Promise { - accessor.get(IChatTipService).dismissTip(); + accessor.get(IChatTipService).dismissTipForSession(); } }); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts index be41fddaa4dd6a..89f40d4aba12b3 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts @@ -368,6 +368,20 @@ suite('ChatTipService', () => { } }); + test('dismissTipForSession hides tips until resetSession', () => { + const service = createService(); + + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + service.dismissTipForSession(); + + assert.strictEqual(service.getWelcomeTip(contextKeyService), undefined, 'Tips should stay hidden for the current session after dismissing'); + + service.resetSession(); + assert.ok(service.getWelcomeTip(contextKeyService), 'Tips should reappear after resetting the session'); + }); + test('navigateToNextTip keeps foundational tips before QoL tips', () => { const service = createService(); contextKeyService.createKey(ChatContextKeys.chatModeKind.key, ChatModeKind.Agent); @@ -1352,6 +1366,9 @@ suite('ChatTipService', () => { commandExecutedEmitter.fire({ commandId: 'workbench.action.openSettings', args: [] }); assert.strictEqual(dismissed, true, `${tipId} should dismiss when its settings command is clicked`); + assert.strictEqual(service.getWelcomeTip(contextKeyService), undefined, 'Tips should hide for the rest of the session after actioning a tip'); + + service.resetSession(); assertTipNeverShown(service, tipId); }); }