From e6aeab60511647b9b00f0bf2f0f02b15f278abb5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 11 Dec 2025 15:02:11 +0100 Subject: [PATCH] debt - removal of old agent sessions views and co (#282712) --- eslint.config.js | 2 - src/vs/platform/actions/common/actions.ts | 7 - .../api/browser/viewsExtensionPoint.ts | 16 +- .../chat/browser/actions/chatActions.ts | 98 +-- .../actions/chatAgentRecommendationActions.ts | 10 - .../browser/actions/chatContinueInAction.ts | 3 +- .../browser/actions/chatSessionActions.ts | 318 --------- .../agentSessions.contribution.ts | 60 +- .../browser/agentSessions/agentSessions.ts | 39 +- .../agentSessions/agentSessionsActions.ts | 51 +- .../agentSessions/agentSessionsControl.ts | 73 +- .../agentSessions/agentSessionsView.ts | 246 ------- .../agentSessions/agentSessionsViewer.ts | 23 +- .../localAgentSessionsProvider.ts | 93 ++- .../agentSessions/media/agentsessionsview.css | 19 - .../contrib/chat/browser/chat.contribution.ts | 28 +- .../chat/browser/chatSessions.contribution.ts | 73 +- .../chat/browser/chatSessions/common.ts | 136 ---- .../chatSessions/view/chatSessionsView.ts | 277 -------- .../chatSessions/view/sessionsTreeRenderer.ts | 628 ------------------ .../chatSessions/view/sessionsViewPane.ts | 511 -------------- .../chatSetup/chatSetupContributions.ts | 8 +- .../browser/chatStatus/chatStatusDashboard.ts | 8 +- .../contrib/chat/browser/chatViewPane.ts | 5 +- .../chat/browser/media/chatSessions.css | 299 --------- .../contrib/chat/common/chatContextKeys.ts | 11 +- .../chat/common/chatSessionsService.ts | 6 - .../contrib/chat/common/constants.ts | 4 - .../test/common/mockChatSessionsService.ts | 18 - 29 files changed, 99 insertions(+), 2971 deletions(-) delete mode 100644 src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css delete mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/common.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/media/chatSessions.css diff --git a/eslint.config.js b/eslint.config.js index 8fda67317b1..583cc820859 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -273,8 +273,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts', 'src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts', 'src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts', - 'src/vs/workbench/contrib/chat/browser/chatSessions/common.ts', - 'src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts', 'src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts', 'src/vs/workbench/contrib/chat/common/annotations.ts', 'src/vs/workbench/contrib/chat/common/chat.ts', diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 796509ef531..f764d472a0f 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -284,7 +284,6 @@ export class MenuId { static readonly DiffEditorHunkToolbar = new MenuId('DiffEditorHunkToolbar'); static readonly DiffEditorSelectionToolbar = new MenuId('DiffEditorSelectionToolbar'); static readonly AgentSessionsViewerFilterSubMenu = new MenuId('AgentSessionsViewerFilterSubMenu'); - static readonly AgentSessionsInstallMenu = new MenuId('AgentSessionsInstallMenu'); static readonly AgentSessionsContext = new MenuId('AgentSessionsContext'); static readonly AgentSessionsCreateSubMenu = new MenuId('AgentSessionsCreateSubMenu'); static readonly AgentSessionsToolbar = new MenuId('AgentSessionsToolbar'); @@ -292,12 +291,6 @@ export class MenuId { static readonly ChatViewSessionTitleNavigationToolbar = new MenuId('ChatViewSessionTitleNavigationToolbar'); static readonly ChatViewSessionTitleToolbar = new MenuId('ChatViewSessionTitleToolbar'); - /** - * @deprecated TODO@bpasero remove - */ - static readonly AgentSessionsViewTitle = new MenuId('AgentSessionsViewTitle'); - static readonly AgentSessionsFilterSubMenu = new MenuId('AgentSessionsFilterSubMenu'); - /** * Create or reuse a `MenuId` with the given identifier */ diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index ed0d09908af..833787070f3 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -22,8 +22,6 @@ import { CustomTreeView, TreeViewPane } from '../../browser/parts/views/treeView import { ViewPaneContainer } from '../../browser/parts/views/viewPaneContainer.js'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../common/contributions.js'; import { ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, Extensions as ViewContainerExtensions, ViewContainerLocation } from '../../common/views.js'; -import { ChatContextKeyExprs } from '../../contrib/chat/common/chatContextKeys.js'; -import { LEGACY_AGENT_SESSIONS_VIEW_ID } from '../../contrib/chat/common/constants.js'; import { VIEWLET_ID as DEBUG } from '../../contrib/debug/common/debug.js'; import { VIEWLET_ID as EXPLORER } from '../../contrib/files/common/files.js'; import { VIEWLET_ID as REMOTE } from '../../contrib/remote/browser/remoteExplorer.js'; @@ -241,12 +239,6 @@ const viewsContribution: IJSONSchema = { items: remoteViewDescriptor, default: [] }, - 'agentSessions': { //TODO@bpasero retire this eventually - description: localize('views.agentSessions', "Contributes views to Agent Sessions container in the Activity bar. To contribute to this container, the 'chatSessionsProvider' API proposal must be enabled."), - type: 'array', - items: viewDescriptor, - default: [] - } }, additionalProperties: { description: localize('views.contributed', "Contributes views to contributed views container"), @@ -521,17 +513,12 @@ class ViewsExtensionHandler implements IWorkbenchContribution { accessibilityHelpContent = new MarkdownString(item.accessibilityHelpContent); } - let when = ContextKeyExpr.deserialize(item.when); - if (key === 'agentSessions') { - when = ContextKeyExpr.and(when, ChatContextKeyExprs.agentViewWhen); - } - const viewDescriptor: ICustomViewDescriptor = { type: type, ctorDescriptor: type === ViewType.Tree ? new SyncDescriptor(TreeViewPane) : new SyncDescriptor(WebviewViewPane), id: item.id, name: { value: item.name, original: item.name }, - when, + when: ContextKeyExpr.deserialize(item.when), containerIcon: icon || viewContainer?.icon, containerTitle: item.contextualTitle || (viewContainer && (typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value)), canToggleVisibility: true, @@ -643,7 +630,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution { case 'debug': return this.viewContainersRegistry.get(DEBUG); case 'scm': return this.viewContainersRegistry.get(SCM); case 'remote': return this.viewContainersRegistry.get(REMOTE); - case 'agentSessions': return this.viewContainersRegistry.get(LEGACY_AGENT_SESSIONS_VIEW_ID); default: return this.viewContainersRegistry.get(`workbench.view.extension.${value}`); } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 71a3fe9e54e..f7a613ca976 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -47,7 +47,7 @@ import { ActiveEditorContext, IsCompactTitleBarContext } from '../../../../commo import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js'; import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; -import { GroupDirection, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; import { IHostService } from '../../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/browser/layoutService.js'; @@ -66,7 +66,7 @@ import { IChatSessionItem, IChatSessionsService, localChatSessionType } from '.. import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind, LEGACY_AGENT_SESSIONS_VIEW_ID } from '../../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js'; import { ILanguageModelToolsConfirmationService } from '../../common/languageModelToolsConfirmationService.js'; @@ -77,7 +77,6 @@ import { ChatEditorInput, showClearEditingSessionConfirmation } from '../chatEdi import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; -import { IMarshalledChatSessionContext } from './chatSessionActions.js'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); @@ -922,7 +921,7 @@ export function registerChatActions() { commandService.executeCommand(buttonItem.id, { session: contextItem.session, $mid: MarshalledId.ChatSessionContext - } satisfies IMarshalledChatSessionContext); + }); } // dismiss quick picker @@ -1098,97 +1097,6 @@ export function registerChatActions() { } }); - registerAction2(class OpenChatEditorInNewWindowAction extends Action2 { - constructor() { - super({ - id: `workbench.action.chat.newChatInNewWindow`, - title: localize2('chatSessions.openNewChatInNewWindow', 'Open New Chat in New Window'), - f1: false, - category: CHAT_CATEGORY, - precondition: ChatContextKeys.enabled, - menu: { - id: MenuId.ViewTitle, - group: 'submenu', - order: 1, - when: ContextKeyExpr.equals('view', `${LEGACY_AGENT_SESSIONS_VIEW_ID}.local`), - } - }); - } - - async run(accessor: ServicesAccessor) { - const widgetService = accessor.get(IChatWidgetService); - await widgetService.openSession(ChatEditorInput.getNewEditorUri(), AUX_WINDOW_GROUP, { - pinned: true, - auxiliary: { compact: true, bounds: { width: 800, height: 640 } } - }); - } - }); - - registerAction2(class NewChatInSideBarAction extends Action2 { - constructor() { - super({ - id: `workbench.action.chat.newChatInSideBar`, - title: localize2('chatSessions.newChatInSideBar', 'Open New Chat in Side Bar'), - f1: false, - category: CHAT_CATEGORY, - precondition: ChatContextKeys.enabled, - menu: { - id: MenuId.ViewTitle, - group: 'submenu', - order: 1, - when: ContextKeyExpr.equals('view', `${LEGACY_AGENT_SESSIONS_VIEW_ID}.local`), - } - }); - } - - async run(accessor: ServicesAccessor) { - const widgetService = accessor.get(IChatWidgetService); - - // Open the chat view in the sidebar and get the widget - const chatWidget = await widgetService.revealWidget(); - - if (chatWidget) { - // Clear the current chat to start a new one - await chatWidget.clear(); - chatWidget.attachmentModel.clear(true); - chatWidget.input.relatedFiles?.clear(); - - // Focus the input area - chatWidget.focusInput(); - } - } - }); - - registerAction2(class OpenChatInNewEditorGroupAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.openNewChatToTheSide', - title: localize2('chat.openNewChatToTheSide.label', "Open New Chat Editor to the Side"), - category: CHAT_CATEGORY, - precondition: ChatContextKeys.enabled, - f1: false, - menu: { - id: MenuId.ViewTitle, - group: 'submenu', - order: 1, - when: ContextKeyExpr.equals('view', `${LEGACY_AGENT_SESSIONS_VIEW_ID}.local`), - } - }); - } - - async run(accessor: ServicesAccessor, ...args: unknown[]) { - const widgetService = accessor.get(IChatWidgetService); - const editorGroupService = accessor.get(IEditorGroupsService); - - // Create a new editor group to the right - const newGroup = editorGroupService.addGroup(editorGroupService.activeGroup, GroupDirection.RIGHT); - editorGroupService.activateGroup(newGroup); - - // Open a new chat editor in the new group - await widgetService.openSession(ChatEditorInput.getNewEditorUri(), newGroup.id, { pinned: true }); - } - }); - registerAction2(class ClearChatInputHistoryAction extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAgentRecommendationActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAgentRecommendationActions.ts index 2183c8ae9ad..ad5763c80b3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAgentRecommendationActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAgentRecommendationActions.ts @@ -72,16 +72,6 @@ export class ChatAgentRecommendation extends Disposable implements IWorkbenchCon icon: Codicon.extensions, precondition: ContextKeyExpr.equals(availabilityContextId, true), menu: [ - { - id: MenuId.AgentSessionsInstallMenu, - group: '0_install', - when: ContextKeyExpr.equals(availabilityContextId, true) - }, - { - id: MenuId.AgentSessionsViewTitle, - group: 'navigation@98', - when: ContextKeyExpr.equals(availabilityContextId, true) - }, { id: MenuId.ChatNewMenu, group: '4_recommendations', diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts index 26b9570bfe9..a255be39625 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContinueInAction.ts @@ -40,7 +40,6 @@ import { IChatWidgetService } from '../chat.js'; import { ctxHasEditorModification } from '../chatEditing/chatEditingEditorContextKeys.js'; import { CHAT_SETUP_ACTION_ID } from './chatActions.js'; import { PromptFileVariableKind, toPromptFileVariableEntry } from '../../common/chatVariableEntries.js'; -import { NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; export const enum ActionLocation { ChatWidget = 'chatWidget', @@ -201,6 +200,8 @@ export class ChatContinueInSessionActionItem extends ActionWidgetDropdownActionV } } +const NEW_CHAT_SESSION_ACTION_ID = 'workbench.action.chat.openNewSessionEditor'; + class CreateRemoteAgentJobAction { constructor() { } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts deleted file mode 100644 index 3d8c003ab14..00000000000 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ /dev/null @@ -1,318 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Codicon } from '../../../../../base/common/codicons.js'; -import { KeyCode } from '../../../../../base/common/keyCodes.js'; -import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; -import Severity from '../../../../../base/common/severity.js'; -import * as nls from '../../../../../nls.js'; -import { localize } from '../../../../../nls.js'; -import { Action2, MenuId, MenuRegistry } from '../../../../../platform/actions/common/actions.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; -import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; -import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { ILogService } from '../../../../../platform/log/common/log.js'; -import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { IChatService } from '../../common/chatService.js'; -import { IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; -import { ChatConfiguration, LEGACY_AGENT_SESSIONS_VIEW_ID } from '../../common/constants.js'; -import { AGENT_SESSIONS_VIEW_CONTAINER_ID, AGENT_SESSIONS_VIEW_ID } from '../agentSessions/agentSessions.js'; -import { ChatViewPaneTarget, IChatWidgetService } from '../chat.js'; -import { ACTION_ID_OPEN_CHAT, CHAT_CATEGORY } from './chatActions.js'; - -export interface IMarshalledChatSessionContext { - readonly $mid: MarshalledId.ChatSessionContext; - readonly session: IChatSessionItem; -} - -export function isMarshalledChatSessionContext(thing: unknown): thing is IMarshalledChatSessionContext { - if (typeof thing === 'object' && thing !== null) { - const candidate = thing as IMarshalledChatSessionContext; - return candidate.$mid === MarshalledId.ChatSessionContext && typeof candidate.session === 'object' && candidate.session !== null; - } - - return false; -} - -export class RenameChatSessionAction extends Action2 { - static readonly id = 'workbench.action.chat.renameSession'; - - constructor() { - super({ - id: RenameChatSessionAction.id, - title: localize('renameSession', "Rename"), - f1: false, - category: CHAT_CATEGORY, - icon: Codicon.pencil, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.F2, - when: ContextKeyExpr.equals('focusedView', 'workbench.view.chat.sessions.local') - } - }); - } - - async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { - if (!context) { - return; - } - - // Handle marshalled context from menu actions - const label = context.session.label; - const chatSessionsService = accessor.get(IChatSessionsService); - const logService = accessor.get(ILogService); - const chatService = accessor.get(IChatService); - - try { - // Find the chat sessions view and trigger inline rename mode - // This is similar to how file renaming works in the explorer - await chatSessionsService.setEditableSession(context.session.resource, { - validationMessage: (value: string) => { - if (!value || value.trim().length === 0) { - return { content: localize('renameSession.emptyName', "Name cannot be empty"), severity: Severity.Error }; - } - if (value.length > 100) { - return { content: localize('renameSession.nameTooLong', "Name is too long (maximum 100 characters)"), severity: Severity.Error }; - } - return null; - }, - placeholder: localize('renameSession.placeholder', "Enter new name for chat session"), - startingValue: label, - onFinish: async (value: string, success: boolean) => { - if (success && value && value.trim() !== label) { - try { - const newTitle = value.trim(); - chatService.setChatSessionTitle(context.session.resource, newTitle); - // Notify the local sessions provider that items have changed - chatSessionsService.notifySessionItemsChanged(localChatSessionType); - } catch (error) { - logService.error( - localize('renameSession.error', "Failed to rename chat session: {0}", - (error instanceof Error ? error.message : String(error))) - ); - } - } - await chatSessionsService.setEditableSession(context.session.resource, null); - } - }); - } catch (error) { - logService.error('Failed to rename chat session', error instanceof Error ? error.message : String(error)); - } - } -} - -/** - * Action to delete a chat session from history - */ -export class DeleteChatSessionAction extends Action2 { - static readonly id = 'workbench.action.chat.deleteSession'; - - constructor() { - super({ - id: DeleteChatSessionAction.id, - title: localize('deleteSession', "Delete"), - f1: false, - category: CHAT_CATEGORY, - icon: Codicon.x, - }); - } - - async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { - if (!context) { - return; - } - - // Handle marshalled context from menu actions - const chatService = accessor.get(IChatService); - const dialogService = accessor.get(IDialogService); - const logService = accessor.get(ILogService); - const chatSessionsService = accessor.get(IChatSessionsService); - - try { - // Show confirmation dialog - const result = await dialogService.confirm({ - message: localize('deleteSession.confirm', "Are you sure you want to delete this chat session?"), - detail: localize('deleteSession.detail', "This action cannot be undone."), - primaryButton: localize('deleteSession.delete', "Delete"), - type: 'warning' - }); - - if (result.confirmed) { - await chatService.removeHistoryEntry(context.session.resource); - // Notify the local sessions provider that items have changed - chatSessionsService.notifySessionItemsChanged(localChatSessionType); - } - } catch (error) { - logService.error('Failed to delete chat session', error instanceof Error ? error.message : String(error)); - } - } -} - - -/** - * Action to open a chat session in the sidebar (chat widget) - */ -export class OpenChatSessionInSidebarAction extends Action2 { - static readonly id = 'workbench.action.chat.openSessionInSidebar'; - - constructor() { - super({ - id: OpenChatSessionInSidebarAction.id, - title: localize('chat.openSessionInSidebar.label', "Move Chat into Side Bar"), - category: CHAT_CATEGORY, - f1: false, - }); - } - - async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { - const chatWidgetService = accessor.get(IChatWidgetService); - - if (!context) { - return; - } - - // TODO: this feels strange. Should we prefer moving the editor to the sidebar instead? @osortega - await chatWidgetService.openSession(context.session.resource, ChatViewPaneTarget); - } -} - -/** - * Action to toggle the description display mode for Chat Sessions - */ -export class ToggleChatSessionsDescriptionDisplayAction extends Action2 { - static readonly id = 'workbench.action.chatSessions.toggleDescriptionDisplay'; - - constructor() { - super({ - id: ToggleChatSessionsDescriptionDisplayAction.id, - title: localize('chatSessions.toggleDescriptionDisplay.label', "Show Rich Descriptions"), - category: CHAT_CATEGORY, - f1: false, - toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ShowAgentSessionsViewDescription}`, true) - }); - } - - async run(accessor: ServicesAccessor): Promise { - const configurationService = accessor.get(IConfigurationService); - const currentValue = configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription); - - await configurationService.updateValue( - ChatConfiguration.ShowAgentSessionsViewDescription, - !currentValue - ); - } -} - -/** - * Action to toggle between 'view' and 'single-view' modes for Agent Sessions - */ -export class ToggleAgentSessionsViewLocationAction extends Action2 { - - static readonly id = 'workbench.action.chatSessions.toggleNewCombinedView'; - - constructor() { - super({ - id: ToggleAgentSessionsViewLocationAction.id, - title: localize('chatSessions.toggleViewLocation.label', "Combined Sessions View"), - category: CHAT_CATEGORY, - f1: false, - toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.AgentSessionsViewLocation}`, 'single-view'), - menu: [ - { - id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.equals('viewContainer', LEGACY_AGENT_SESSIONS_VIEW_ID), - group: '2_togglenew', - order: 1 - }, - { - id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.equals('viewContainer', AGENT_SESSIONS_VIEW_CONTAINER_ID), - group: '2_togglenew', - order: 1 - } - ] - }); - } - - async run(accessor: ServicesAccessor): Promise { - const configurationService = accessor.get(IConfigurationService); - const viewsService = accessor.get(IViewsService); - - const currentValue = configurationService.getValue(ChatConfiguration.AgentSessionsViewLocation); - - const newValue = currentValue === 'single-view' ? 'view' : 'single-view'; - - await configurationService.updateValue(ChatConfiguration.AgentSessionsViewLocation, newValue); - - const viewId = newValue === 'single-view' ? AGENT_SESSIONS_VIEW_ID : `${LEGACY_AGENT_SESSIONS_VIEW_ID}.local`; - await viewsService.openView(viewId, true); - } -} - -// Register the menu item - show for all local chat sessions (including history items) -MenuRegistry.appendMenuItem(MenuId.AgentSessionsContext, { - command: { - id: RenameChatSessionAction.id, - title: localize('renameSession', "Rename"), - icon: Codicon.pencil - }, - group: 'inline', - order: 1, - when: ContextKeyExpr.and( - ChatContextKeys.agentSessionType.isEqualTo(localChatSessionType), - ChatContextKeys.isCombinedAgentSessionsViewer.negate() - ) -}); - -// Register delete menu item - only show for non-active sessions (history items) -MenuRegistry.appendMenuItem(MenuId.AgentSessionsContext, { - command: { - id: DeleteChatSessionAction.id, - title: localize('deleteSession', "Delete"), - icon: Codicon.x - }, - group: 'inline', - order: 2, - when: ContextKeyExpr.and( - ChatContextKeys.isArchivedAgentSession.isEqualTo(true), - ChatContextKeys.isActiveAgentSession.isEqualTo(false) - ) -}); - -MenuRegistry.appendMenuItem(MenuId.AgentSessionsContext, { - command: { - id: OpenChatSessionInSidebarAction.id, - title: localize('openSessionInSidebar', "Open in Sidebar") - }, - group: 'navigation', - order: 3, - when: ChatContextKeys.isCombinedAgentSessionsViewer.negate() -}); - -// Register the toggle command for the ViewTitle menu -MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { - command: { - id: ToggleChatSessionsDescriptionDisplayAction.id, - title: localize('chatSessions.toggleDescriptionDisplay.label', "Show Rich Descriptions"), - toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ShowAgentSessionsViewDescription}`, true) - }, - group: '1_config', - order: 1, - when: ContextKeyExpr.equals('viewContainer', LEGACY_AGENT_SESSIONS_VIEW_ID), -}); - -MenuRegistry.appendMenuItem(MenuId.ViewTitle, { - command: { - id: ACTION_ID_OPEN_CHAT, - title: nls.localize2('interactiveSession.open', "New Chat Editor"), - icon: Codicon.plus - }, - group: 'navigation', - order: 1, - when: ContextKeyExpr.equals('view', `${LEGACY_AGENT_SESSIONS_VIEW_ID}.local`), -}); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts index 558deb1acc9..da5824ec07d 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts @@ -6,60 +6,14 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { localize2 } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; import { registerSingleton, InstantiationType } from '../../../../../platform/instantiation/common/extensions.js'; -import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js'; -import { ViewPaneContainer } from '../../../../browser/parts/views/viewPaneContainer.js'; -import { IViewContainersRegistry, ViewContainerLocation, IViewDescriptor, IViewsRegistry, Extensions as ViewExtensions } from '../../../../common/views.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { ChatConfiguration } from '../../common/constants.js'; -import { AGENT_SESSIONS_VIEW_CONTAINER_ID, AGENT_SESSIONS_VIEW_ID, AgentSessionsViewerOrientation, AgentSessionsViewerPosition } from './agentSessions.js'; +import { AgentSessionsViewerOrientation, AgentSessionsViewerPosition } from './agentSessions.js'; import { IAgentSessionsService, AgentSessionsService } from './agentSessionsService.js'; -import { AgentSessionsView } from './agentSessionsView.js'; -import { Registry } from '../../../../../platform/registry/common/platform.js'; import { LocalAgentsSessionsProvider } from './localAgentSessionsProvider.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../common/contributions.js'; import { ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js'; -import { ArchiveAgentSessionAction, UnarchiveAgentSessionAction, RefreshAgentSessionsViewAction, FindAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, FocusAgentSessionsAction } from './agentSessionsActions.js'; - -//#region View Container and View Registration - -const chatAgentsIcon = registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'); - -const AGENT_SESSIONS_VIEW_TITLE = localize2('agentSessions.view.label', "Agents"); - -const agentSessionsViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ - id: AGENT_SESSIONS_VIEW_CONTAINER_ID, - title: AGENT_SESSIONS_VIEW_TITLE, - icon: chatAgentsIcon, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [AGENT_SESSIONS_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }]), - storageId: AGENT_SESSIONS_VIEW_CONTAINER_ID, - hideIfEmpty: true, - order: 6, -}, ViewContainerLocation.AuxiliaryBar); - -const agentSessionsViewDescriptor: IViewDescriptor = { - id: AGENT_SESSIONS_VIEW_ID, - containerIcon: chatAgentsIcon, - containerTitle: AGENT_SESSIONS_VIEW_TITLE.value, - singleViewPaneContainerTitle: AGENT_SESSIONS_VIEW_TITLE.value, - name: AGENT_SESSIONS_VIEW_TITLE, - canToggleVisibility: false, - canMoveView: true, - openCommandActionDescriptor: { - id: AGENT_SESSIONS_VIEW_ID, - title: AGENT_SESSIONS_VIEW_TITLE - }, - ctorDescriptor: new SyncDescriptor(AgentSessionsView), - when: ContextKeyExpr.and( - ChatContextKeys.Setup.hidden.negate(), - ChatContextKeys.Setup.disabled.negate(), - ContextKeyExpr.equals(`config.${ChatConfiguration.AgentSessionsViewLocation}`, 'single-view'), - ) -}; -Registry.as(ViewExtensions.ViewsRegistry).registerViews([agentSessionsViewDescriptor], agentSessionsViewContainer); - -//#endregion +import { ArchiveAgentSessionAction, UnarchiveAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, FocusAgentSessionsAction } from './agentSessionsActions.js'; //#region Actions and Menus @@ -71,21 +25,11 @@ registerAction2(MarkAgentSessionReadAction); registerAction2(OpenAgentSessionInNewWindowAction); registerAction2(OpenAgentSessionInEditorGroupAction); registerAction2(OpenAgentSessionInNewEditorGroupAction); -registerAction2(RefreshAgentSessionsViewAction); -registerAction2(FindAgentSessionAction); registerAction2(RefreshAgentSessionsViewerAction); registerAction2(FindAgentSessionInViewerAction); registerAction2(ShowAgentSessionsSidebar); registerAction2(HideAgentSessionsSidebar); -MenuRegistry.appendMenuItem(MenuId.AgentSessionsViewTitle, { - submenu: MenuId.AgentSessionsFilterSubMenu, - title: localize2('filterAgentSessions', "Filter Agent Sessions"), - group: 'navigation', - order: 100, - icon: Codicon.filter -} satisfies ISubmenuItem); - // --- Agent Sessions Toolbar MenuRegistry.appendMenuItem(MenuId.AgentSessionsToolbar, { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts index 8a5eeb5530a..d43f5b6f8eb 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts @@ -6,16 +6,9 @@ import { localize } from '../../../../../nls.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { localChatSessionType } from '../../common/chatSessionsService.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; -import { LEGACY_AGENT_SESSIONS_VIEW_ID } from '../../common/constants.js'; -import { ChatViewId } from '../chat.js'; +import { IChatSessionItem, localChatSessionType } from '../../common/chatSessionsService.js'; import { foreground, listActiveSelectionForeground, registerColor, transparent } from '../../../../../platform/theme/common/colorRegistry.js'; - -export const AGENT_SESSIONS_VIEW_CONTAINER_ID = 'workbench.viewContainer.agentSessions'; -export const AGENT_SESSIONS_VIEW_ID = 'workbench.view.agentSessions'; +import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; export enum AgentSessionProviders { Local = localChatSessionType, @@ -45,20 +38,6 @@ export function getAgentSessionProviderIcon(provider: AgentSessionProviders): Th } } -export function openAgentSessionsView(accessor: ServicesAccessor): void { - const viewService = accessor.get(IViewsService); - const configurationService = accessor.get(IConfigurationService); - - const viewLocation = configurationService.getValue('chat.agentSessionsViewLocation'); - if (viewLocation === 'single-view') { - viewService.openView(AGENT_SESSIONS_VIEW_ID, true); - } else if (viewLocation === 'view') { - viewService.openViewContainer(LEGACY_AGENT_SESSIONS_VIEW_ID, true); - } else { - viewService.openView(ChatViewId, true); - } -} - export enum AgentSessionsViewerOrientation { Stacked = 1, SideBySide, @@ -91,3 +70,17 @@ export const agentSessionSelectedUnfocusedBadgeBorder = registerColor( { dark: transparent(foreground, 0.3), light: transparent(foreground, 0.3), hcDark: foreground, hcLight: foreground }, localize('agentSessionSelectedUnfocusedBadgeBorder', "Border color for the badges in selected agent session items when the view is unfocused.") ); + +export interface IMarshalledChatSessionContext { + readonly $mid: MarshalledId.ChatSessionContext; + readonly session: IChatSessionItem; +} + +export function isMarshalledChatSessionContext(thing: unknown): thing is IMarshalledChatSessionContext { + if (typeof thing === 'object' && thing !== null) { + const candidate = thing as IMarshalledChatSessionContext; + return candidate.$mid === MarshalledId.ChatSessionContext && typeof candidate.session === 'object' && candidate.session !== null; + } + + return false; +} diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts index 7a9c39b066d..18293eecfb5 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts @@ -8,12 +8,9 @@ import { IAgentSession } from './agentSessionsModel.js'; import { Action2, MenuId } from '../../../../../platform/actions/common/actions.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; -import { ViewAction } from '../../../../browser/parts/views/viewPane.js'; -import { AGENT_SESSIONS_VIEW_ID, AgentSessionsViewerOrientation, IAgentSessionsControl } from './agentSessions.js'; +import { AgentSessionsViewerOrientation, IAgentSessionsControl, IMarshalledChatSessionContext, isMarshalledChatSessionContext } from './agentSessions.js'; import { IChatService } from '../../common/chatService.js'; -import { AgentSessionsView } from './agentSessionsView.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { IMarshalledChatSessionContext, isMarshalledChatSessionContext } from '../actions/chatSessionActions.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, PreferredGroup, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; @@ -307,52 +304,6 @@ export class MarkAgentSessionReadAction extends BaseAgentSessionAction { //#endregion -//#region View Actions - -export class RefreshAgentSessionsViewAction extends ViewAction { - - constructor() { - super({ - id: 'agentSessionsView.refresh', - title: localize2('refresh', "Refresh Agent Sessions"), - icon: Codicon.refresh, - menu: { - id: MenuId.AgentSessionsViewTitle, - group: 'navigation', - order: 1 - }, - viewId: AGENT_SESSIONS_VIEW_ID - }); - } - - runInView(accessor: ServicesAccessor, view: AgentSessionsView): void { - view.refresh(); - } -} - -export class FindAgentSessionAction extends ViewAction { - - constructor() { - super({ - id: 'agentSessionsView.find', - title: localize2('find', "Find Agent Session"), - icon: Codicon.search, - menu: { - id: MenuId.AgentSessionsViewTitle, - group: 'navigation', - order: 2 - }, - viewId: AGENT_SESSIONS_VIEW_ID - }); - } - - runInView(accessor: ServicesAccessor, view: AgentSessionsView): void { - view.openFind(); - } -} - -//#endregion - //#region Sessions Control Toolbar export class RefreshAgentSessionsViewerAction extends Action2 { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts index 6aca44fc1eb..e781843c6fa 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts @@ -15,44 +15,38 @@ import { FuzzyScore } from '../../../../../base/common/filters.js'; import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { getSessionItemContextOverlay } from '../chatSessions/common.js'; import { ACTION_ID_NEW_CHAT } from '../actions/chatActions.js'; import { IChatEditorOptions } from '../chatEditor.js'; -import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ITreeContextMenuEvent } from '../../../../../base/browser/ui/tree/tree.js'; import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; import { Separator } from '../../../../../base/common/actions.js'; -import { IChatService } from '../../common/chatService.js'; import { ChatViewPaneTarget, IChatWidgetService } from '../chat.js'; import { TreeFindMode } from '../../../../../base/browser/ui/tree/abstractTree.js'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; -import { IMarshalledChatSessionContext } from '../actions/chatSessionActions.js'; +import { ACTIVE_GROUP, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; import { IAgentSessionsService } from './agentSessionsService.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { IListStyles } from '../../../../../base/browser/ui/list/listWidget.js'; import { IStyleOverride } from '../../../../../platform/theme/browser/defaultStyles.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; -import { IAgentSessionsControl } from './agentSessions.js'; +import { IAgentSessionsControl, IMarshalledChatSessionContext } from './agentSessions.js'; import { Schemas } from '../../../../../base/common/network.js'; +import { HoverPosition } from '../../../../../base/browser/ui/hover/hoverWidget.js'; export interface IAgentSessionsControlOptions { readonly overrideStyles?: IStyleOverride; readonly filter?: IAgentSessionsFilter; - readonly allowOpenSessionsInPanel?: boolean; // TODO@bpasero retire this option eventually - readonly trackActiveEditor?: boolean; + + getHoverPosition(): HoverPosition; } type AgentSessionOpenedClassification = { owner: 'bpasero'; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'From where the session was opened.' }; providerType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The provider type of the opened agent session.' }; comment: 'Event fired when a agent session is opened from the agent sessions control.'; }; type AgentSessionOpenedEvent = { - source: 'agentsView' | 'chatView'; providerType: string; }; @@ -65,57 +59,20 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo constructor( private readonly container: HTMLElement, - private readonly options: IAgentSessionsControlOptions | undefined, + private readonly options: IAgentSessionsControlOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, @ICommandService private readonly commandService: ICommandService, - @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, - @IChatService private readonly chatService: IChatService, @IMenuService private readonly menuService: IMenuService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IEditorService private readonly editorService: IEditorService, ) { super(); this.createList(this.container); - - this.registerListeners(); - } - - private registerListeners(): void { - if (this.options?.trackActiveEditor) { - this._register(this.editorService.onDidActiveEditorChange(() => this.revealAndFocusActiveEditorSession())); - } - } - - private revealAndFocusActiveEditorSession(): void { - if (!this.visible) { - return; - } - - const input = this.editorService.activeEditor; - if (!(input instanceof ChatEditorInput)) { - return; - } - - const sessionResource = input.sessionResource; - if (!sessionResource) { - return; - } - - const matchingSession = this.agentSessionsService.model.getSession(sessionResource); - if (matchingSession && this.sessionsList?.hasNode(matchingSession)) { - if (this.sessionsList.getRelativeTop(matchingSession) === null) { - this.sessionsList.reveal(matchingSession, 0.5); // only reveal when not already visible - } - - this.sessionsList.setFocus([matchingSession]); - this.sessionsList.setSelection([matchingSession]); - } } private createList(container: HTMLElement): void { @@ -128,7 +85,7 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo new AgentSessionsListDelegate(), new AgentSessionsCompressionDelegate(), [ - this.instantiationService.createInstance(AgentSessionRenderer) + this.instantiationService.createInstance(AgentSessionRenderer, this.options), ], new AgentSessionsDataSource(this.options?.filter, sorter), { @@ -176,7 +133,6 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo } this.telemetryService.publicLog2('agentSessionOpened', { - source: this.options?.allowOpenSessionsInPanel ? 'chatView' : 'agentsView', providerType: session.providerType }); @@ -194,18 +150,16 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo let options: IChatEditorOptions = { ...sessionOptions, ...e.editorOptions, - revealIfOpened: this.options?.allowOpenSessionsInPanel // always try to reveal if already opened + revealIfOpened: true // always try to reveal if already opened }; await this.chatSessionsService.activateChatSessionItemProvider(session.providerType); // ensure provider is activated before trying to open let target: typeof SIDE_GROUP | typeof ACTIVE_GROUP | typeof ChatViewPaneTarget | undefined; if (e.sideBySide) { - target = this.options?.allowOpenSessionsInPanel ? ACTIVE_GROUP : SIDE_GROUP; - } else if (this.options?.allowOpenSessionsInPanel) { - target = ChatViewPaneTarget; - } else { target = ACTIVE_GROUP; + } else { + target = ChatViewPaneTarget; } const isLocalChatSession = session.resource.scheme === Schemas.vscodeChatEditor || session.resource.scheme === Schemas.vscodeLocalChatSession; @@ -224,11 +178,12 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo EventHelper.stop(browserEvent, true); - const provider = await this.chatSessionsService.activateChatSessionItemProvider(session.providerType); - const contextOverlay = getSessionItemContextOverlay(session, provider, this.chatService, this.editorGroupsService); - contextOverlay.push([ChatContextKeys.isCombinedAgentSessionsViewer.key, true]); + await this.chatSessionsService.activateChatSessionItemProvider(session.providerType); + + const contextOverlay: Array<[string, boolean | string]> = []; contextOverlay.push([ChatContextKeys.isReadAgentSession.key, session.isRead()]); contextOverlay.push([ChatContextKeys.isArchivedAgentSession.key, session.isArchived()]); + contextOverlay.push([ChatContextKeys.agentSessionType.key, session.providerType]); const menu = this.menuService.createMenu(MenuId.AgentSessionsContext, this.contextKeyService.createOverlay(contextOverlay)); const marshalledSession: IMarshalledChatSessionContext = { session, $mid: MarshalledId.ChatSessionContext }; diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts deleted file mode 100644 index ec41db066a7..00000000000 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ /dev/null @@ -1,246 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import './media/agentsessionsview.css'; -import { localize } from '../../../../../nls.js'; -import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { IViewPaneOptions, ViewPane } from '../../../../browser/parts/views/viewPane.js'; -import { IViewDescriptorService } from '../../../../common/views.js'; -import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; -import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; -import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; -import { $, append } from '../../../../../base/browser/dom.js'; -import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; -import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js'; -import { IAction, Separator, toAction } from '../../../../../base/common/actions.js'; -import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; -import { IChatSessionsService } from '../../common/chatSessionsService.js'; -import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; -import { ACTION_ID_OPEN_CHAT } from '../actions/chatActions.js'; -import { IProgressService } from '../../../../../platform/progress/common/progress.js'; -import { DeferredPromise } from '../../../../../base/common/async.js'; -import { Event } from '../../../../../base/common/event.js'; -import { MutableDisposable } from '../../../../../base/common/lifecycle.js'; -import { getActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { AgentSessionProviders } from './agentSessions.js'; -import { AgentSessionsFilter } from './agentSessionsFilter.js'; -import { AgentSessionsControl } from './agentSessionsControl.js'; -import { IAgentSessionsService } from './agentSessionsService.js'; -import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; - -type AgentSessionsViewPaneOpenedClassification = { - owner: 'bpasero'; - comment: 'Event fired when the agent sessions pane is opened'; -}; - -export class AgentSessionsView extends ViewPane { - - constructor( - options: IViewPaneOptions, - @IKeybindingService keybindingService: IKeybindingService, - @IContextMenuService contextMenuService: IContextMenuService, - @IConfigurationService configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IInstantiationService instantiationService: IInstantiationService, - @IOpenerService openerService: IOpenerService, - @IThemeService themeService: IThemeService, - @IHoverService hoverService: IHoverService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @ICommandService private readonly commandService: ICommandService, - @IProgressService private readonly progressService: IProgressService, - @IMenuService private readonly menuService: IMenuService, - @IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - ) { - super({ ...options, titleMenuId: MenuId.AgentSessionsViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); - - this.registerListeners(); - } - - private registerListeners(): void { - const sessionsModel = this.agentSessionsService.model; - const didResolveDisposable = this._register(new MutableDisposable()); - this._register(sessionsModel.onWillResolve(() => { - const didResolve = new DeferredPromise(); - didResolveDisposable.value = Event.once(sessionsModel.onDidResolve)(() => didResolve.complete()); - - this.progressService.withProgress( - { - location: this.id, - title: localize('agentSessions.refreshing', 'Refreshing agent sessions...'), - delay: 500 - }, - () => didResolve.p - ); - })); - } - - protected override renderBody(container: HTMLElement): void { - super.renderBody(container); - - this.telemetryService.publicLog2<{}, AgentSessionsViewPaneOpenedClassification>('agentSessionsViewPaneOpened'); - - container.classList.add('agent-sessions-view'); - - // New Session - this.createNewSessionButton(container); - - // Sessions Control - this.createSessionsControl(container); - } - - //#region New Session Controls - - private newSessionContainer: HTMLElement | undefined; - - private createNewSessionButton(container: HTMLElement): void { - this.newSessionContainer = append(container, $('.agent-sessions-new-session-container')); - - const newSessionButton = this._register(new ButtonWithDropdown(this.newSessionContainer, { - title: localize('agentSessions.newSession', "New Session"), - ariaLabel: localize('agentSessions.newSessionAriaLabel', "New Session"), - contextMenuProvider: this.contextMenuService, - actions: { - getActions: () => { - return this.getNewSessionActions(); - } - }, - addPrimaryActionToDropdown: false, - ...defaultButtonStyles, - })); - - newSessionButton.label = localize('agentSessions.newSession', "New Session"); - - this._register(newSessionButton.onDidClick(() => this.commandService.executeCommand(ACTION_ID_OPEN_CHAT))); - } - - private getNewSessionActions(): IAction[] { - const actions: IAction[] = []; - - // Default action - actions.push(toAction({ - id: 'newChatSession.default', - label: localize('newChatSessionDefault', "New Local Session"), - run: () => this.commandService.executeCommand(ACTION_ID_OPEN_CHAT) - })); - - // Background (CLI) - actions.push(toAction({ - id: 'newChatSessionFromProvider.background', - label: localize('newBackgroundSession', "New Background Session"), - run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${AgentSessionProviders.Background}`) - })); - - // Cloud - actions.push(toAction({ - id: 'newChatSessionFromProvider.cloud', - label: localize('newCloudSession', "New Cloud Session"), - run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${AgentSessionProviders.Cloud}`) - })); - - let addedSeparator = false; - for (const provider of this.chatSessionsService.getAllChatSessionContributions()) { - if (provider.type === AgentSessionProviders.Background || provider.type === AgentSessionProviders.Cloud) { - continue; // already added above - } - - if (!addedSeparator) { - actions.push(new Separator()); - addedSeparator = true; - } - - const menuActions = this.menuService.getMenuActions(MenuId.AgentSessionsCreateSubMenu, this.scopedContextKeyService.createOverlay([ - [ChatContextKeys.agentSessionType.key, provider.type] - ])); - - const primaryActions = getActionBarActions(menuActions, () => true).primary; - - // Prefer provider creation actions... - if (primaryActions.length > 0) { - actions.push(...primaryActions); - } - - // ...over our generic one - else { - actions.push(toAction({ - id: `newChatSessionFromProvider.${provider.type}`, - label: localize('newChatSessionFromProvider', "New {0}", provider.displayName), - run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${provider.type}`) - })); - } - } - - // Install more - const installMenuActions = this.menuService.getMenuActions(MenuId.AgentSessionsInstallMenu, this.scopedContextKeyService, { shouldForwardArgs: true }); - const installActionBar = getActionBarActions(installMenuActions, () => true); - if (installActionBar.primary.length > 0) { - actions.push(new Separator()); - actions.push(...installActionBar.primary); - } - - return actions; - } - - //#endregion - - //#region Sessions Control - - private sessionsControl: AgentSessionsControl | undefined; - - private createSessionsControl(container: HTMLElement): void { - const sessionsFilter = this._register(this.instantiationService.createInstance(AgentSessionsFilter, { - filterMenuId: MenuId.AgentSessionsFilterSubMenu, - })); - - this.sessionsControl = this._register(this.instantiationService.createInstance(AgentSessionsControl, - container, - { - filter: sessionsFilter, - trackActiveEditor: true, - } - )); - this.sessionsControl.setVisible(this.isBodyVisible()); - - this._register(this.onDidChangeBodyVisibility(visible => { - this.sessionsControl?.setVisible(visible); - })); - } - - //#endregion - - //#region Actions internal API - - openFind(): void { - this.sessionsControl?.openFind(); - } - - refresh(): void { - this.sessionsControl?.refresh(); - } - - //#endregion - - protected override layoutBody(height: number, width: number): void { - super.layoutBody(height, width); - - let sessionsControlHeight = height; - sessionsControlHeight -= this.newSessionContainer?.offsetHeight ?? 0; - - this.sessionsControl?.layout(sessionsControlHeight, width); - } - - override focus(): void { - super.focus(); - - this.sessionsControl?.focus(); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index f82e95ae30e..032b5ba771c 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -30,10 +30,7 @@ import { fillEditorsDragData } from '../../../../browser/dnd.js'; import { ChatSessionStatus } from '../../common/chatSessionsService.js'; import { HoverStyle } from '../../../../../base/browser/ui/hover/hover.js'; import { HoverPosition } from '../../../../../base/browser/ui/hover/hoverWidget.js'; -import { IWorkbenchLayoutService, Position } from '../../../../services/layout/browser/layoutService.js'; -import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; -import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js'; import { IntervalTimer } from '../../../../../base/common/async.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; @@ -68,6 +65,10 @@ interface IAgentSessionItemTemplate { readonly disposables: IDisposable; } +export interface IAgentSessionRendererOptions { + getHoverPosition(): HoverPosition; +} + export class AgentSessionRenderer implements ICompressibleTreeRenderer { static readonly TEMPLATE_ID = 'agent-session'; @@ -75,10 +76,9 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer { - const sideBarPosition = this.layoutService.getSideBarPosition(); - const viewLocation = this.viewDescriptorService.getViewLocationById(AGENT_SESSIONS_VIEW_ID); - switch (viewLocation) { - case ViewContainerLocation.Sidebar: - return sideBarPosition === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT; - case ViewContainerLocation.AuxiliaryBar: - return sideBarPosition === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT; - default: - return HoverPosition.RIGHT; - } - })() + hoverPosition: this.options.getHoverPosition() } }), { groupId: 'agent.sessions' }) ); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsProvider.ts index 800141b1100..c1ae636d6f8 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsProvider.ts @@ -15,7 +15,10 @@ import { IChatModel } from '../../common/chatModel.js'; import { IChatDetail, IChatService, ResponseModelState } from '../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { getChatSessionType } from '../../common/chatUri.js'; -import { ChatSessionItemWithProvider } from '../chatSessions/common.js'; + +interface IChatSessionItemWithProvider extends IChatSessionItem { + readonly provider: IChatSessionItemProvider; +} export class LocalAgentsSessionsProvider extends Disposable implements IChatSessionItemProvider, IWorkbenchContribution { @@ -41,14 +44,12 @@ export class LocalAgentsSessionsProvider extends Disposable implements IChatSess } private registerListeners(): void { - this._register(this.chatSessionsService.registerChatModelChangeListeners( this.chatService, Schemas.vscodeLocalChatSession, () => this._onDidChangeChatSessionItems.fire() )); - // Listen for global session items changes for our session type this._register(this.chatSessionsService.onDidChangeSessionItems(sessionType => { if (sessionType === this.chatSessionType) { this._onDidChange.fire(); @@ -63,35 +64,8 @@ export class LocalAgentsSessionsProvider extends Disposable implements IChatSess })); } - - private modelToStatus(model: IChatModel): ChatSessionStatus | undefined { - if (model.requestInProgress.get()) { - return ChatSessionStatus.InProgress; - } - - const requests = model.getRequests(); - if (requests.length > 0) { - - // Check if the last request was completed successfully or failed - const lastRequest = requests[requests.length - 1]; - if (lastRequest?.response) { - if (lastRequest.response.isCanceled || lastRequest.response.result?.errorDetails?.code === 'canceled') { - return ChatSessionStatus.Completed; - } else if (lastRequest.response.result?.errorDetails) { - return ChatSessionStatus.Failed; - } else if (lastRequest.response.isComplete) { - return ChatSessionStatus.Completed; - } else { - return ChatSessionStatus.InProgress; - } - } - } - - return undefined; - } - async provideChatSessionItems(token: CancellationToken): Promise { - const sessions: ChatSessionItemWithProvider[] = []; + const sessions: IChatSessionItemWithProvider[] = []; const sessionsByResource = new ResourceSet(); for (const sessionDetail of await this.chatService.getLiveSessionItems()) { @@ -112,23 +86,17 @@ export class LocalAgentsSessionsProvider extends Disposable implements IChatSess return sessions; } - private async getHistoryItems(): Promise { + private async getHistoryItems(): Promise { try { const historyItems = await this.chatService.getHistorySessionItems(); - return coalesce(historyItems.map(history => { - const sessionItem = this.toChatSessionItem(history); - return sessionItem ? { - ...sessionItem, - //todo@bpasero remove this property once classic view is gone - history: true - } : undefined; - })); + + return coalesce(historyItems.map(history => this.toChatSessionItem(history))); } catch (error) { return []; } } - private toChatSessionItem(chat: IChatDetail): ChatSessionItemWithProvider | undefined { + private toChatSessionItem(chat: IChatDetail): IChatSessionItemWithProvider | undefined { const model = this.chatService.getSession(chat.sessionResource); let description: string | undefined; @@ -145,9 +113,7 @@ export class LocalAgentsSessionsProvider extends Disposable implements IChatSess provider: this, label: chat.title, description, - status: model ? - this.modelToStatus(model) : - chatResponseStateToSessionStatus(chat.lastResponseState), + status: model ? this.modelToStatus(model) : this.chatResponseStateToStatus(chat.lastResponseState), iconPath: Codicon.chatSparkle, timing: chat.timing, changes: chat.stats ? { @@ -157,16 +123,37 @@ export class LocalAgentsSessionsProvider extends Disposable implements IChatSess } : undefined }; } -} -function chatResponseStateToSessionStatus(state: ResponseModelState): ChatSessionStatus { - switch (state) { - case ResponseModelState.Cancelled: - case ResponseModelState.Complete: - return ChatSessionStatus.Completed; - case ResponseModelState.Failed: - return ChatSessionStatus.Failed; - case ResponseModelState.Pending: + private modelToStatus(model: IChatModel): ChatSessionStatus | undefined { + if (model.requestInProgress.get()) { return ChatSessionStatus.InProgress; + } + + const lastRequest = model.getRequests().at(-1); + if (lastRequest?.response) { + if (lastRequest.response.isCanceled || lastRequest.response.result?.errorDetails?.code === 'canceled') { + return ChatSessionStatus.Completed; + } else if (lastRequest.response.result?.errorDetails) { + return ChatSessionStatus.Failed; + } else if (lastRequest.response.isComplete) { + return ChatSessionStatus.Completed; + } else { + return ChatSessionStatus.InProgress; + } + } + + return undefined; + } + + private chatResponseStateToStatus(state: ResponseModelState): ChatSessionStatus { + switch (state) { + case ResponseModelState.Cancelled: + case ResponseModelState.Complete: + return ChatSessionStatus.Completed; + case ResponseModelState.Failed: + return ChatSessionStatus.Failed; + case ResponseModelState.Pending: + return ChatSessionStatus.InProgress; + } } } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css deleted file mode 100644 index 3ab0bed3bc1..00000000000 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.agent-sessions-view { - - display: flex; - flex-direction: column; - - .agent-sessions-new-session-container { - padding: 6px 12px; - flex: 0 0 auto; - } - - .agent-sessions-new-session-container .monaco-dropdown-button { - padding: 0 4px; - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index f10f6315ab9..8aa12890df4 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -81,7 +81,6 @@ import { registerNewChatActions } from './actions/chatNewActions.js'; import { registerChatPromptNavigationActions } from './actions/chatPromptNavigationActions.js'; import { registerQuickChatActions } from './actions/chatQuickInputActions.js'; import { ChatAgentRecommendation } from './actions/chatAgentRecommendationActions.js'; -import { DeleteChatSessionAction, OpenChatSessionInSidebarAction, RenameChatSessionAction, ToggleAgentSessionsViewLocationAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js'; import { registerChatTitleActions } from './actions/chatTitleActions.js'; import { registerChatElicitationActions } from './actions/chatElicitationActions.js'; import { registerChatToolActions } from './actions/chatToolActions.js'; @@ -113,7 +112,6 @@ import { ChatPasteProvidersFeature } from './chatPasteProviders.js'; import { QuickChatService } from './chatQuick.js'; import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; import { ChatTerminalOutputAccessibleView } from './chatTerminalOutputAccessibleView.js'; -import { ChatSessionsView, ChatSessionsViewContrib } from './chatSessions/view/chatSessionsView.js'; import { ChatSetupContribution, ChatTeardownContribution } from './chatSetup/chatSetupContributions.js'; import { ChatStatusBarEntry } from './chatStatus/chatStatusEntry.js'; import { ChatVariablesService } from './chatVariables.js'; @@ -571,16 +569,6 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, - [ChatConfiguration.AgentSessionsViewLocation]: { - type: 'string', - enum: ['disabled', 'view', 'single-view'], // TODO@bpasero remove this setting eventually - description: nls.localize('chat.sessionsViewLocation.description', "Controls where to show the agent sessions menu."), - default: 'disabled', - tags: ['preview', 'experimental'], - experiment: { - mode: 'auto' - } - }, [mcpDiscoverySection]: { type: 'object', properties: Object.fromEntries(allDiscoverySources.map(k => [k, { type: 'boolean', description: discoverySourceSettingsLabel[k] }])), @@ -819,11 +807,6 @@ configurationRegistry.registerConfiguration({ default: false, scope: ConfigurationScope.WINDOW }, - [ChatConfiguration.ShowAgentSessionsViewDescription]: { - type: 'boolean', - description: nls.localize('chat.showAgentSessionsViewDescription', "Controls whether session descriptions are displayed on a second row in the Chat Sessions view."), - default: true, - }, 'chat.allowAnonymousAccess': { // TODO@bpasero remove me eventually type: 'boolean', description: nls.localize('chat.allowAnonymousAccess', "Controls whether anonymous access is allowed in chat."), @@ -1211,8 +1194,6 @@ registerWorkbenchContribution2(ChatTransferContribution.ID, ChatTransferContribu registerWorkbenchContribution2(ChatContextContributions.ID, ChatContextContributions, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatResponseResourceFileSystemProvider.ID, ChatResponseResourceFileSystemProvider, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(PromptUrlHandler.ID, PromptUrlHandler, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(ChatSessionsViewContrib.ID, ChatSessionsViewContrib, WorkbenchPhase.AfterRestored); -registerWorkbenchContribution2(ChatSessionsView.ID, ChatSessionsView, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatEditingNotebookFileSystemProviderContrib.ID, ChatEditingNotebookFileSystemProviderContrib, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(UserToolSetsContributions.ID, UserToolSetsContributions, WorkbenchPhase.Eventually); registerWorkbenchContribution2(PromptLanguageFeaturesProvider.ID, PromptLanguageFeaturesProvider, WorkbenchPhase.Eventually); @@ -1236,7 +1217,7 @@ registerChatEditorActions(); registerChatElicitationActions(); registerChatToolActions(); registerLanguageModelActions(); - +registerAction2(ConfigureToolSets); registerEditorFeature(ChatPasteProvidersFeature); @@ -1268,11 +1249,4 @@ registerSingleton(IChatTodoListService, ChatTodoListService, InstantiationType.D registerSingleton(IChatOutputRendererService, ChatOutputRendererService, InstantiationType.Delayed); registerSingleton(IChatLayoutService, ChatLayoutService, InstantiationType.Delayed); -registerAction2(ConfigureToolSets); -registerAction2(RenameChatSessionAction); -registerAction2(DeleteChatSessionAction); -registerAction2(OpenChatSessionInSidebarAction); -registerAction2(ToggleChatSessionsDescriptionDisplayAction); -registerAction2(ToggleAgentSessionsViewLocationAction); - ChatWidget.CONTRIBS.push(ChatDynamicVariableModel); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 799f1954aae..d3e1cec9078 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -25,7 +25,6 @@ import { ILabelService } from '../../../../platform/label/common/label.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { isDark } from '../../../../platform/theme/common/theme.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { IEditableData } from '../../../common/views.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; @@ -33,10 +32,9 @@ import { ChatEditorInput } from '../browser/chatEditorInput.js'; import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatSessionStatus, IChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType, SessionOptionsChangedCallback } from '../common/chatSessionsService.js'; -import { LEGACY_AGENT_SESSIONS_VIEW_ID, ChatAgentLocation, ChatModeKind } from '../common/constants.js'; +import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; -import { NEW_CHAT_SESSION_ACTION_ID } from './chatSessions/common.js'; import { IChatModel } from '../common/chatModel.js'; import { IChatService, IChatToolInvocation } from '../common/chatService.js'; import { autorun, autorunIterableDelta, observableSignalFromEvent } from '../../../../base/common/observable.js'; @@ -277,7 +275,6 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private readonly _sessionTypeInputPlaceholders: Map = new Map(); private readonly _sessions = new ResourceMap(); - private readonly _editableSessions = new ResourceMap(); private readonly _hasCanDelegateProvidersKey: IContextKey; @@ -489,56 +486,9 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const rawMenuActions = this._menuService.getMenuActions(MenuId.AgentSessionsCreateSubMenu, contextKeyService); const menuActions = rawMenuActions.map(value => value[1]).flat(); - const whenClause = ContextKeyExpr.and( - ContextKeyExpr.equals('view', `${LEGACY_AGENT_SESSIONS_VIEW_ID}.${contribution.type}`) - ); - const disposables = new DisposableStore(); - // If there's exactly one action, inline it - if (menuActions.length === 1) { - const first = menuActions[0]; - if (first instanceof MenuItemAction) { - disposables.add(MenuRegistry.appendMenuItem(MenuId.ViewTitle, { - group: 'navigation', - title: first.label, - icon: Codicon.plus, - order: 1, - when: whenClause, - command: first.item, - })); - } - } - - if (menuActions.length) { - disposables.add(MenuRegistry.appendMenuItem(MenuId.ViewTitle, { - group: 'navigation', - title: localize('interactiveSession.chatSessionSubMenuTitle', "Create chat session"), - icon: Codicon.plus, - order: 1, - when: whenClause, - submenu: MenuId.AgentSessionsCreateSubMenu, - isSplitButton: menuActions.length > 1 - })); - } else { - // We control creation instead - disposables.add(MenuRegistry.appendMenuItem(MenuId.ViewTitle, { - command: { - id: `${NEW_CHAT_SESSION_ACTION_ID}.${contribution.type}`, - title: localize('interactiveSession.openNewSessionEditor', "New {0}", contribution.displayName), - icon: Codicon.plus, - source: { - id: extensionDescription.identifier.value, - title: extensionDescription.displayName || extensionDescription.name, - } - }, - group: 'navigation', - order: 1, - when: whenClause, - })); - } - - // Also mirror all create submenu actions into the global Chat New menu + // Mirror all create submenu actions into the global Chat New menu for (const action of menuActions) { if (action instanceof MenuItemAction) { disposables.add(MenuRegistry.appendMenuItem(MenuId.ChatNewMenu, { @@ -1037,25 +987,6 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return !!session?.setOption(optionId, value); } - // Implementation of editable session methods - public async setEditableSession(sessionResource: URI, data: IEditableData | null): Promise { - if (!data) { - this._editableSessions.delete(sessionResource); - } else { - this._editableSessions.set(sessionResource, data); - } - // Trigger refresh of the session views that might need to update their rendering - this._onDidChangeSessionItems.fire(localChatSessionType); - } - - public getEditableData(sessionResource: URI): IEditableData | undefined { - return this._editableSessions.get(sessionResource); - } - - public isEditable(sessionResource: URI): boolean { - return this._editableSessions.has(sessionResource); - } - public notifySessionItemsChanged(chatSessionType: string): void { this._onDidChangeSessionItems.fire(chatSessionType); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts deleted file mode 100644 index 130a543ebfb..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ /dev/null @@ -1,136 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { fromNow } from '../../../../../base/common/date.js'; -import { Schemas } from '../../../../../base/common/network.js'; -import { EditorInput } from '../../../../common/editor/editorInput.js'; -import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; -import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { IChatService } from '../../common/chatService.js'; -import { IChatSessionItem, IChatSessionItemProvider, localChatSessionType } from '../../common/chatSessionsService.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; - - -export const NEW_CHAT_SESSION_ACTION_ID = 'workbench.action.chat.openNewSessionEditor'; - -export type ChatSessionItemWithProvider = IChatSessionItem & { - readonly provider: IChatSessionItemProvider; - relativeTime?: string; - relativeTimeFullWord?: string; - hideRelativeTime?: boolean; -}; - -export function isChatSession(schemes: readonly string[], editor?: EditorInput): editor is ChatEditorInput { - if (!(editor instanceof ChatEditorInput)) { - return false; - } - - if (!schemes.includes(editor.resource?.scheme) && editor.resource?.scheme !== Schemas.vscodeLocalChatSession && editor.resource?.scheme !== Schemas.vscodeChatEditor) { - return false; - } - - if (editor.options.ignoreInView) { - return false; - } - - return true; -} - -// Helper function to update relative time for chat sessions (similar to timeline) -function updateRelativeTime(item: ChatSessionItemWithProvider, lastRelativeTime: string | undefined): string | undefined { - if (item.timing?.startTime) { - item.relativeTime = fromNow(item.timing.startTime); - item.relativeTimeFullWord = fromNow(item.timing.startTime, false, true); - if (lastRelativeTime === undefined || item.relativeTime !== lastRelativeTime) { - lastRelativeTime = item.relativeTime; - item.hideRelativeTime = false; - } else { - item.hideRelativeTime = true; - } - } else { - // Clear timestamp properties if no timestamp - item.relativeTime = undefined; - item.relativeTimeFullWord = undefined; - item.hideRelativeTime = false; - } - - return lastRelativeTime; -} - -// Helper function to extract timestamp from session item -export function extractTimestamp(item: IChatSessionItem): number | undefined { - // Use timing.startTime if available from the API - if (item.timing?.startTime) { - return item.timing.startTime; - } - - // For other items, timestamp might already be set - if ('timestamp' in item) { - // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any - return (item as any).timestamp; - } - - return undefined; -} - -// Helper function to sort sessions by timestamp (newest first) -function sortSessionsByTimestamp(sessions: ChatSessionItemWithProvider[]): void { - sessions.sort((a, b) => { - const aTime = a.timing?.startTime ?? 0; - const bTime = b.timing?.startTime ?? 0; - return bTime - aTime; // newest first - }); -} - -// Helper function to apply time grouping to a list of sessions -function applyTimeGrouping(sessions: ChatSessionItemWithProvider[]): void { - let lastRelativeTime: string | undefined; - sessions.forEach(session => { - lastRelativeTime = updateRelativeTime(session, lastRelativeTime); - }); -} - -// Helper function to process session items with timestamps, sorting, and grouping -export function processSessionsWithTimeGrouping(sessions: ChatSessionItemWithProvider[]): ChatSessionItemWithProvider[] { - const sessionsTemp = [...sessions]; - // Only process if we have sessions with timestamps - if (sessions.some(session => session.timing?.startTime !== undefined)) { - sortSessionsByTimestamp(sessionsTemp); - applyTimeGrouping(sessionsTemp); - } - return sessionsTemp; -} - -// Helper function to create context overlay for session items -export function getSessionItemContextOverlay( - session: IChatSessionItem, - provider?: IChatSessionItemProvider, - chatService?: IChatService, - editorGroupsService?: IEditorGroupsService - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): [string, any][] { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const overlay: [string, any][] = []; - if (provider) { - overlay.push([ChatContextKeys.agentSessionType.key, provider.chatSessionType]); - } - - // Mark history items - overlay.push([ChatContextKeys.isArchivedAgentSession.key, session.archived]); - - // Mark active sessions - check if session is currently open in editor or widget - let isActiveSession = false; - - if (!session.archived && provider?.chatSessionType === localChatSessionType) { - // Local non-history sessions are always active - isActiveSession = true; - } else if (session.archived && chatService && editorGroupsService) { - isActiveSession = !!chatService.getSession(session.resource); - } - - overlay.push([ChatContextKeys.isActiveAgentSession.key, isActiveSession]); - - return overlay; -} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts deleted file mode 100644 index 88351c4b541..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ /dev/null @@ -1,277 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import * as nls from '../../../../../../nls.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; -import { SyncDescriptor } from '../../../../../../platform/instantiation/common/descriptors.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { IProductService } from '../../../../../../platform/product/common/productService.js'; -import { Registry } from '../../../../../../platform/registry/common/platform.js'; -import { IStorageService } from '../../../../../../platform/storage/common/storage.js'; -import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; -import { registerIcon } from '../../../../../../platform/theme/common/iconRegistry.js'; -import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; -import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; -import { ViewPaneContainer } from '../../../../../browser/parts/views/viewPaneContainer.js'; -import { IWorkbenchContribution } from '../../../../../common/contributions.js'; -import { Extensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from '../../../../../common/views.js'; -import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; -import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; -import { ChatContextKeyExprs } from '../../../common/chatContextKeys.js'; -import { IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; -import { LEGACY_AGENT_SESSIONS_VIEW_ID } from '../../../common/constants.js'; -import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; -import { SessionsViewPane } from './sessionsViewPane.js'; - -export class ChatSessionsView extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.chatSessionsView'; - constructor() { - super(); - this.registerViewContainer(); - } - private registerViewContainer(): void { - Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( - { - id: LEGACY_AGENT_SESSIONS_VIEW_ID, - title: nls.localize2('chat.agent.sessions', "Agent Sessions"), - ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer), - hideIfEmpty: true, - icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), - order: 6 - }, ViewContainerLocation.Sidebar); - } - -} - -export class ChatSessionsViewContrib extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.chatSessions'; - private readonly registeredViewDescriptors: Map = new Map(); - - constructor( - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IProductService private readonly productService: IProductService, - ) { - super(); - - // Initial check - void this.updateViewRegistration(); - - this._register(this.chatSessionsService.onDidChangeItemsProviders(() => { - void this.updateViewRegistration(); - })); - - this._register(this.chatSessionsService.onDidChangeAvailability(() => { - void this.updateViewRegistration(); - })); - } - - private getAllChatSessionItemProviders(): IChatSessionItemProvider[] { - return Array.from(this.chatSessionsService.getAllChatSessionItemProviders()); - } - - private async updateViewRegistration(): Promise { - // prepare all chat session providers - const contributions = this.chatSessionsService.getAllChatSessionContributions(); - await Promise.all(contributions.map(contrib => this.chatSessionsService.activateChatSessionItemProvider(contrib.type))); - const currentProviders = this.getAllChatSessionItemProviders(); - const currentProviderIds = new Set(currentProviders.map(p => p.chatSessionType)); - - // Find views that need to be unregistered (providers that are no longer available) - const viewsToUnregister: IViewDescriptor[] = []; - for (const [providerId, viewDescriptor] of this.registeredViewDescriptors.entries()) { - if (!currentProviderIds.has(providerId)) { - viewsToUnregister.push(viewDescriptor); - this.registeredViewDescriptors.delete(providerId); - } - } - - // Unregister removed views - if (viewsToUnregister.length > 0) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(LEGACY_AGENT_SESSIONS_VIEW_ID); - if (container) { - Registry.as(Extensions.ViewsRegistry).deregisterViews(viewsToUnregister, container); - } - } - - // Register new views - this.registerViews(contributions); - } - - private async registerViews(extensionPointContributions: IChatSessionsExtensionPoint[]) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(LEGACY_AGENT_SESSIONS_VIEW_ID); - const providers = this.getAllChatSessionItemProviders(); - - if (container && providers.length > 0) { - const viewDescriptorsToRegister: IViewDescriptor[] = []; - - // Separate providers by type and prepare display names with order - const localProvider = providers.find(p => p.chatSessionType === localChatSessionType); - const historyProvider = providers.find(p => p.chatSessionType === 'history'); - const otherProviders = providers.filter(p => p.chatSessionType !== localChatSessionType && p.chatSessionType !== 'history'); - - // Sort other providers by order, then alphabetically by display name - const providersWithDisplayNames = otherProviders.map(provider => { - const extContribution = extensionPointContributions.find(c => c.type === provider.chatSessionType); - if (!extContribution) { - return null; - } - return { - provider, - displayName: extContribution.displayName, - order: extContribution.order - }; - }).filter(item => item !== null) as Array<{ provider: IChatSessionItemProvider; displayName: string; order: number | undefined }>; - - providersWithDisplayNames.sort((a, b) => { - // Both have no order - sort by display name - if (a.order === undefined && b.order === undefined) { - return a.displayName.localeCompare(b.displayName); - } - - // Only a has no order - push it to the end - if (a.order === undefined) { - return 1; - } - - // Only b has no order - push it to the end - if (b.order === undefined) { - return -1; - } - - // Both have orders - compare numerically - const orderCompare = a.order - b.order; - if (orderCompare !== 0) { - return orderCompare; - } - - // Same order - sort by display name - return a.displayName.localeCompare(b.displayName); - }); - - // Register views in priority order: local, history, then alphabetically sorted others - const orderedProviders = [ - ...(localProvider ? [{ provider: localProvider, displayName: 'Local Chat Agent', baseOrder: 0, when: ChatContextKeyExprs.agentViewWhen }] : []), - ...(historyProvider ? [{ provider: historyProvider, displayName: 'History', baseOrder: 1, when: ChatContextKeyExprs.agentViewWhen }] : []), - ...providersWithDisplayNames.map((item, index) => ({ - ...item, - baseOrder: 2 + index, // Start from 2 for other providers - when: ChatContextKeyExprs.agentViewWhen, - })) - ]; - - orderedProviders.forEach(({ provider, displayName, baseOrder, when }) => { - // Only register if not already registered - if (!this.registeredViewDescriptors.has(provider.chatSessionType)) { - const viewId = `${LEGACY_AGENT_SESSIONS_VIEW_ID}.${provider.chatSessionType}`; - const viewDescriptor: IViewDescriptor = { - id: viewId, - name: { - value: displayName, - original: displayName, - }, - ctorDescriptor: new SyncDescriptor(SessionsViewPane, [provider, viewId]), - canToggleVisibility: true, - canMoveView: true, - order: baseOrder, // Use computed order based on priority and alphabetical sorting - when, - }; - - viewDescriptorsToRegister.push(viewDescriptor); - this.registeredViewDescriptors.set(provider.chatSessionType, viewDescriptor); - - if (provider.chatSessionType === localChatSessionType) { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - this._register(viewsRegistry.registerViewWelcomeContent(viewDescriptor.id, { - content: nls.localize('chatSessions.noResults', "No local chat agent sessions\n[Start an Agent Session](command:{0})", ACTION_ID_OPEN_CHAT), - })); - } - } - }); - - const gettingStartedViewId = `${LEGACY_AGENT_SESSIONS_VIEW_ID}.gettingStarted`; - if (!this.registeredViewDescriptors.has('gettingStarted') - && this.productService.chatSessionRecommendations?.length) { - const gettingStartedDescriptor: IViewDescriptor = { - id: gettingStartedViewId, - name: { - value: nls.localize('chat.sessions.gettingStarted', "Getting Started"), - original: 'Getting Started', - }, - ctorDescriptor: new SyncDescriptor(SessionsViewPane, [null, gettingStartedViewId]), - canToggleVisibility: true, - canMoveView: true, - order: 1000, - collapsed: !!otherProviders.length, - when: ContextKeyExpr.false() - }; - viewDescriptorsToRegister.push(gettingStartedDescriptor); - this.registeredViewDescriptors.set('gettingStarted', gettingStartedDescriptor); - } - - if (viewDescriptorsToRegister.length > 0) { - Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptorsToRegister, container); - } - } - } - - override dispose(): void { - // Unregister all views before disposal - if (this.registeredViewDescriptors.size > 0) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(LEGACY_AGENT_SESSIONS_VIEW_ID); - if (container) { - const allRegisteredViews = Array.from(this.registeredViewDescriptors.values()); - Registry.as(Extensions.ViewsRegistry).deregisterViews(allRegisteredViews, container); - } - this.registeredViewDescriptors.clear(); - } - - super.dispose(); - } -} - -// Chat sessions container -class ChatSessionsViewPaneContainer extends ViewPaneContainer { - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IContextMenuService contextMenuService: IContextMenuService, - @ITelemetryService telemetryService: ITelemetryService, - @IExtensionService extensionService: IExtensionService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @ILogService logService: ILogService, - ) { - super( - LEGACY_AGENT_SESSIONS_VIEW_ID, - { - mergeViewWithContainerWhenSingleView: false, - }, - instantiationService, - configurationService, - layoutService, - contextMenuService, - telemetryService, - extensionService, - themeService, - storageService, - contextService, - viewDescriptorService, - logService - ); - } - - override getTitle(): string { - const title = nls.localize('chat.agent.sessions.title', "Agent Sessions"); - return title; - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts deleted file mode 100644 index 7bd794263dc..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ /dev/null @@ -1,628 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as DOM from '../../../../../../base/browser/dom.js'; -import { $, append } from '../../../../../../base/browser/dom.js'; -import { StandardKeyboardEvent } from '../../../../../../base/browser/keyboardEvent.js'; -import { ActionBar } from '../../../../../../base/browser/ui/actionbar/actionbar.js'; -import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; -import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; -import { IconLabel } from '../../../../../../base/browser/ui/iconLabel/iconLabel.js'; -import { InputBox, MessageType } from '../../../../../../base/browser/ui/inputbox/inputBox.js'; -import { IListRenderer, IListVirtualDelegate } from '../../../../../../base/browser/ui/list/list.js'; -import { IAsyncDataSource, ITreeNode, ITreeRenderer } from '../../../../../../base/browser/ui/tree/tree.js'; -import { timeout } from '../../../../../../base/common/async.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { FuzzyScore, createMatches } from '../../../../../../base/common/filters.js'; -import { createSingleCallFunction } from '../../../../../../base/common/functional.js'; -import { isMarkdownString } from '../../../../../../base/common/htmlContent.js'; -import { KeyCode } from '../../../../../../base/common/keyCodes.js'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; -import Severity from '../../../../../../base/common/severity.js'; -import { ThemeIcon } from '../../../../../../base/common/themables.js'; -import * as nls from '../../../../../../nls.js'; -import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { IContextViewService } from '../../../../../../platform/contextview/browser/contextView.js'; -import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; -import { IMarkdownRendererService } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; -import product from '../../../../../../platform/product/common/product.js'; -import { defaultInputBoxStyles } from '../../../../../../platform/theme/browser/defaultStyles.js'; -import { IResourceLabel, ResourceLabels } from '../../../../../browser/labels.js'; -import { IEditableData, ViewContainerLocation } from '../../../../../common/views.js'; -import { IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js'; -import { IWorkbenchLayoutService, Position } from '../../../../../services/layout/browser/layoutService.js'; -import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; -import { IChatService } from '../../../common/chatService.js'; -import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../../common/chatSessionsService.js'; -import { LocalChatSessionUri } from '../../../common/chatUri.js'; -import { ChatConfiguration } from '../../../common/constants.js'; -import { IMarshalledChatSessionContext } from '../../actions/chatSessionActions.js'; -import { allowedChatMarkdownHtmlTags } from '../../chatContentMarkdownRenderer.js'; -import '../../media/chatSessions.css'; -import { ChatSessionItemWithProvider, extractTimestamp, getSessionItemContextOverlay, processSessionsWithTimeGrouping } from '../common.js'; - -interface ISessionTemplateData { - readonly container: HTMLElement; - readonly iconLabel: IconLabel; - readonly actionBar: ActionBar; - readonly elementDisposable: DisposableStore; - readonly timestamp: HTMLElement; - readonly descriptionRow: HTMLElement; - readonly descriptionLabel: HTMLElement; - readonly statisticsLabel: HTMLElement; - readonly customIcon: HTMLElement; -} - -export class ArchivedSessionItems { - private readonly items: Map = new Map(); - constructor(public readonly label: string) { - } - - pushItem(item: ChatSessionItemWithProvider): void { - const key = item.resource.toString(); - this.items.set(key, item); - } - - getItems(): ChatSessionItemWithProvider[] { - return Array.from(this.items.values()); - } - - clear(): void { - this.items.clear(); - } -} - -export interface IGettingStartedItem { - id: string; - label: string; - commandId: string; - icon?: ThemeIcon; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args?: any[]; -} - -export class GettingStartedDelegate implements IListVirtualDelegate { - getHeight(): number { - return 22; - } - - getTemplateId(): string { - return 'gettingStartedItem'; - } -} - -interface IGettingStartedTemplateData { - resourceLabel: IResourceLabel; -} - -export class GettingStartedRenderer implements IListRenderer { - readonly templateId = 'gettingStartedItem'; - - constructor(private readonly labels: ResourceLabels) { } - - renderTemplate(container: HTMLElement): IGettingStartedTemplateData { - const resourceLabel = this.labels.create(container, { supportHighlights: true }); - return { resourceLabel }; - } - - renderElement(element: IGettingStartedItem, index: number, templateData: IGettingStartedTemplateData): void { - templateData.resourceLabel.setResource({ - name: element.label, - resource: undefined - }, { - icon: element.icon, - hideIcon: false - }); - templateData.resourceLabel.element.setAttribute('data-command', element.commandId); - } - - disposeTemplate(templateData: IGettingStartedTemplateData): void { - templateData.resourceLabel.dispose(); - } -} - -export class SessionsRenderer extends Disposable implements ITreeRenderer { - static readonly TEMPLATE_ID = 'session'; - - constructor( - private readonly viewLocation: ViewContainerLocation | null, - @IContextViewService private readonly contextViewService: IContextViewService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IMenuService private readonly menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IHoverService private readonly hoverService: IHoverService, - @IChatService private readonly chatService: IChatService, - @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, - ) { - super(); - } - - get templateId(): string { - return SessionsRenderer.TEMPLATE_ID; - } - - private getHoverPosition(): HoverPosition { - const sideBarPosition = this.layoutService.getSideBarPosition(); - switch (this.viewLocation) { - case ViewContainerLocation.Sidebar: - return sideBarPosition === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT; - case ViewContainerLocation.AuxiliaryBar: - return sideBarPosition === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT; - default: - return HoverPosition.RIGHT; - } - } - - renderTemplate(container: HTMLElement): ISessionTemplateData { - const element = append(container, $('.chat-session-item')); - - // Create a container that holds the label, timestamp, and actions - const contentContainer = append(element, $('.session-content')); - // Custom icon element rendered separately from label text - const customIcon = append(contentContainer, $('.chat-session-custom-icon')); - const iconLabel = new IconLabel(contentContainer, { supportHighlights: true, supportIcons: true }); - const descriptionRow = append(element, $('.description-row')); - const descriptionLabel = append(descriptionRow, $('span.description')); - const statisticsLabel = append(descriptionRow, $('span.statistics')); - - // Create timestamp container and element - const timestampContainer = append(contentContainer, $('.timestamp-container')); - const timestamp = append(timestampContainer, $('.timestamp')); - - const actionsContainer = append(contentContainer, $('.actions')); - const actionBar = new ActionBar(actionsContainer); - const elementDisposable = new DisposableStore(); - - return { - container: element, - iconLabel, - customIcon, - actionBar, - elementDisposable, - timestamp, - descriptionRow, - descriptionLabel, - statisticsLabel, - }; - } - - statusToIcon(status?: ChatSessionStatus) { - switch (status) { - case ChatSessionStatus.InProgress: - return ThemeIcon.modify(Codicon.loading, 'spin'); - case ChatSessionStatus.Completed: - return Codicon.pass; - case ChatSessionStatus.Failed: - return Codicon.error; - default: - return Codicon.circleOutline; - } - } - - private renderArchivedNode(node: ArchivedSessionItems, templateData: ISessionTemplateData): void { - templateData.customIcon.className = ''; - templateData.descriptionRow.style.display = 'none'; - templateData.timestamp.parentElement!.style.display = 'none'; - - const childCount = node.getItems().length; - templateData.iconLabel.setLabel(node.label, undefined, { - title: childCount === 1 ? nls.localize('chat.sessions.groupNode.single', '1 session') : nls.localize('chat.sessions.groupNode.multiple', '{0} sessions', childCount) - }); - } - - renderElement(element: ITreeNode, index: number, templateData: ISessionTemplateData): void { - if (element.element instanceof ArchivedSessionItems) { - this.renderArchivedNode(element.element, templateData); - return; - } - - const session = element.element as ChatSessionItemWithProvider; - // Add CSS class for local sessions - let editableData: IEditableData | undefined; - if (LocalChatSessionUri.parseLocalSessionId(session.resource)) { - templateData.container.classList.add('local-session'); - editableData = this.chatSessionsService.getEditableData(session.resource); - } else { - templateData.container.classList.remove('local-session'); - } - - // Check if this session is being edited using the actual session ID - if (editableData) { - // Render input box for editing - templateData.actionBar.clear(); - const editDisposable = this.renderInputBox(templateData.container, session, editableData); - templateData.elementDisposable.add(editDisposable); - return; - } - - // Normal rendering - clear the action bar in case it was used for editing - templateData.actionBar.clear(); - - // Handle different icon types - let iconTheme: ThemeIcon | undefined; - if (!session.iconPath) { - iconTheme = this.statusToIcon(session.status); - } else { - iconTheme = session.iconPath; - } - - const renderDescriptionOnSecondRow = this.configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription); - - if (renderDescriptionOnSecondRow && session.description) { - templateData.container.classList.toggle('multiline', true); - templateData.descriptionRow.style.display = 'flex'; - if (typeof session.description === 'string') { - templateData.descriptionLabel.textContent = session.description; - } else { - templateData.elementDisposable.add(this.markdownRendererService.render(session.description, { - sanitizerConfig: { - replaceWithPlaintext: true, - allowedTags: { - override: allowedChatMarkdownHtmlTags, - }, - allowedLinkSchemes: { augment: [product.urlProtocol] } - }, - }, templateData.descriptionLabel)); - templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'mousedown', e => e.stopPropagation())); - templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'click', e => e.stopPropagation())); - templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'auxclick', e => e.stopPropagation())); - } - - DOM.clearNode(templateData.statisticsLabel); - - let insertions = 0; - let deletions = 0; - if (session.changes instanceof Array) { - for (const change of session.changes) { - insertions += change.insertions; - deletions += change.deletions; - } - } else if (session.changes) { - insertions = session.changes.insertions; - deletions = session.changes.deletions; - } - - const insertionNode = append(templateData.statisticsLabel, $('span.insertions')); - insertionNode.textContent = session.changes ? `+${insertions}` : ''; - const deletionNode = append(templateData.statisticsLabel, $('span.deletions')); - deletionNode.textContent = session.changes ? `-${deletions}` : ''; - } else { - templateData.container.classList.toggle('multiline', false); - } - - // Prepare tooltip content - const tooltipContent = 'tooltip' in session && session.tooltip ? - (typeof session.tooltip === 'string' ? session.tooltip : - isMarkdownString(session.tooltip) ? { - markdown: session.tooltip, - markdownNotSupportedFallback: session.tooltip.value - } : undefined) : - undefined; - - templateData.customIcon.className = iconTheme ? `chat-session-custom-icon ${ThemeIcon.asClassName(iconTheme)}` : ''; - - // Set the icon label - templateData.iconLabel.setLabel( - session.label, - !renderDescriptionOnSecondRow && typeof session.description === 'string' ? session.description : undefined, - { - title: !renderDescriptionOnSecondRow || !session.description ? tooltipContent : undefined, - matches: createMatches(element.filterData) - } - ); - - // For two-row items, set tooltip on the container instead - if (renderDescriptionOnSecondRow && session.description && tooltipContent) { - if (typeof tooltipContent === 'string') { - templateData.elementDisposable.add( - this.hoverService.setupDelayedHover(templateData.container, () => ({ - content: tooltipContent, - style: HoverStyle.Pointer, - position: { hoverPosition: this.getHoverPosition() } - }), { groupId: 'chat.sessions' }) - ); - } else if (tooltipContent && typeof tooltipContent === 'object' && 'markdown' in tooltipContent) { - templateData.elementDisposable.add( - this.hoverService.setupDelayedHover(templateData.container, () => ({ - content: tooltipContent.markdown, - style: HoverStyle.Pointer, - position: { hoverPosition: this.getHoverPosition() } - }), { groupId: 'chat.sessions' }) - ); - } - } - - // Handle timestamp display and grouping - const hasTimestamp = session.timing?.startTime !== undefined; - if (hasTimestamp) { - templateData.timestamp.textContent = session.relativeTime ?? ''; - templateData.timestamp.ariaLabel = session.relativeTimeFullWord ?? ''; - templateData.timestamp.parentElement!.classList.toggle('timestamp-duplicate', session.hideRelativeTime === true); - templateData.timestamp.parentElement!.style.display = ''; - - // Add tooltip showing full date/time when hovering over the timestamp - if (session.timing?.startTime) { - const fullDateTime = getLocalHistoryDateFormatter().format(session.timing.startTime); - templateData.elementDisposable.add( - this.hoverService.setupDelayedHover(templateData.timestamp, () => ({ - content: nls.localize('chat.sessions.lastActivity', 'Last Activity: {0}', fullDateTime), - style: HoverStyle.Pointer, - position: { hoverPosition: this.getHoverPosition() } - }), { groupId: 'chat.sessions' }) - ); - } - } else { - // Hide timestamp container if no timestamp available - templateData.timestamp.parentElement!.style.display = 'none'; - } - - // Create context overlay for this specific session item - const contextOverlay = getSessionItemContextOverlay( - session, - session.provider, - this.chatService, - this.editorGroupsService - ); - - const contextKeyService = this.contextKeyService.createOverlay(contextOverlay); - - // Create menu for this session item - const menu = templateData.elementDisposable.add( - this.menuService.createMenu(MenuId.AgentSessionsContext, contextKeyService) - ); - - // Setup action bar with contributed actions - const setupActionBar = () => { - templateData.actionBar.clear(); - - // Create marshalled context for command execution - const marshalledSession: IMarshalledChatSessionContext = { - session: session, - $mid: MarshalledId.ChatSessionContext - }; - - const actions = menu.getActions({ arg: marshalledSession, shouldForwardArgs: true }); - - const { primary } = getActionBarActions( - actions, - 'inline', - ); - - templateData.actionBar.push(primary, { icon: true, label: false }); - - // Set context for the action bar - templateData.actionBar.context = session; - }; - - // Setup initial action bar and listen for menu changes - templateData.elementDisposable.add(menu.onDidChange(() => setupActionBar())); - setupActionBar(); - } - - disposeElement(_element: ITreeNode, _index: number, templateData: ISessionTemplateData): void { - templateData.elementDisposable.clear(); - templateData.actionBar.clear(); - } - - private renderInputBox(container: HTMLElement, session: IChatSessionItem, editableData: IEditableData): DisposableStore { - // Hide the existing resource label element and session content - // eslint-disable-next-line no-restricted-syntax - const existingResourceLabelElement = container.querySelector('.monaco-icon-label') as HTMLElement; - if (existingResourceLabelElement) { - existingResourceLabelElement.style.display = 'none'; - } - - // Hide the session content container to avoid layout conflicts - // eslint-disable-next-line no-restricted-syntax - const sessionContentElement = container.querySelector('.session-content') as HTMLElement; - if (sessionContentElement) { - sessionContentElement.style.display = 'none'; - } - - // Create a simple container that mimics the file explorer's structure - const editContainer = DOM.append(container, DOM.$('.explorer-item.explorer-item-edited')); - - // Add the icon - const iconElement = DOM.append(editContainer, DOM.$('.codicon')); - if (session.iconPath && ThemeIcon.isThemeIcon(session.iconPath)) { - iconElement.classList.add(`codicon-${session.iconPath.id}`); - } else { - iconElement.classList.add('codicon-file'); // Default file icon - } - - // Create the input box directly - const inputBox = new InputBox(editContainer, this.contextViewService, { - validationOptions: { - validation: (value) => { - const message = editableData.validationMessage(value); - if (!message || message.severity !== Severity.Error) { - return null; - } - return { - content: message.content, - formatContent: true, - type: MessageType.ERROR - }; - } - }, - ariaLabel: nls.localize('chatSessionInputAriaLabel', "Type session name. Press Enter to confirm or Escape to cancel."), - inputBoxStyles: defaultInputBoxStyles, - }); - - inputBox.value = session.label; - inputBox.focus(); - inputBox.select({ start: 0, end: session.label.length }); - - const done = createSingleCallFunction((success: boolean, finishEditing: boolean) => { - const value = inputBox.value; - - // Clean up the edit container - editContainer.style.display = 'none'; - editContainer.remove(); - - // Restore the original resource label - if (existingResourceLabelElement) { - existingResourceLabelElement.style.display = ''; - } - - // Restore the session content container - // eslint-disable-next-line no-restricted-syntax - const sessionContentElement = container.querySelector('.session-content') as HTMLElement; - if (sessionContentElement) { - sessionContentElement.style.display = ''; - } - - if (finishEditing) { - editableData.onFinish(value, success); - } - }); - - const showInputBoxNotification = () => { - if (inputBox.isInputValid()) { - const message = editableData.validationMessage(inputBox.value); - if (message) { - inputBox.showMessage({ - content: message.content, - formatContent: true, - type: message.severity === Severity.Info ? MessageType.INFO : message.severity === Severity.Warning ? MessageType.WARNING : MessageType.ERROR - }); - } else { - inputBox.hideMessage(); - } - } - }; - showInputBoxNotification(); - - const disposables: IDisposable[] = [ - inputBox, - DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => { - if (e.equals(KeyCode.Enter)) { - if (!inputBox.validate()) { - done(true, true); - } - } else if (e.equals(KeyCode.Escape)) { - done(false, true); - } - }), - DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_UP, () => { - showInputBoxNotification(); - }), - DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, async () => { - while (true) { - await timeout(0); - - const ownerDocument = inputBox.inputElement.ownerDocument; - if (!ownerDocument.hasFocus()) { - break; - } - if (DOM.isActiveElement(inputBox.inputElement)) { - return; - } else if (DOM.isHTMLElement(ownerDocument.activeElement) && DOM.hasParentWithClass(ownerDocument.activeElement, 'context-view')) { - // Do nothing - context menu is open - } else { - break; - } - } - - done(inputBox.isInputValid(), true); - }) - ]; - - const disposableStore = new DisposableStore(); - disposables.forEach(d => disposableStore.add(d)); - disposableStore.add(toDisposable(() => done(false, false))); - return disposableStore; - } - - disposeTemplate(templateData: ISessionTemplateData): void { - templateData.elementDisposable.dispose(); - templateData.iconLabel.dispose(); - templateData.actionBar.dispose(); - } -} - -// Chat sessions item data source for the tree -export class SessionsDataSource implements IAsyncDataSource { - // For now call it History until we support archive on all providers - private archivedItems = new ArchivedSessionItems(nls.localize('chat.sessions.archivedSessions', 'History')); - constructor( - private readonly provider: IChatSessionItemProvider, - ) { - } - - hasChildren(element: IChatSessionItemProvider | ChatSessionItemWithProvider | ArchivedSessionItems): boolean { - if (element === this.provider) { - // Root provider always has children - return true; - } - - if (element instanceof ArchivedSessionItems) { - return element.getItems().length > 0; - } - - return false; - } - - async getChildren(element: IChatSessionItemProvider | ChatSessionItemWithProvider | ArchivedSessionItems): Promise<(ChatSessionItemWithProvider | ArchivedSessionItems)[]> { - if (element === this.provider) { - try { - const items = await this.provider.provideChatSessionItems(CancellationToken.None); - // Clear archived items from previous calls - this.archivedItems.clear(); - const result: (ChatSessionItemWithProvider | ArchivedSessionItems)[] = items.map(item => { - const itemWithProvider = { ...item, provider: this.provider, timing: { startTime: extractTimestamp(item) ?? 0 } }; - if (itemWithProvider.history) { - this.archivedItems.pushItem(itemWithProvider); - return; - } - return itemWithProvider; - }).filter(item => item !== undefined); - - if (this.archivedItems.getItems().length > 0) { - result.push(this.archivedItems); - } - return result; - } catch (error) { - return []; - } - } - - if (element instanceof ArchivedSessionItems) { - return processSessionsWithTimeGrouping(element.getItems()); - } - - // Individual session items don't have children - return []; - } -} - - -export class SessionsDelegate implements IListVirtualDelegate { - static readonly ITEM_HEIGHT = 22; - static readonly ITEM_HEIGHT_WITH_DESCRIPTION = 44; // Slightly smaller for cleaner look - - constructor(private readonly configurationService: IConfigurationService) { } - - getHeight(element: ChatSessionItemWithProvider | ArchivedSessionItems): number { - // Return consistent height for all items (single-line layout) - if (this.configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription) && !(element instanceof ArchivedSessionItems) && element.description) { - return SessionsDelegate.ITEM_HEIGHT_WITH_DESCRIPTION; - } else { - return SessionsDelegate.ITEM_HEIGHT; - } - } - - getTemplateId(element: ChatSessionItemWithProvider): string { - return SessionsRenderer.TEMPLATE_ID; - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts deleted file mode 100644 index 825521dbbc9..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ /dev/null @@ -1,511 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as DOM from '../../../../../../base/browser/dom.js'; -import { $, append } from '../../../../../../base/browser/dom.js'; -import { renderAsPlaintext } from '../../../../../../base/browser/markdownRenderer.js'; -import { IActionViewItem } from '../../../../../../base/browser/ui/actionbar/actionbar.js'; -import { IBaseActionViewItemOptions } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js'; -import { ITreeContextMenuEvent } from '../../../../../../base/browser/ui/tree/tree.js'; -import { IAction, toAction } from '../../../../../../base/common/actions.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { FuzzyScore } from '../../../../../../base/common/filters.js'; -import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; -import { truncate } from '../../../../../../base/common/strings.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import * as nls from '../../../../../../nls.js'; -import { DropdownWithPrimaryActionViewItem } from '../../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IMenuService, MenuId, MenuItemAction } from '../../../../../../platform/actions/common/actions.js'; -import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; -import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; -import { WorkbenchAsyncDataTree, WorkbenchList } from '../../../../../../platform/list/browser/listService.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; -import { IProgressService } from '../../../../../../platform/progress/common/progress.js'; -import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; -import { fillEditorsDragData } from '../../../../../browser/dnd.js'; -import { ResourceLabels } from '../../../../../browser/labels.js'; -import { IViewPaneOptions, ViewPane } from '../../../../../browser/parts/views/viewPane.js'; -import { IViewDescriptorService } from '../../../../../common/views.js'; -import { IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js'; -import { IChatService } from '../../../common/chatService.js'; -import { IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; -import { ChatConfiguration, ChatEditorTitleMaxLength } from '../../../common/constants.js'; -import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; -import { IMarshalledChatSessionContext } from '../../actions/chatSessionActions.js'; -import { IChatWidgetService } from '../../chat.js'; -import { IChatEditorOptions } from '../../chatEditor.js'; -import { ChatSessionItemWithProvider, getSessionItemContextOverlay, NEW_CHAT_SESSION_ACTION_ID } from '../common.js'; -import { ArchivedSessionItems, GettingStartedDelegate, GettingStartedRenderer, IGettingStartedItem, SessionsDataSource, SessionsDelegate, SessionsRenderer } from './sessionsTreeRenderer.js'; - -// Identity provider for session items -class SessionsIdentityProvider { - getId(element: ChatSessionItemWithProvider | ArchivedSessionItems): string { - if (element instanceof ArchivedSessionItems) { - return 'archived-session-items'; - } - return element.resource.toString(); - } - -} - -// Accessibility provider for session items -class SessionsAccessibilityProvider { - getWidgetAriaLabel(): string { - return nls.localize('chatSessions', 'Chat Sessions'); - } - - getAriaLabel(element: ChatSessionItemWithProvider | ArchivedSessionItems): string | null { - return element.label; - } -} - - -export class SessionsViewPane extends ViewPane { - private tree: WorkbenchAsyncDataTree | undefined; - private list: WorkbenchList | undefined; - private treeContainer: HTMLElement | undefined; - private messageElement?: HTMLElement; - private _isEmpty: boolean = true; - - constructor( - private readonly provider: IChatSessionItemProvider, - private readonly viewId: string, - options: IViewPaneOptions, - @IKeybindingService keybindingService: IKeybindingService, - @IContextMenuService contextMenuService: IContextMenuService, - @IConfigurationService configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IInstantiationService instantiationService: IInstantiationService, - @IOpenerService openerService: IOpenerService, - @IThemeService themeService: IThemeService, - @IHoverService hoverService: IHoverService, - @IChatService private readonly chatService: IChatService, - @ILogService private readonly logService: ILogService, - @IProgressService private readonly progressService: IProgressService, - @IMenuService private readonly menuService: IMenuService, - @ICommandService private readonly commandService: ICommandService, - @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, - @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); - this.minimumBodySize = 44; - - // Listen for configuration changes to refresh view when description display changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatConfiguration.ShowAgentSessionsViewDescription)) { - if (this.tree && this.isBodyVisible()) { - this.refreshTreeWithProgress(); - } - } - })); - - this._register(this.chatSessionsService.onDidChangeSessionItems((chatSessionType) => { - if (provider.chatSessionType === chatSessionType && this.tree && this.isBodyVisible()) { - this.refreshTreeWithProgress(); - } - })); - - if (provider) { // TODO: Why can this be undefined? - this.scopedContextKeyService.createKey('chatSessionType', provider.chatSessionType); - } - } - - override shouldShowWelcome(): boolean { - return this._isEmpty; - } - - public override createActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { - if (action.id.startsWith(NEW_CHAT_SESSION_ACTION_ID)) { - return this.getChatSessionDropdown(action, options); - } - return super.createActionViewItem(action, options); - } - - private getChatSessionDropdown(defaultAction: IAction, options: IBaseActionViewItemOptions) { - const primaryAction = this.instantiationService.createInstance(MenuItemAction, { - id: defaultAction.id, - title: defaultAction.label, - icon: Codicon.plus, - }, undefined, undefined, undefined, undefined); - - const actions = this.menuService.getMenuActions(MenuId.AgentSessionsContext, this.scopedContextKeyService, { shouldForwardArgs: true }); - const primaryActions = getActionBarActions( - actions, - 'submenu', - ).primary.filter(action => { - if (action instanceof MenuItemAction && defaultAction instanceof MenuItemAction) { - if (!action.item.source?.id || !defaultAction.item.source?.id) { - return false; - } - if (action.item.source.id === defaultAction.item.source.id) { - return true; - } - } - return false; - }); - - if (!primaryActions || primaryActions.length === 0) { - return; - } - - const dropdownAction = toAction({ - id: 'selectNewChatSessionOption', - label: nls.localize('chatSession.selectOption', 'More...'), - class: 'codicon-chevron-down', - run: () => { } - }); - - const dropdownActions: IAction[] = []; - - primaryActions.forEach(element => { - dropdownActions.push(element); - }); - - return this.instantiationService.createInstance( - DropdownWithPrimaryActionViewItem, - primaryAction, - dropdownAction, - dropdownActions, - '', - options - ); - } - - private isEmpty() { - // Check if the tree has the provider node and get its children count - if (!this.tree?.hasNode(this.provider)) { - return true; - } - const providerNode = this.tree.getNode(this.provider); - const childCount = providerNode.children?.length || 0; - - return childCount === 0; - } - - /** - * Updates the empty state message based on current tree data. - * Uses the tree's existing data to avoid redundant provider calls. - */ - private updateEmptyState(): void { - try { - const newEmptyState = this.isEmpty(); - if (newEmptyState !== this._isEmpty) { - this._isEmpty = newEmptyState; - this._onDidChangeViewWelcomeState.fire(); - } - } catch (error) { - this.logService.error('Error checking tree data for empty state:', error); - } - } - - /** - * Refreshes the tree data with progress indication. - * Shows a progress indicator while the tree updates its children from the provider. - */ - private async refreshTreeWithProgress(): Promise { - if (!this.tree) { - return; - } - - try { - await this.progressService.withProgress( - { - location: this.id, // Use the view ID as the progress location - title: nls.localize('chatSessions.refreshing', 'Refreshing chat sessions...'), - }, - async () => { - await this.tree!.updateChildren(this.provider); - } - ); - - // Check for empty state after refresh using tree data - this.updateEmptyState(); - } catch (error) { - // Log error but don't throw to avoid breaking the UI - this.logService.error('Error refreshing chat sessions tree:', error); - } - } - - /** - * Loads initial tree data with progress indication. - * Shows a progress indicator while the tree loads data from the provider. - */ - private async loadDataWithProgress(): Promise { - if (!this.tree) { - return; - } - - try { - await this.progressService.withProgress( - { - location: this.id, // Use the view ID as the progress location - title: nls.localize('chatSessions.loading', 'Loading chat sessions...'), - }, - async () => { - await this.tree!.setInput(this.provider); - } - ); - - // Check for empty state after loading using tree data - this.updateEmptyState(); - } catch (error) { - // Log error but don't throw to avoid breaking the UI - this.logService.error('Error loading chat sessions data:', error); - } - } - - protected override renderBody(container: HTMLElement): void { - super.renderBody(container); - - container.classList.add('chat-sessions-view'); - - // For Getting Started view (null provider), show simple list - if (this.provider === null) { - this.renderGettingStartedList(container); - return; - } - - this.treeContainer = DOM.append(container, DOM.$('.chat-sessions-tree-container')); - // Create message element for empty state - this.messageElement = append(container, $('.chat-sessions-message')); - this.messageElement.style.display = 'none'; - // Create the tree components - const dataSource = new SessionsDataSource(this.provider); - const delegate = new SessionsDelegate(this.configurationService); - const identityProvider = new SessionsIdentityProvider(); - const accessibilityProvider = new SessionsAccessibilityProvider(); - - // Use the existing ResourceLabels service for consistent styling - const renderer = this.instantiationService.createInstance(SessionsRenderer, this.viewDescriptorService.getViewLocationById(this.viewId)); - this._register(renderer); - - const getResourceForElement = (element: ChatSessionItemWithProvider): URI => { - return element.resource; - }; - - this.tree = this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - 'ChatSessions', - this.treeContainer, - delegate, - [renderer], - dataSource, - { - dnd: { - onDragStart: (data, originalEvent) => { - try { - const elements = data.getData() as ChatSessionItemWithProvider[]; - const uris = elements.map(getResourceForElement); - this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent)); - } catch { - // noop - } - }, - getDragURI: (element: ChatSessionItemWithProvider | ArchivedSessionItems) => { - if (element instanceof ArchivedSessionItems) { - return null; - } - return getResourceForElement(element).toString(); - }, - getDragLabel: (elements: ChatSessionItemWithProvider[]) => { - if (elements.length === 1) { - return elements[0].label; - } - return nls.localize('chatSessions.dragLabel', "{0} agent sessions", elements.length); - }, - drop: () => { }, - onDragOver: () => false, - dispose: () => { }, - }, - accessibilityProvider, - identityProvider, - keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (session: ChatSessionItemWithProvider) => { - const parts = [ - session.label || '', - typeof session.description === 'string' ? session.description : (session.description ? renderAsPlaintext(session.description) : '') - ]; - return parts.filter(text => text.length > 0).join(' '); - } - }, - multipleSelectionSupport: false, - overrideStyles: { - listBackground: undefined - }, - paddingBottom: SessionsDelegate.ITEM_HEIGHT, - setRowLineHeight: false - - } - ) as WorkbenchAsyncDataTree; - - // Set the input - this.tree.setInput(this.provider); - - // Register tree events - this._register(this.tree.onDidOpen((e) => { - if (e.element) { - this.openChatSession(e.element); - } - })); - - // Register context menu event for right-click actions - this._register(this.tree.onContextMenu((e) => { - if (e.element && !(e.element instanceof ArchivedSessionItems)) { - this.showContextMenu(e); - } - if (e.element) { - this.showContextMenu(e); - } - })); - - this._register(this.tree.onMouseDblClick(e => { - const scrollingByPage = this.configurationService.getValue('workbench.list.scrollByPage'); - if (e.element === null && !scrollingByPage) { - if (this.provider?.chatSessionType && this.provider.chatSessionType !== localChatSessionType) { - this.commandService.executeCommand(`workbench.action.chat.openNewSessionEditor.${this.provider?.chatSessionType}`); - } else { - this.commandService.executeCommand(ACTION_ID_OPEN_CHAT); - } - } - })); - - // Handle visibility changes to load data - this._register(this.onDidChangeBodyVisibility(async visible => { - if (visible && this.tree) { - await this.loadDataWithProgress(); - } - })); - - // Initially load data if visible - if (this.isBodyVisible() && this.tree) { - this.loadDataWithProgress(); - } - - this._register(this.tree); - } - - private renderGettingStartedList(container: HTMLElement): void { - const listContainer = DOM.append(container, DOM.$('.getting-started-list-container')); - const items: IGettingStartedItem[] = [ - { - id: 'install-extensions', - label: nls.localize('chatSessions.installExtensions', "Install Agents..."), - icon: Codicon.extensions, - commandId: 'chat.sessions.gettingStarted' - }, - { - id: 'learn-more', - label: nls.localize('chatSessions.learnMoreGHCodingAgent', "Learn More About GitHub Copilot coding agent"), - commandId: 'vscode.open', - icon: Codicon.book, - args: [URI.parse('https://aka.ms/coding-agent-docs')] - } - ]; - const delegate = new GettingStartedDelegate(); - - // Create ResourceLabels instance for the renderer - const labels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - this._register(labels); - - const renderer = new GettingStartedRenderer(labels); - this.list = this.instantiationService.createInstance( - WorkbenchList, - 'GettingStarted', - listContainer, - delegate, - [renderer], - { - horizontalScrolling: false, - } - ); - this.list.splice(0, 0, items); - this._register(this.list.onDidOpen(e => { - if (e.element) { - this.commandService.executeCommand(e.element.commandId, ...e.element.args ?? []); - } - })); - - this._register(this.list); - } - - protected override layoutBody(height: number, width: number): void { - super.layoutBody(height, width); - if (this.tree) { - this.tree.layout(height, width); - } - if (this.list) { - this.list.layout(height, width); - } - } - - private async openChatSession(session: ChatSessionItemWithProvider) { - try { - if (session instanceof ArchivedSessionItems) { - return; - } - - const options: IChatEditorOptions = { - pinned: true, - ignoreInView: true, - title: { - preferred: truncate(session.label, ChatEditorTitleMaxLength), - }, - preserveFocus: true, - }; - await this.chatWidgetService.openSession(session.resource, undefined, options); - - } catch (error) { - this.logService.error('[SessionsViewPane] Failed to open chat session:', error); - } - } - - private showContextMenu(e: ITreeContextMenuEvent) { - if (!e.element) { - return; - } - - const session = e.element; - const sessionWithProvider = session; - - // Create context overlay for this specific session item - const contextOverlay = getSessionItemContextOverlay( - session, - sessionWithProvider.provider, - this.chatService, - this.editorGroupsService - ); - const contextKeyService = this.contextKeyService.createOverlay(contextOverlay); - - // Create marshalled context for command execution - const marshalledSession: IMarshalledChatSessionContext = { - session: session, - $mid: MarshalledId.ChatSessionContext - }; - - // Create menu for this session item to get actions - const menu = this.menuService.createMenu(MenuId.AgentSessionsContext, contextKeyService); - - // Get actions and filter for context menu (all actions that are NOT inline) - const actions = menu.getActions({ arg: marshalledSession, shouldForwardArgs: true }); - - const { secondary } = getActionBarActions(actions, 'inline'); - this.contextMenuService.showContextMenu({ - getActions: () => secondary, - getAnchor: () => e.anchor, - getActionsContext: () => marshalledSession, - }); - - menu.dispose(); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts index 87380f6dd8f..29cbd338213 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts @@ -43,7 +43,6 @@ import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatModeService } from '../../common/chatModes.js'; import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js'; import { CHAT_CATEGORY, CHAT_SETUP_ACTION_ID, CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../actions/chatActions.js'; -import { AGENT_SESSIONS_VIEW_CONTAINER_ID } from '../agentSessions/agentSessions.js'; import { ChatViewContainerId, IChatWidgetService } from '../chat.js'; import { chatViewsWelcomeRegistry } from '../viewsWelcome/chatViewsWelcome.js'; import { ChatSetupAnonymous } from './chatSetup.js'; @@ -707,12 +706,9 @@ export class ChatTeardownContribution extends Disposable implements IWorkbenchCo const activeContainers = this.viewDescriptorService.getViewContainersByLocation(ViewContainerLocation.AuxiliaryBar).filter( container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0 ); - const hasChatView = activeContainers.some(container => container.id === ChatViewContainerId); - const hasAgentSessionsView = activeContainers.some(container => container.id === AGENT_SESSIONS_VIEW_CONTAINER_ID); if ( - (activeContainers.length === 0) || // chat view is already gone but we know it was there before - (activeContainers.length === 1 && (hasChatView || hasAgentSessionsView)) || // chat view or agent sessions is the only view which is going to go away - (activeContainers.length === 2 && hasChatView && hasAgentSessionsView) // both chat and agent sessions view are going to go away + (activeContainers.length === 0) || // chat view is already gone but we know it was there before + (activeContainers.length === 1 && activeContainers.at(0)?.id === ChatViewContainerId) // chat view is the only view which is going to go away ) { this.layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); // hide if there are no views in the secondary sidebar } diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus/chatStatusDashboard.ts b/src/vs/workbench/contrib/chat/browser/chatStatus/chatStatusDashboard.ts index ec7ec0af9e8..8eee4aa2652 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus/chatStatusDashboard.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus/chatStatusDashboard.ts @@ -40,13 +40,13 @@ import { EditorResourceAccessor, SideBySideEditor } from '../../../../common/edi import { IChatEntitlementService, ChatEntitlementService, ChatEntitlement, IQuotaSnapshot } from '../../../../services/chat/common/chatEntitlementService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; -import { openAgentSessionsView } from '../agentSessions/agentSessions.js'; import { isNewUser, isCompletionsEnabled } from './chatStatus.js'; import { IChatStatusItemService, ChatStatusEntry } from './chatStatusItemService.js'; import product from '../../../../../platform/product/common/product.js'; import { contrastBorder, inputValidationErrorBorder, inputValidationInfoBorder, inputValidationWarningBorder, registerColor, transparent } from '../../../../../platform/theme/common/colorRegistry.js'; import { Color } from '../../../../../base/common/color.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { ChatViewId } from '../chat.js'; const defaultChat = product.defaultChatAgent; @@ -141,7 +141,7 @@ export class ChatStatusDashboard extends DomWidget { @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IViewsService private readonly viewService: IViewsService, ) { super(); @@ -227,7 +227,7 @@ export class ChatStatusDashboard extends DomWidget { tooltip: localize('viewChatSessionsTooltip', "View Agent Sessions"), class: ThemeIcon.asClassName(Codicon.eye), run: () => { - this.instantiationService.invokeFunction(openAgentSessionsView); + this.viewService.openView(ChatViewId, true); this.hoverService.hideHover(true); } })); diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index fce404e090b..5530669e101 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -55,6 +55,7 @@ import { ChatViewId } from './chat.js'; import { disposableTimeout } from '../../../../base/common/async.js'; import { AgentSessionsFilter } from './agentSessions/agentSessionsFilter.js'; import { IAgentSessionsService } from './agentSessions/agentSessionsService.js'; +import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; interface IChatViewPaneState extends Partial { sessionId?: string; @@ -321,8 +322,8 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { // Sessions Control this.sessionsControlContainer = append(sessionsContainer, $('.agent-sessions-control-container')); const sessionsControl = this.sessionsControl = this._register(this.instantiationService.createInstance(AgentSessionsControl, this.sessionsControlContainer, { - allowOpenSessionsInPanel: true, - filter: sessionsFilter + filter: sessionsFilter, + getHoverPosition: () => this.sessionsViewerPosition === AgentSessionsViewerPosition.Right ? HoverPosition.LEFT : HoverPosition.RIGHT })); this._register(this.onDidChangeBodyVisibility(visible => sessionsControl.setVisible(visible))); diff --git a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css deleted file mode 100644 index cf28644ddea..00000000000 --- a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css +++ /dev/null @@ -1,299 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* Ensure consistent title background regardless of number of views */ -.composite.viewlet[id="workbench.view.chat.sessions"] .pane-header.expanded.not-collapsible { - background-color: var(--vscode-sideBarSectionHeader-background) !important; -} - -.chat-sessions-view { - display: flex; - flex-direction: column; - height: 100%; -} - -.chat-sessions-tree-container, -.getting-started-list-container { - flex: 1; - display: flex; - flex-direction: column; - min-height: 0; -} - -.chat-sessions-tree-container > .monaco-list, -.getting-started-list-container > .monaco-list { - flex: 1; -} - -/* Style for empty state message */ -.chat-sessions-message { - padding: 20px; - text-align: center; - color: var(--vscode-descriptionForeground); -} - -.chat-sessions-message .no-sessions-message { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - font-style: italic; -} - -/* Simple approach - directly style the edit container for chat sessions */ -.chat-sessions-tree-container .explorer-item.explorer-item-edited { - display: flex; - align-items: center; - height: 22px; - padding: 0; -} - -.chat-sessions-tree-container .explorer-item.explorer-item-edited .codicon { - margin-right: 6px; - flex-shrink: 0; -} - -.chat-sessions-tree-container .explorer-item.explorer-item-edited .monaco-inputbox { - flex: 1; - width: 100%; - line-height: normal; - border: none !important; - background: transparent !important; -} - -/* Add the complete outline border that file explorer uses (this replaces the border) */ -.chat-sessions-tree-container .explorer-item.explorer-item-edited .monaco-inputbox input[type="text"] { - outline-width: 1px; - outline-style: solid; - outline-offset: -1px; - outline-color: var(--vscode-focusBorder); - opacity: 1; - border: none !important; /* Remove any default border */ -} - -.chat-sessions-tree-container .chat-session-item.multiline { - padding: 2px 0; -} - -/* Position session content and actions inline */ -.chat-sessions-tree-container .chat-session-item .session-content { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - min-height: 22px; - line-height: 22px; -} - -.chat-sessions-tree-container .chat-session-item .description-row { - display: none; - align-items: center; - font-size: 0.9em; - line-height: 1em; - margin: 2px 22px 0 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.chat-sessions-tree-container .chat-session-item .description-row p { - padding: 2px; - margin: 0px; - border-radius: 4px; -} - -.chat-sessions-tree-container .chat-session-item .description-row a { - color: var(--vscode-foreground); -} - -.chat-sessions-tree-container .chat-session-item .description-row .description:hover p { - background: var(--vscode-toolbar-hoverBackground); -} - -.chat-sessions-tree-container .chat-session-item .description-row .description { - opacity: 0.5; -} - -.chat-sessions-tree-container .chat-session-item .description-row .description p { - display: flex; - align-items: center; -} - -.chat-sessions-tree-container .chat-session-item .description-row .description p .codicon { - font-size: 14px; -} - -.chat-sessions-tree-container .chat-session-item .description-row .statistics { - margin-left: 8px; -} - -.getting-started-list-container .monaco-list-row { - padding-left: 8px; -} - -.chat-sessions-tree-container .chat-session-item .description-row .statistics .insertions { - color: var(--vscode-chat-linesAddedForeground); - padding-left: 4px; -} - -.chat-sessions-tree-container .chat-session-item .description-row .statistics .deletions { - color: var(--vscode-chat-linesRemovedForeground); - padding-left: 4px; -} - -.chat-sessions-tree-container .chat-session-item .actions { - display: flex; - align-items: center; - flex-shrink: 0; -} - -/* Hide actions by default, show on hover and focus */ -.chat-sessions-tree-container .chat-session-item .actions .monaco-action-bar .action-label { - opacity: 0; -} - -.chat-sessions-tree-container .chat-session-item:hover .actions .monaco-action-bar .action-label, -.chat-sessions-tree-container .monaco-list-row.focused .chat-session-item .actions .monaco-action-bar .action-label, -.chat-sessions-tree-container .monaco-list-row.selected .chat-session-item .actions .monaco-action-bar .action-label { - opacity: 1; -} - -/* For items with descriptions, keep the structure but adjust alignment */ -.chat-sessions-tree-container .chat-session-item .session-content { - align-items: center; - padding-top: 0; - padding-bottom: 0; -} - -/* Ensure resource label takes up available space */ -.chat-sessions-tree-container .chat-session-item .monaco-icon-label { - flex: 1; - min-width: 0; /* Allow text to truncate */ - text-overflow: ellipsis; - overflow: hidden; -} - -.chat-sessions-tree-container .chat-session-item .monaco-icon-label::before { - text-align: center; -} - -/* Chat session icon */ -.chat-sessions-tree-container .chat-session-item .chat-session-custom-icon { - flex-shrink: 0; - width: 16px; - height: 16px; - margin-right: 6px; - font-size: 16px; - display: flex; -} - -/* Timestamp styling - similar to timeline pane */ -.chat-sessions-tree-container .chat-session-item .timestamp-container { - margin-left: auto; - margin-right: 4px; - opacity: 0.5; - overflow: hidden; - text-overflow: ellipsis; - flex-shrink: 0; - font-size: 0.9em; - min-width: 10px; -} - -.chat-sessions-tree-container .chat-session-item .timestamp-container.timestamp-duplicate::before { - content: ' '; - position: absolute; - top: 0px; - right: 10px; - border-right: 1px solid currentColor; - display: block; - height: 100%; - width: 1px; - opacity: 0.25; -} - -.chat-sessions-tree-container .monaco-list-row:hover .chat-session-item .timestamp-container.timestamp-duplicate::before, -.chat-sessions-tree-container .monaco-list-row.selected .chat-session-item .timestamp-container.timestamp-duplicate::before, -.chat-sessions-tree-container .monaco-list-row.focused .chat-session-item .timestamp-container.timestamp-duplicate::before { - display: none; -} - -.chat-sessions-tree-container .chat-session-item .timestamp-container .timestamp { - display: inline-block; -} - -.chat-sessions-tree-container .chat-session-item .timestamp-container.timestamp-duplicate .timestamp { - visibility: hidden; - width: 10px; -} - -.chat-sessions-tree-container .monaco-list-row:hover .chat-session-item .timestamp-container.timestamp-duplicate .timestamp, -.chat-sessions-tree-container .monaco-list-row.selected .chat-session-item .timestamp-container.timestamp-duplicate .timestamp, -.chat-sessions-tree-container .monaco-list-row.focused .chat-session-item .timestamp-container.timestamp-duplicate .timestamp { - visibility: visible !important; - width: initial; -} - -.chat-sessions-tree-container .monaco-list-row .actions { - display: none; -} - -.chat-sessions-tree-container .monaco-list-row:hover .actions { - display: block; -} - -/* Hide twisties for elements that don't have children */ -.chat-sessions-tree-container .monaco-list-row .monaco-tl-twistie { - /* Ultra-small indent to separate parent/child without large gutter */ - visibility: hidden; /* keep layout space */ - width: 3px; - min-width: 3px; - padding: 0; - margin: 0; -} - -/* Show twistie only for collapsible items (like "Show history...") */ -.chat-sessions-tree-container .monaco-list-row[aria-expanded] .monaco-tl-twistie { - visibility: visible; - width: auto; - padding-left: 0px; - padding-right: 6px; - margin: initial; -} - -/* History items styling */ -.chat-sessions-tree-container .chat-session-item[data-history-item="true"] { - opacity: 0.9; -} - -.chat-sessions-tree-container .chat-session-item[data-history-item="true"]:hover { - background-color: var(--vscode-list-hoverBackground); -} - -/* Chat editor relative positioning for loading overlay */ -.chat-editor-relative { - position: relative; -} - -/* Chat editor loading overlay styles */ -.chat-loading-overlay { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: var(--vscode-editor-background); - z-index: 1000; -} - -.chat-loading-overlay .chat-loading-content { - display: flex; - align-items: center; - gap: 8px; - color: var(--vscode-editor-foreground); -} - -.chat-loading-overlay .codicon { - font-size: 16px; -} diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index e82069a6a95..d888dc9a57d 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -9,7 +9,7 @@ import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys import { RemoteNameContext } from '../../../common/contextkeys.js'; import { ViewContainerLocation } from '../../../common/views.js'; import { ChatEntitlementContextKeys } from '../../../services/chat/common/chatEntitlementService.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from './constants.js'; +import { ChatAgentLocation, ChatModeKind } from './constants.js'; export namespace ChatContextKeys { export const responseVote = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); @@ -92,15 +92,13 @@ export namespace ChatContextKeys { export const panelLocation = new RawContextKey('chatPanelLocation', undefined, { type: 'number', description: localize('chatPanelLocation', "The location of the chat panel.") }); - export const isCombinedAgentSessionsViewer = new RawContextKey('chatIsCombinedSessionViewer', false, { type: 'boolean', description: localize('chatIsCombinedSessionViewer', "True when the chat session viewer uses the new combined style.") }); // TODO@bpasero eventually retire this context key export const agentSessionsViewerLimited = new RawContextKey('agentSessionsViewerLimited', undefined, { type: 'boolean', description: localize('agentSessionsViewerLimited', "If the agent sessions view in the chat view is limited to show recent sessions only.") }); export const agentSessionsViewerOrientation = new RawContextKey('agentSessionsViewerOrientation', undefined, { type: 'number', description: localize('agentSessionsViewerOrientation', "Orientation of the agent sessions view in the chat view.") }); export const agentSessionsViewerPosition = new RawContextKey('agentSessionsViewerPosition', undefined, { type: 'number', description: localize('agentSessionsViewerPosition', "Position of the agent sessions view in the chat view.") }); export const agentSessionType = new RawContextKey('chatSessionType', '', { type: 'string', description: localize('agentSessionType', "The type of the current agent session item.") }); - export const hasAgentSessionChanges = new RawContextKey('agentSessionHasChanges', false, { type: 'boolean', description: localize('agentSessionHasChanges', "True when the current agent session item has changes.") }); export const isArchivedAgentSession = new RawContextKey('agentSessionIsArchived', false, { type: 'boolean', description: localize('agentSessionIsArchived', "True when the agent session item is archived.") }); export const isReadAgentSession = new RawContextKey('agentSessionIsRead', false, { type: 'boolean', description: localize('agentSessionIsRead', "True when the agent session item is read.") }); - export const isActiveAgentSession = new RawContextKey('agentSessionIsActive', false, { type: 'boolean', description: localize('agentSessionIsActive', "True when the agent session is currently active (not deletable).") }); + export const hasAgentSessionChanges = new RawContextKey('agentSessionHasChanges', false, { type: 'boolean', description: localize('agentSessionHasChanges', "True when the current agent session item has changes.") }); export const isKatexMathElement = new RawContextKey('chatIsKatexMathElement', false, { type: 'boolean', description: localize('chatIsKatexMathElement', "True when focusing a KaTeX math element.") }); } @@ -118,9 +116,4 @@ export namespace ChatContextKeyExprs { ChatContextKeys.Setup.installed.negate(), ChatContextKeys.Entitlement.canSignUp ); - - export const agentViewWhen = ContextKeyExpr.and( - ChatEntitlementContextKeys.Setup.hidden.negate(), - ChatEntitlementContextKeys.Setup.disabled.negate(), - ContextKeyExpr.equals(`config.${ChatConfiguration.AgentSessionsViewLocation}`, 'view')); } diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index a12b6caad8a..87ebc4a7a8a 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -11,7 +11,6 @@ import { IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IEditableData } from '../../../common/views.js'; import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './chatAgents.js'; import { IChatEditingSession } from './chatEditingService.js'; import { IChatModel, IChatRequestVariableData, ISerializableChatModelInputState } from './chatModel.js'; @@ -225,11 +224,6 @@ export interface IChatSessionsService { setOptionsChangeCallback(callback: SessionOptionsChangedCallback): void; notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string | IChatSessionProviderOptionItem }>): Promise; - // Editable session support - setEditableSession(sessionResource: URI, data: IEditableData | null): Promise; - getEditableData(sessionResource: URI): IEditableData | undefined; - isEditable(sessionResource: URI): boolean; - // #endregion registerChatModelChangeListeners(chatService: IChatService, chatSessionType: string, onChange: () => void): IDisposable; getInProgressSessionDescription(chatModel: IChatModel): string | undefined; } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 8633518703a..2ece134ffd7 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -19,11 +19,9 @@ export enum ChatConfiguration { EligibleForAutoApproval = 'chat.tools.eligibleForAutoApproval', EnableMath = 'chat.math.enabled', CheckpointsEnabled = 'chat.checkpoints.enabled', - AgentSessionsViewLocation = 'chat.agentSessionsViewLocation', ThinkingStyle = 'chat.agent.thinkingStyle', ThinkingGenerateTitles = 'chat.agent.thinking.generateTitles', TodosShowWidget = 'chat.tools.todos.showWidget', - ShowAgentSessionsViewDescription = 'chat.showAgentSessionsViewDescription', NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived', ChatViewSessionsEnabled = 'chat.viewSessions.enabled', ChatViewSessionsOrientation = 'chat.viewSessions.orientation', @@ -133,8 +131,6 @@ export function isSupportedChatFileScheme(accessor: ServicesAccessor, scheme: st return true; } -/** @deprecated */ -export const LEGACY_AGENT_SESSIONS_VIEW_ID = 'workbench.view.chat.sessions'; // TODO@bpasero clear once settled export const MANAGE_CHAT_COMMAND_ID = 'workbench.action.chat.manage'; export const ChatEditorTitleMaxLength = 30; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index 8b4f0b08e28..d090059c086 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -9,7 +9,6 @@ import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; -import { IEditableData } from '../../../../common/views.js'; import { IChatAgentAttachmentCapabilities } from '../../common/chatAgents.js'; import { IChatModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; @@ -40,7 +39,6 @@ export class MockChatSessionsService implements IChatSessionsService { private contributions: IChatSessionsExtensionPoint[] = []; private optionGroups = new Map(); private sessionOptions = new ResourceMap>(); - private editableData = new ResourceMap(); private inProgress = new Map(); private onChange = () => { }; @@ -173,22 +171,6 @@ export class MockChatSessionsService implements IChatSessionsService { await this.optionsChangeCallback?.(sessionResource, updates); } - async setEditableSession(sessionResource: URI, data: IEditableData | null): Promise { - if (data) { - this.editableData.set(sessionResource, data); - } else { - this.editableData.delete(sessionResource); - } - } - - getEditableData(sessionResource: URI): IEditableData | undefined { - return this.editableData.get(sessionResource); - } - - isEditable(sessionResource: URI): boolean { - return this.editableData.has(sessionResource); - } - notifySessionItemsChanged(chatSessionType: string): void { this._onDidChangeSessionItems.fire(chatSessionType); }