mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-05 22:36:01 +01:00
sessions: always show New Chat action in session header (#323123)
Render the New Chat action as a labeled "New Chat" button in the session header (via SessionHeaderMetaActionViewItem, registered through the IActionViewItemService so the header toolbar stays generic) and keep it always available. It is hidden only when the chat tab strip is shown (more than one open chat), where the tab strip offers its own "+" button instead - gated by the new SessionHasMultipleOpenChats context key. The Conversations menu keeps its existing behaviour (shown in the header once the session has more than one committed chat) and Mark as Done is grouped with Pin/Close. Header toolbar items are now spaced with a small gap so New Chat and Conversations no longer render flush. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b7924202a3
commit
fc4a0d56cb
@@ -302,6 +302,10 @@
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.chat-composite-bar-toolbar .monaco-action-bar .actions-container {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Floating variant: a lightweight, absolutely positioned toolbar with no header chrome */
|
||||
.chat-composite-bar.chat-composite-bar-toolbar-floating {
|
||||
position: absolute;
|
||||
|
||||
@@ -28,6 +28,7 @@ export const SessionIsStickyContext = new RawContextKey<boolean>('sessionIsStick
|
||||
export const SessionIsMaximizedContext = new RawContextKey<boolean>('sessionIsMaximized', false, localize('sessionIsMaximized', "Whether the session view is currently maximized in the sessions part's grid"));
|
||||
export const SessionSupportsMultipleChatsContext = new RawContextKey<boolean>('sessionSupportsMultipleChats', false, localize('sessionSupportsMultipleChats', "Whether the session view's session supports multiple chats"));
|
||||
export const SessionHasMultipleCommittedChatsContext = new RawContextKey<boolean>('sessionHasMultipleCommittedChats', false, localize('sessionHasMultipleCommittedChats', "Whether the session view's session has more than one committed (non-draft) chat, which drives the Conversations menu visibility"));
|
||||
export const SessionHasMultipleOpenChatsContext = new RawContextKey<boolean>('sessionHasMultipleOpenChats', false, localize('sessionHasMultipleOpenChats', "Whether the session view's session has more than one open chat, i.e. the chat tab strip is shown. Used to hide the header New Chat button, which the tab strip then offers instead"));
|
||||
export const SessionIsReadContext = new RawContextKey<boolean>('sessionIsRead', true, localize('sessionIsRead', "Whether the session has been marked as read"));
|
||||
export const SessionIsArchivedContext = new RawContextKey<boolean>('sessionIsArchived', false, localize('sessionIsArchived', "Whether the session in scope is archived/marked as done (the active session globally, or a specific session within an isolated component such as the session view or a context menu overlay)"));
|
||||
export const SessionHasChangesContext = new RawContextKey<boolean>('sessionHasChanges', false, localize('sessionHasChanges', "Whether the session view's session has pending changes (insertions or deletions)"));
|
||||
|
||||
@@ -29,7 +29,7 @@ export class SessionsChatAccessibilityHelp implements IAccessibleViewImplementat
|
||||
content.push(localize('sessionsChat.workspace', "Shift+Tab to navigate to the workspace picker and choose a workspace for your session."));
|
||||
content.push(localize('sessionsChat.mobileConfig', "On mobile, the mode and model pickers appear as tappable chips below the input. Tap a chip to open a bottom sheet where you can change the selection."));
|
||||
content.push(localize('sessionsChat.history', "Use up and down arrows to navigate your request history in the input box."));
|
||||
content.push(localize('sessionsChat.conversations', "When a session supports multiple chats, the session header has a New Chat action. Once the session has more than one chat, a chat tab strip with a New Chat button appears (including while a new chat is still a draft). The session header keeps the New Chat action until the session has more than one committed chat, at which point the header shows a Conversations menu instead. Open it to start a new chat or to reopen a closed chat. Each chat is listed with a checkbox: checked chats are shown as tabs, unchecked chats are closed (hidden)."));
|
||||
content.push(localize('sessionsChat.conversations', "When a session supports multiple chats, a New Chat button is always shown: as a labeled button in the session header while the session has a single open chat, and as a compact button at the end of the chat tab strip once the session has more than one open chat. Activate it to start a new chat. Once the session has more than one committed chat, the session header also shows a Conversations menu. Open it to start a new chat or to reopen a closed chat: each chat is listed with a checkbox, where checked chats are shown as tabs and unchecked chats are closed (hidden)."));
|
||||
content.push(localize('sessionsChat.closeChat', "Activate a chat tab's close button to close (hide) that chat from the tab strip without deleting it; reopen it later from the Conversations menu. The session's main chat cannot be closed."));
|
||||
content.push(localize('sessionsChat.deleteChat', "To permanently delete a chat, open the chat tab's context menu and choose Delete Chat. This is destructive and cannot be undone."));
|
||||
content.push(localize('sessionsChat.navigatePreviousSession', "Navigate to the previous session in the list{0}.", '<keybinding:sessionsViewPane.navigatePreviousSession>'));
|
||||
|
||||
@@ -14,7 +14,7 @@ import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../work
|
||||
import { SessionsTitleBarContribution } from './sessionsTitleBarWidget.js';
|
||||
import { NewSessionActionViewItemContribution } from './newSessionActionViewItem.js';
|
||||
import { SessionsTelemetryContribution } from './sessionsTelemetry.contribution.js';
|
||||
import { SessionConversationsMenuContribution } from './sessionsActions.js';
|
||||
import { SessionConversationsMenuContribution, SessionNewChatActionViewItemContribution } from './sessionsActions.js';
|
||||
import { SessionsView, SessionsViewId } from './views/sessionsView.js';
|
||||
import './views/sessionsViewActions.js';
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
@@ -58,3 +58,4 @@ registerWorkbenchContribution2(SessionsTitleBarContribution.ID, SessionsTitleBar
|
||||
registerWorkbenchContribution2(NewSessionActionViewItemContribution.ID, NewSessionActionViewItemContribution, WorkbenchPhase.BlockRestore);
|
||||
registerWorkbenchContribution2(SessionsTelemetryContribution.ID, SessionsTelemetryContribution, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(SessionConversationsMenuContribution.ID, SessionConversationsMenuContribution, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(SessionNewChatActionViewItemContribution.ID, SessionNewChatActionViewItemContribution, WorkbenchPhase.AfterRestored);
|
||||
|
||||
@@ -9,9 +9,11 @@ import { hash } from '../../../../base/common/hash.js';
|
||||
import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun, IReader } from '../../../../base/common/observable.js';
|
||||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { Action2, MenuRegistry, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { Action2, MenuRegistry, MenuId, registerAction2, MenuItemAction } from '../../../../platform/actions/common/actions.js';
|
||||
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
|
||||
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
|
||||
@@ -24,13 +26,14 @@ import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/l
|
||||
import { getQuickNavigateHandler } from '../../../../workbench/browser/quickaccess.js';
|
||||
import { Menus } from '../../../browser/menus.js';
|
||||
import { SessionsCategories } from '../../../common/categories.js';
|
||||
import { CanGoBackContext, CanGoForwardContext, SessionProviderIdContext, MultipleSessionsVisibleContext, SessionIsArchivedContext, SessionIsCreatedContext, SessionIsMaximizedContext, SessionIsStickyContext, SessionsFocusContext, SessionSupportsMultipleChatsContext, SessionsWelcomeVisibleContext, SessionIdContext, SessionHasMultipleCommittedChatsContext, SessionsPickerVisibleContext } from '../../../common/contextkeys.js';
|
||||
import { CanGoBackContext, CanGoForwardContext, SessionProviderIdContext, MultipleSessionsVisibleContext, SessionIsArchivedContext, SessionIsCreatedContext, SessionIsMaximizedContext, SessionIsStickyContext, SessionsFocusContext, SessionSupportsMultipleChatsContext, SessionsWelcomeVisibleContext, SessionIdContext, SessionHasMultipleCommittedChatsContext, SessionHasMultipleOpenChatsContext, SessionsPickerVisibleContext } from '../../../common/contextkeys.js';
|
||||
import { ANY_AGENT_HOST_PROVIDER_RE } from '../../../common/agentHostSessionsProvider.js';
|
||||
import { IActiveSession } from '../../../services/sessions/common/sessionsManagement.js';
|
||||
import { ISessionsService } from '../../../services/sessions/browser/sessionsService.js';
|
||||
import { ISession, SessionStatus } from '../../../services/sessions/common/session.js';
|
||||
import { ISessionsPartService } from '../../../services/sessions/browser/sessionsPartService.js';
|
||||
import { ISessionsListModelService } from '../../../services/sessions/browser/sessionsListModelService.js';
|
||||
import { SessionHeaderMetaActionViewItem } from '../../../browser/parts/sessionHeaderMetaActionViewItem.js';
|
||||
|
||||
// -- Show Sessions Picker --
|
||||
|
||||
@@ -404,23 +407,21 @@ registerAction2(class CloseAllSessionsAction extends Action2 {
|
||||
}
|
||||
});
|
||||
|
||||
// The "New Chat" toolbar entry starts a new chat in the session. It is shown in
|
||||
// the session header while the session has at most one committed chat (so it
|
||||
// stays available even when an in-composer draft has surfaced the tab strip).
|
||||
// Once the session has more than one committed chat this toolbar slot shows the
|
||||
// "Conversations" dropdown instead; the tab strip also renders its own "New
|
||||
// Chat" button at the end of the tabs whenever it is visible.
|
||||
// "New Chat" starts a new chat. Hidden once the session has more than one open
|
||||
// chat, since the chat tab strip then offers New Chat at the end of the tabs.
|
||||
const ADD_CHAT_TO_SESSION_ACTION_ID = 'sessions.chatCompositeBar.addChat';
|
||||
|
||||
registerAction2(class AddChatToSessionAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'sessions.chatCompositeBar.addChat',
|
||||
id: ADD_CHAT_TO_SESSION_ACTION_ID,
|
||||
title: localize2('chatCompositeBar.addChat', "New Chat"),
|
||||
icon: Codicon.add,
|
||||
menu: {
|
||||
id: Menus.SessionBarToolbar,
|
||||
group: 'navigation',
|
||||
order: 10,
|
||||
when: ContextKeyExpr.and(SessionIsCreatedContext, SessionSupportsMultipleChatsContext, SessionIsArchivedContext.negate(), SessionHasMultipleCommittedChatsContext.negate()),
|
||||
order: 0,
|
||||
when: ContextKeyExpr.and(SessionIsCreatedContext, SessionSupportsMultipleChatsContext, SessionIsArchivedContext.negate(), SessionHasMultipleOpenChatsContext.negate()),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -436,13 +437,37 @@ registerAction2(class AddChatToSessionAction extends Action2 {
|
||||
}
|
||||
});
|
||||
|
||||
export class SessionNewChatActionViewItemContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
static readonly ID = 'workbench.contrib.sessions.newChatActionViewItem';
|
||||
|
||||
constructor(
|
||||
@IActionViewItemService actionViewItemService: IActionViewItemService,
|
||||
) {
|
||||
super();
|
||||
|
||||
// Fire once after registering so a header toolbar that was already built
|
||||
// (e.g. for a session restored before this contribution runs) re-renders and
|
||||
// picks up this factory; otherwise New Chat stays icon-only until its menu
|
||||
// next changes.
|
||||
const onDidRegister = this._register(new Emitter<void>());
|
||||
this._register(actionViewItemService.register(Menus.SessionBarToolbar, ADD_CHAT_TO_SESSION_ACTION_ID, (action, options, instantiationService) => {
|
||||
if (!(action instanceof MenuItemAction)) {
|
||||
return undefined;
|
||||
}
|
||||
return instantiationService.createInstance(SessionHeaderMetaActionViewItem, undefined, action, options);
|
||||
}, onDidRegister.event));
|
||||
onDidRegister.fire();
|
||||
}
|
||||
}
|
||||
|
||||
// The "Conversations" toolbar entry is a submenu (rendered as a dropdown): it
|
||||
// opens with a "New Chat" entry (registered by SessionConversationsMenuContribution
|
||||
// below), then lists every chat in the session with a checkbox. Checked chats are
|
||||
// shown as tabs; unchecked chats are closed (hidden from the tab strip). Toggling
|
||||
// an entry closes or reopens the corresponding chat. The main chat is always shown
|
||||
// and cannot be closed, so its entry is checked and disabled. It replaces the "New
|
||||
// Chat" toolbar button once the session has more than one committed chat.
|
||||
// and cannot be closed, so its entry is checked and disabled. It is shown in the
|
||||
// session header toolbar once the session has more than one committed chat.
|
||||
MenuRegistry.appendMenuItem(Menus.SessionBarToolbar, {
|
||||
submenu: Menus.SessionConversations,
|
||||
title: localize2('chatCompositeBar.conversations', "Conversations"),
|
||||
|
||||
@@ -718,8 +718,8 @@ registerAction2(class ArchiveSessionAction extends Action2 {
|
||||
when: ContextKeyExpr.equals(SessionIsArchivedContext.key, false),
|
||||
}, {
|
||||
id: Menus.SessionBarToolbar,
|
||||
group: 'navigation',
|
||||
order: 15,
|
||||
group: '1_session',
|
||||
order: 5,
|
||||
when: ContextKeyExpr.and(SessionIsCreatedContext, ContextKeyExpr.equals(SessionIsArchivedContext.key, false)),
|
||||
}]
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
SessionWorkspaceIsVirtualContext,
|
||||
SessionIdContext,
|
||||
SessionHasMultipleCommittedChatsContext,
|
||||
SessionHasMultipleOpenChatsContext,
|
||||
} from '../../../common/contextkeys.js';
|
||||
import { ISession, SessionStatus } from './session.js';
|
||||
import { IActiveSession } from './sessionsManagement.js';
|
||||
@@ -44,6 +45,7 @@ interface ISessionContextKeys {
|
||||
readonly isCreated: IContextKey<boolean>;
|
||||
readonly sticky: IContextKey<boolean>;
|
||||
readonly hasMultipleCommittedChats: IContextKey<boolean>;
|
||||
readonly hasMultipleOpenChats: IContextKey<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,6 +77,7 @@ function getBoundKeys(contextKeyService: IContextKeyService): ISessionContextKey
|
||||
isCreated: SessionIsCreatedContext.bindTo(contextKeyService),
|
||||
sticky: SessionIsStickyContext.bindTo(contextKeyService),
|
||||
hasMultipleCommittedChats: SessionHasMultipleCommittedChatsContext.bindTo(contextKeyService),
|
||||
hasMultipleOpenChats: SessionHasMultipleOpenChatsContext.bindTo(contextKeyService),
|
||||
};
|
||||
boundKeysByService.set(contextKeyService, keys);
|
||||
}
|
||||
@@ -144,4 +147,8 @@ export function setActiveSessionContextKeys(session: IActiveSession | undefined,
|
||||
const committedChatCount = session?.chats.read(reader)
|
||||
.reduce((count, chat) => chat.status.read(reader) === SessionStatus.Untitled ? count : count + 1, 0) ?? 0;
|
||||
keys.hasMultipleCommittedChats.set(committedChatCount > 1);
|
||||
|
||||
// More than one open chat (incl. drafts) means the tab strip is shown; the
|
||||
// header then hides its own New Chat button.
|
||||
keys.hasMultipleOpenChats.set((session?.openChats.read(reader).length ?? 0) > 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user