mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-19 17:58:39 +00:00
debt - removal of old agent sessions views and co (#282712)
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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() { }
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
|
||||
const currentValue = configurationService.getValue<string>(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`),
|
||||
});
|
||||
@@ -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<IViewContainersRegistry>(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<IViewsRegistry>(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, {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<AgentSessionsView> {
|
||||
|
||||
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<AgentSessionsView> {
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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<IListStyles>;
|
||||
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<AgentSessionOpenedEvent, AgentSessionOpenedClassification>('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 };
|
||||
|
||||
@@ -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<void>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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<IAgentSession, FuzzyScore, IAgentSessionItemTemplate> {
|
||||
|
||||
static readonly TEMPLATE_ID = 'agent-session';
|
||||
@@ -75,10 +76,9 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
readonly templateId = AgentSessionRenderer.TEMPLATE_ID;
|
||||
|
||||
constructor(
|
||||
private readonly options: IAgentSessionRendererOptions,
|
||||
@IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
||||
@IHoverService private readonly hoverService: IHoverService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@@ -299,18 +299,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
content: tooltip,
|
||||
style: HoverStyle.Pointer,
|
||||
position: {
|
||||
hoverPosition: (() => {
|
||||
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' })
|
||||
);
|
||||
|
||||
@@ -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<IChatSessionItem[]> {
|
||||
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<ChatSessionItemWithProvider[]> {
|
||||
private async getHistoryItems(): Promise<IChatSessionItemWithProvider[]> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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<string, string> = new Map();
|
||||
|
||||
private readonly _sessions = new ResourceMap<ContributedChatSessionData>();
|
||||
private readonly _editableSessions = new ResourceMap<IEditableData>();
|
||||
|
||||
private readonly _hasCanDelegateProvidersKey: IContextKey<boolean>;
|
||||
|
||||
@@ -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<void> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<IViewContainersRegistry>(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<string, IViewDescriptor> = 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<void> {
|
||||
// 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<IViewContainersRegistry>(Extensions.ViewContainersRegistry).get(LEGACY_AGENT_SESSIONS_VIEW_ID);
|
||||
if (container) {
|
||||
Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).deregisterViews(viewsToUnregister, container);
|
||||
}
|
||||
}
|
||||
|
||||
// Register new views
|
||||
this.registerViews(contributions);
|
||||
}
|
||||
|
||||
private async registerViews(extensionPointContributions: IChatSessionsExtensionPoint[]) {
|
||||
const container = Registry.as<IViewContainersRegistry>(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<IViewsRegistry>(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<IViewsRegistry>(Extensions.ViewsRegistry).registerViews(viewDescriptorsToRegister, container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
// Unregister all views before disposal
|
||||
if (this.registeredViewDescriptors.size > 0) {
|
||||
const container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).get(LEGACY_AGENT_SESSIONS_VIEW_ID);
|
||||
if (container) {
|
||||
const allRegisteredViews = Array.from(this.registeredViewDescriptors.values());
|
||||
Registry.as<IViewsRegistry>(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;
|
||||
}
|
||||
}
|
||||
@@ -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<string, ChatSessionItemWithProvider> = 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<IGettingStartedItem> {
|
||||
getHeight(): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(): string {
|
||||
return 'gettingStartedItem';
|
||||
}
|
||||
}
|
||||
|
||||
interface IGettingStartedTemplateData {
|
||||
resourceLabel: IResourceLabel;
|
||||
}
|
||||
|
||||
export class GettingStartedRenderer implements IListRenderer<IGettingStartedItem, IGettingStartedTemplateData> {
|
||||
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<IChatSessionItem, FuzzyScore, ISessionTemplateData> {
|
||||
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<IChatSessionItem, FuzzyScore>, 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<boolean>(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<IChatSessionItem, FuzzyScore>, _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<IChatSessionItemProvider, ChatSessionItemWithProvider | ArchivedSessionItems> {
|
||||
// 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<ChatSessionItemWithProvider> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<IChatSessionItemProvider, ChatSessionItemWithProvider, FuzzyScore> | undefined;
|
||||
private list: WorkbenchList<IGettingStartedItem> | 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<void> {
|
||||
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<void> {
|
||||
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<IChatSessionItemProvider, ChatSessionItemWithProvider, FuzzyScore>;
|
||||
|
||||
// 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<boolean>('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<IGettingStartedItem>,
|
||||
'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<ChatSessionItemWithProvider>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -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<IChatModelInputState> {
|
||||
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)));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<string>('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<ViewContainerLocation>('chatPanelLocation', undefined, { type: 'number', description: localize('chatPanelLocation', "The location of the chat panel.") });
|
||||
|
||||
export const isCombinedAgentSessionsViewer = new RawContextKey<boolean>('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<boolean>('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<number>('agentSessionsViewerOrientation', undefined, { type: 'number', description: localize('agentSessionsViewerOrientation', "Orientation of the agent sessions view in the chat view.") });
|
||||
export const agentSessionsViewerPosition = new RawContextKey<number>('agentSessionsViewerPosition', undefined, { type: 'number', description: localize('agentSessionsViewerPosition', "Position of the agent sessions view in the chat view.") });
|
||||
export const agentSessionType = new RawContextKey<string>('chatSessionType', '', { type: 'string', description: localize('agentSessionType', "The type of the current agent session item.") });
|
||||
export const hasAgentSessionChanges = new RawContextKey<boolean>('agentSessionHasChanges', false, { type: 'boolean', description: localize('agentSessionHasChanges', "True when the current agent session item has changes.") });
|
||||
export const isArchivedAgentSession = new RawContextKey<boolean>('agentSessionIsArchived', false, { type: 'boolean', description: localize('agentSessionIsArchived', "True when the agent session item is archived.") });
|
||||
export const isReadAgentSession = new RawContextKey<boolean>('agentSessionIsRead', false, { type: 'boolean', description: localize('agentSessionIsRead', "True when the agent session item is read.") });
|
||||
export const isActiveAgentSession = new RawContextKey<boolean>('agentSessionIsActive', false, { type: 'boolean', description: localize('agentSessionIsActive', "True when the agent session is currently active (not deletable).") });
|
||||
export const hasAgentSessionChanges = new RawContextKey<boolean>('agentSessionHasChanges', false, { type: 'boolean', description: localize('agentSessionHasChanges', "True when the current agent session item has changes.") });
|
||||
|
||||
export const isKatexMathElement = new RawContextKey<boolean>('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'));
|
||||
}
|
||||
|
||||
@@ -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<void>;
|
||||
|
||||
// Editable session support
|
||||
setEditableSession(sessionResource: URI, data: IEditableData | null): Promise<void>;
|
||||
getEditableData(sessionResource: URI): IEditableData | undefined;
|
||||
isEditable(sessionResource: URI): boolean;
|
||||
// #endregion
|
||||
registerChatModelChangeListeners(chatService: IChatService, chatSessionType: string, onChange: () => void): IDisposable;
|
||||
getInProgressSessionDescription(chatModel: IChatModel): string | undefined;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<string, IChatSessionProviderOptionGroup[]>();
|
||||
private sessionOptions = new ResourceMap<Map<string, string>>();
|
||||
private editableData = new ResourceMap<IEditableData>();
|
||||
private inProgress = new Map<string, number>();
|
||||
private onChange = () => { };
|
||||
|
||||
@@ -173,22 +171,6 @@ export class MockChatSessionsService implements IChatSessionsService {
|
||||
await this.optionsChangeCallback?.(sessionResource, updates);
|
||||
}
|
||||
|
||||
async setEditableSession(sessionResource: URI, data: IEditableData | null): Promise<void> {
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user