Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chatTipService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand All @@ -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();
}

Expand Down Expand Up @@ -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<void> {
if (this._shownTip) {
this._logTipTelemetry(this._shownTip.id, 'disabled');
Expand All @@ -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;

Expand Down Expand Up @@ -802,6 +835,7 @@ export class ChatTipService extends Disposable implements IChatTipService {
if (dismissCommandSet.has(e.commandId)) {
this.dismissTip();
}
this.hideTipsForSession();
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -230,7 +222,7 @@ registerAction2(class DismissTipToolbarAction extends Action2 {
}

override async run(accessor: ServicesAccessor): Promise<void> {
accessor.get(IChatTipService).dismissTip();
accessor.get(IChatTipService).dismissTipForSession();
}
});

Expand All @@ -253,7 +245,7 @@ registerAction2(class DismissTipAction extends Action2 {
}

override async run(accessor: ServicesAccessor): Promise<void> {
accessor.get(IChatTipService).dismissTip();
accessor.get(IChatTipService).dismissTipForSession();
}
});

Expand Down
17 changes: 17 additions & 0 deletions src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
});
}
Expand Down
Loading