add workbench.action.chat.newLocalChat command

This commit is contained in:
BeniBenj
2026-01-19 17:27:36 +01:00
parent d331d50a2c
commit 388d1b9887
2 changed files with 103 additions and 53 deletions
@@ -24,12 +24,12 @@ import { localChatSessionType } from '../../common/chatSessionsService.js';
import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js';
import { getChatSessionType, LocalChatSessionUri } from '../../common/model/chatUri.js';
import { ChatViewId, IChatWidgetService, isIChatViewViewContext } from '../chat.js';
import { EditingSessionAction, getEditingSessionContext } from '../chatEditing/chatEditingActions.js';
import { EditingSessionAction, EditingSessionActionContext, getEditingSessionContext } from '../chatEditing/chatEditingActions.js';
import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';
import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';
import { ACTION_ID_NEW_CHAT, ACTION_ID_NEW_EDIT_SESSION, CHAT_CATEGORY, handleCurrentEditingSession } from './chatActions.js';
import { clearChatEditor } from './chatClear.js';
import { AgentSessionsViewerOrientation } from '../agentSessions/agentSessions.js';
import { AgentSessionProviders, AgentSessionsViewerOrientation } from '../agentSessions/agentSessions.js';
export interface INewEditSessionActionContext {
@@ -51,6 +51,23 @@ export interface INewEditSessionActionContext {
isPartialQuery?: boolean;
}
function isNewEditSessionActionContext(arg: unknown): arg is INewEditSessionActionContext {
if (arg && typeof arg === 'object') {
const obj = arg as Record<string, unknown>;
if (obj.inputValue !== undefined && typeof obj.inputValue !== 'string') {
return false;
}
if (obj.agentMode !== undefined && typeof obj.agentMode !== 'boolean') {
return false;
}
if (obj.isPartialQuery !== undefined && typeof obj.isPartialQuery !== 'boolean') {
return false;
}
return true;
}
return false;
}
export function registerNewChatActions() {
// Add "New Chat" submenu to Chat view menu
@@ -119,64 +136,36 @@ export function registerNewChatActions() {
}
async run(accessor: ServicesAccessor, ...args: unknown[]) {
const accessibilityService = accessor.get(IAccessibilityService);
const viewsService = accessor.get(IViewsService);
const executeCommandContext = args[0] as INewEditSessionActionContext | undefined;
const executeCommandContext = isNewEditSessionActionContext(args[0]) ? args[0] : undefined;
// Context from toolbar or lastFocusedWidget
const context = getEditingSessionContext(accessor, args);
const { editingSession, chatWidget: widget } = context ?? {};
if (!widget) {
return;
}
await runNewChatAction(accessor, context, executeCommandContext);
}
}
);
CommandsRegistry.registerCommandAlias(ACTION_ID_NEW_EDIT_SESSION, ACTION_ID_NEW_CHAT);
const dialogService = accessor.get(IDialogService);
registerAction2(class NewLocalChatAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.newLocalChat',
title: localize2('chat.newLocalChat.label', "New Local Chat"),
category: CHAT_CATEGORY,
icon: Codicon.plus,
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat)),
f1: false,
});
}
const model = widget.viewModel?.model;
if (model && !(await handleCurrentEditingSession(model, undefined, dialogService))) {
return;
}
async run(accessor: ServicesAccessor, ...args: unknown[]) {
const executeCommandContext = isNewEditSessionActionContext(args[0]) ? args[0] : undefined;
await editingSession?.stop();
// Create a new session with the same type as the current session
const currentResource = widget.viewModel?.model.sessionResource;
const sessionType = currentResource ? getChatSessionType(currentResource) : localChatSessionType;
if (isIChatViewViewContext(widget.viewContext) && sessionType !== localChatSessionType) {
// For the sidebar, we need to explicitly load a session with the same type
const newResource = getResourceForNewChatSession(sessionType);
const view = await viewsService.openView(ChatViewId) as ChatViewPane;
await view.loadSession(newResource);
} else {
// For the editor, widget.clear() already preserves the session type via clearChatEditor
await widget.clear();
}
widget.attachmentModel.clear(true);
widget.input.relatedFiles?.clear();
widget.focusInput();
accessibilityService.alert(localize('newChat', "New chat"));
if (!executeCommandContext) {
return;
}
if (typeof executeCommandContext.agentMode === 'boolean') {
widget.input.setChatMode(executeCommandContext.agentMode ? ChatModeKind.Agent : ChatModeKind.Edit);
}
if (executeCommandContext.inputValue) {
if (executeCommandContext.isPartialQuery) {
widget.setInput(executeCommandContext.inputValue);
} else {
widget.acceptInput(executeCommandContext.inputValue);
}
}
// Context from toolbar or lastFocusedWidget
const context = getEditingSessionContext(accessor, args);
await runNewChatAction(accessor, context, executeCommandContext, AgentSessionProviders.Local);
}
});
CommandsRegistry.registerCommandAlias(ACTION_ID_NEW_EDIT_SESSION, ACTION_ID_NEW_CHAT);
MenuRegistry.appendMenuItem(MenuId.ChatViewSessionTitleNavigationToolbar, {
command: {
@@ -296,3 +285,62 @@ function getResourceForNewChatSession(sessionType: string): URI {
return LocalChatSessionUri.forSession(generateUuid());
}
async function runNewChatAction(
accessor: ServicesAccessor,
context: EditingSessionActionContext | undefined,
executeCommandContext?: INewEditSessionActionContext,
sessionType?: AgentSessionProviders
) {
const accessibilityService = accessor.get(IAccessibilityService);
const viewsService = accessor.get(IViewsService);
const { editingSession, chatWidget: widget } = context ?? {};
if (!widget) {
return;
}
const dialogService = accessor.get(IDialogService);
const model = widget.viewModel?.model;
if (model && !(await handleCurrentEditingSession(model, undefined, dialogService))) {
return;
}
await editingSession?.stop();
// Create a new session with the same type as the current session
const currentResource = widget.viewModel?.model.sessionResource;
const newSessionType = sessionType ?? (currentResource ? getChatSessionType(currentResource) : localChatSessionType);
if (isIChatViewViewContext(widget.viewContext) && newSessionType !== localChatSessionType) {
// For the sidebar, we need to explicitly load a session with the same type
const newResource = getResourceForNewChatSession(newSessionType);
const view = await viewsService.openView(ChatViewId) as ChatViewPane;
await view.loadSession(newResource);
} else {
// For the editor, widget.clear() already preserves the session type via clearChatEditor
await widget.clear();
}
widget.attachmentModel.clear(true);
widget.input.relatedFiles?.clear();
widget.focusInput();
accessibilityService.alert(localize('newChat', "New chat"));
if (!executeCommandContext) {
return;
}
if (typeof executeCommandContext.agentMode === 'boolean') {
widget.input.setChatMode(executeCommandContext.agentMode ? ChatModeKind.Agent : ChatModeKind.Edit);
}
if (executeCommandContext.inputValue) {
if (executeCommandContext.isPartialQuery) {
widget.setInput(executeCommandContext.inputValue);
} else {
widget.acceptInput(executeCommandContext.inputValue);
}
}
}
@@ -58,11 +58,13 @@ export abstract class EditingSessionAction extends Action2 {
abstract runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]): any;
}
export type EditingSessionActionContext = { editingSession?: IChatEditingSession; chatWidget: IChatWidget };
/**
* Resolve view title toolbar context. If none, return context from the lastFocusedWidget.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getEditingSessionContext(accessor: ServicesAccessor, args: any[]): { editingSession?: IChatEditingSession; chatWidget: IChatWidget } | undefined {
export function getEditingSessionContext(accessor: ServicesAccessor, args: any[]): EditingSessionActionContext | undefined {
const arg0 = args.at(0);
const context = isChatViewTitleActionContext(arg0) ? arg0 : undefined;