Skip to content

Commit 5d214c4

Browse files
Copilotrebornixosortega
authored
Allow deleting and renaming history from chat session view (microsoft#264797)
* Initial plan * Add DeleteChatSessionAction and menu registration for chat session history items Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> * Register DeleteChatSessionAction in chat.contribution.ts Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> * Fix delete icon and list refresh bug for chat session deletion - Changed delete icon from Codicon.trash to Codicon.x as requested - Fixed list view not updating after deletion by calling notifySessionItemsChanged('local') - Added IChatSessionsService injection to DeleteChatSessionAction Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> * Group should be context * Add rename functionality to chat history sessions Co-authored-by: osortega <48293249+osortega@users.noreply.github.com> * Proper deletion * Fix rename functionality for chat history sessions Co-authored-by: osortega <48293249+osortega@users.noreply.github.com> * Rename fix --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> Co-authored-by: Osvaldo Ortega <osortega@microsoft.com> Co-authored-by: osortega <48293249+osortega@users.noreply.github.com>
1 parent 2561e7c commit 5d214c4

4 files changed

Lines changed: 167 additions & 9 deletions

File tree

src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { ChatViewId } from '../chat.js';
2727
import { ChatViewPane } from '../chatViewPane.js';
2828
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
2929
import { ChatConfiguration } from '../../common/constants.js';
30+
import { Codicon } from '../../../../../base/common/codicons.js';
31+
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
3032

3133
export interface IChatSessionContext {
3234
sessionId: string;
@@ -90,8 +92,10 @@ export class RenameChatSessionAction extends Action2 {
9092
let actualSessionId: string | undefined;
9193
const currentTitle = session.label;
9294

93-
// For local sessions, we need to extract the actual session ID from editor or widget
94-
if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) {
95+
// For history sessions, we need to extract the actual session ID
96+
if (session.id.startsWith('history-')) {
97+
actualSessionId = session.id.replace('history-', '');
98+
} else if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) {
9599
actualSessionId = session.editor.sessionId;
96100
} else if (session.sessionType === 'widget' && session.widget) {
97101
actualSessionId = session.widget.viewModel?.model.sessionId;
@@ -139,6 +143,8 @@ export class RenameChatSessionAction extends Action2 {
139143
try {
140144
const newTitle = value.trim();
141145
chatService.setChatSessionTitle(sessionContext.sessionId, newTitle);
146+
// Notify the local sessions provider that items have changed
147+
chatSessionsService.notifySessionItemsChanged('local');
142148
} catch (error) {
143149
logService.error(
144150
localize('renameSession.error', "Failed to rename chat session: {0}",
@@ -155,6 +161,87 @@ export class RenameChatSessionAction extends Action2 {
155161
}
156162
}
157163

164+
/**
165+
* Action to delete a chat session from history
166+
*/
167+
export class DeleteChatSessionAction extends Action2 {
168+
static readonly id = 'workbench.action.chat.deleteSession';
169+
170+
constructor() {
171+
super({
172+
id: DeleteChatSessionAction.id,
173+
title: localize('deleteSession', "Delete"),
174+
f1: false,
175+
category: CHAT_CATEGORY,
176+
icon: Codicon.x,
177+
});
178+
}
179+
180+
async run(accessor: ServicesAccessor, context?: IChatSessionContext | IMarshalledChatSessionContext): Promise<void> {
181+
if (!context) {
182+
return;
183+
}
184+
185+
// Handle marshalled context from menu actions
186+
let sessionContext: IChatSessionContext;
187+
if (isMarshalledChatSessionContext(context)) {
188+
const session = context.session;
189+
// Extract actual session ID based on session type
190+
let actualSessionId: string | undefined;
191+
const currentTitle = session.label;
192+
193+
// For history sessions, we need to extract the actual session ID
194+
if (session.id.startsWith('history-')) {
195+
actualSessionId = session.id.replace('history-', '');
196+
} else if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) {
197+
actualSessionId = session.editor.sessionId;
198+
} else if (session.sessionType === 'widget' && session.widget) {
199+
actualSessionId = session.widget.viewModel?.model.sessionId;
200+
} else {
201+
// Fall back to using the session ID directly
202+
actualSessionId = session.id;
203+
}
204+
205+
if (!actualSessionId) {
206+
return; // Can't proceed without a session ID
207+
}
208+
209+
sessionContext = {
210+
sessionId: actualSessionId,
211+
sessionType: session.sessionType || 'editor',
212+
currentTitle: currentTitle,
213+
editorInput: session.editor,
214+
widget: session.widget
215+
};
216+
} else {
217+
sessionContext = context;
218+
}
219+
220+
const chatService = accessor.get(IChatService);
221+
const dialogService = accessor.get(IDialogService);
222+
const logService = accessor.get(ILogService);
223+
const chatSessionsService = accessor.get(IChatSessionsService);
224+
225+
try {
226+
// Show confirmation dialog
227+
const result = await dialogService.confirm({
228+
message: localize('deleteSession.confirm', "Are you sure you want to delete this chat session?"),
229+
detail: localize('deleteSession.detail', "This action cannot be undone."),
230+
primaryButton: localize('deleteSession.delete', "Delete"),
231+
type: 'warning'
232+
});
233+
234+
if (result.confirmed) {
235+
await chatService.removeHistoryEntry(sessionContext.sessionId);
236+
// Notify the local sessions provider that items have changed
237+
chatSessionsService.notifySessionItemsChanged('local');
238+
}
239+
} catch (error) {
240+
logService.error('Failed to delete chat session', error instanceof Error ? error.message : String(error));
241+
}
242+
}
243+
}
244+
158245
/**
159246
* Action to open a chat session in a new window
160247
*/
@@ -389,17 +476,28 @@ export class ToggleChatSessionsDescriptionDisplayAction extends Action2 {
389476
}
390477
}
391478

392-
// Register the menu item - only show for local chat sessions that are not history items
479+
// Register the menu item - show for all local chat sessions (including history items)
393480
MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, {
394481
command: {
395482
id: RenameChatSessionAction.id,
396483
title: localize('renameSession', "Rename")
397484
},
398485
group: 'context',
399486
order: 1,
487+
when: ChatContextKeys.sessionType.isEqualTo('local')
488+
});
489+
490+
// Register delete menu item - only show for non-active sessions (history items)
491+
MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, {
492+
command: {
493+
id: DeleteChatSessionAction.id,
494+
title: localize('deleteSession', "Delete"),
495+
icon: Codicon.x
496+
},
497+
group: 'context',
498+
order: 1,
400499
when: ContextKeyExpr.and(
401-
ChatContextKeys.sessionType.isEqualTo('local'),
402-
ChatContextKeys.isHistoryItem.isEqualTo(false)
500+
ChatContextKeys.isActiveSession.isEqualTo(false)
403501
)
404502
});
405503

src/vs/workbench/contrib/chat/browser/chat.contribution.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js';
117117
import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js';
118118
import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js';
119119
import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js';
120-
import { RenameChatSessionAction, OpenChatSessionInNewWindowAction, OpenChatSessionInNewEditorGroupAction, OpenChatSessionInSidebarAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js';
120+
import { RenameChatSessionAction, DeleteChatSessionAction, OpenChatSessionInNewWindowAction, OpenChatSessionInNewEditorGroupAction, OpenChatSessionInSidebarAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js';
121121
import { IChatLayoutService } from '../common/chatLayoutService.js';
122122
import { ChatLayoutService } from './chatLayoutService.js';
123123

@@ -955,6 +955,7 @@ registerPromptFileContributions();
955955

956956
registerAction2(ConfigureToolSets);
957957
registerAction2(RenameChatSessionAction);
958+
registerAction2(DeleteChatSessionAction);
958959
registerAction2(OpenChatSessionInNewWindowAction);
959960
registerAction2(OpenChatSessionInNewEditorGroupAction);
960961
registerAction2(OpenChatSessionInSidebarAction);

src/vs/workbench/contrib/chat/browser/chatSessions.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,13 @@ function processSessionsWithTimeGrouping(sessions: ChatSessionItemWithProvider[]
148148
}
149149

150150
// Helper function to create context overlay for session items
151-
function getSessionItemContextOverlay(session: IChatSessionItem, provider?: IChatSessionItemProvider): [string, any][] {
151+
function getSessionItemContextOverlay(
152+
session: IChatSessionItem,
153+
provider?: IChatSessionItemProvider,
154+
chatWidgetService?: IChatWidgetService,
155+
chatService?: IChatService,
156+
editorGroupsService?: IEditorGroupsService
157+
): [string, any][] {
152158
const overlay: [string, any][] = [];
153159
if (provider) {
154160
overlay.push([ChatContextKeys.sessionType.key, provider.chatSessionType]);
@@ -158,6 +164,38 @@ function getSessionItemContextOverlay(session: IChatSessionItem, provider?: ICha
158164
const isHistoryItem = session.id.startsWith('history-');
159165
overlay.push([ChatContextKeys.isHistoryItem.key, isHistoryItem]);
160166

167+
// Mark active sessions - check if session is currently open in editor or widget
168+
let isActiveSession = false;
169+
170+
if (!isHistoryItem && provider?.chatSessionType === 'local') {
171+
// Local non-history sessions are always active
172+
isActiveSession = true;
173+
} else if (isHistoryItem && chatWidgetService && chatService && editorGroupsService) {
174+
// For history sessions, check if they're currently opened somewhere
175+
const sessionId = session.id.substring('history-'.length); // Remove 'history-' prefix
176+
177+
// Check if session is open in a chat widget
178+
const widget = chatWidgetService.getWidgetBySessionId(sessionId);
179+
if (widget) {
180+
isActiveSession = true;
181+
} else {
182+
// Check if session is open in any editor
183+
for (const group of editorGroupsService.groups) {
184+
for (const editor of group.editors) {
185+
if (editor instanceof ChatEditorInput && editor.sessionId === sessionId) {
186+
isActiveSession = true;
187+
break;
188+
}
189+
}
190+
if (isActiveSession) {
191+
break;
192+
}
193+
}
194+
}
195+
}
196+
197+
overlay.push([ChatContextKeys.isActiveSession.key, isActiveSession]);
198+
161199
return overlay;
162200
}
163201

@@ -884,6 +922,9 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
884922
@IMenuService private readonly menuService: IMenuService,
885923
@IContextKeyService private readonly contextKeyService: IContextKeyService,
886924
@IInstantiationService instantiationService: IInstantiationService,
925+
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
926+
@IChatService private readonly chatService: IChatService,
927+
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
887928
) {
888929
super();
889930

@@ -1008,6 +1049,9 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
10081049
} else if (session.sessionType === 'widget' && session.widget) {
10091050
actualSessionId = session.widget.viewModel?.model.sessionId;
10101051
}
1052+
} else if (session.id.startsWith('history-')) {
1053+
// For history items, extract the actual session ID by removing the 'history-' prefix
1054+
actualSessionId = session.id.substring('history-'.length);
10111055
}
10121056

10131057
// Check if this session is being edited using the actual session ID
@@ -1097,7 +1141,13 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
10971141
}
10981142

10991143
// Create context overlay for this specific session item
1100-
const contextOverlay = getSessionItemContextOverlay(session, sessionWithProvider.provider);
1144+
const contextOverlay = getSessionItemContextOverlay(
1145+
session,
1146+
sessionWithProvider.provider,
1147+
this.chatWidgetService,
1148+
this.chatService,
1149+
this.editorGroupsService
1150+
);
11011151

11021152
const contextKeyService = this.contextKeyService.createOverlay(contextOverlay);
11031153

@@ -1319,6 +1369,8 @@ class SessionsViewPane extends ViewPane {
13191369
@IProgressService private readonly progressService: IProgressService,
13201370
@IMenuService private readonly menuService: IMenuService,
13211371
@ICommandService private readonly commandService: ICommandService,
1372+
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
1373+
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
13221374
) {
13231375
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
13241376

@@ -1683,7 +1735,13 @@ class SessionsViewPane extends ViewPane {
16831735
const sessionWithProvider = session as ChatSessionItemWithProvider;
16841736

16851737
// Create context overlay for this specific session item
1686-
const contextOverlay = getSessionItemContextOverlay(session, sessionWithProvider.provider);
1738+
const contextOverlay = getSessionItemContextOverlay(
1739+
session,
1740+
sessionWithProvider.provider,
1741+
this.chatWidgetService,
1742+
this.chatService,
1743+
this.editorGroupsService
1744+
);
16871745
const contextKeyService = this.contextKeyService.createOverlay(contextOverlay);
16881746

16891747
// Create marshalled context for command execution

src/vs/workbench/contrib/chat/common/chatContextKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export namespace ChatContextKeys {
107107

108108
export const sessionType = new RawContextKey<string>('chatSessionType', '', { type: 'string', description: localize('chatSessionType', "The type of the current chat session item.") });
109109
export const isHistoryItem = new RawContextKey<boolean>('chatIsHistoryItem', false, { type: 'boolean', description: localize('chatIsHistoryItem', "True when the chat session item is from history.") });
110+
export const isActiveSession = new RawContextKey<boolean>('chatIsActiveSession', false, { type: 'boolean', description: localize('chatIsActiveSession', "True when the chat session is currently active (not deletable).") });
110111
}
111112

112113
export namespace ChatContextKeyExprs {

0 commit comments

Comments
 (0)