From 147268034e772f7133220136ef4829e6eb5a32fe Mon Sep 17 00:00:00 2001 From: Tajudeen Date: Thu, 23 Jul 2026 12:09:02 +0100 Subject: [PATCH] refactor(ui): extract CommandBarInChat and extend plan a11y Move the file-change command bar out of SidebarChat into composer/CommandBarInChat and add aria labels for accept/reject controls, file links, and plan expand/collapse. Co-authored-by: Cursor --- .../react/src/sidebar-tsx/SidebarChat.tsx | 271 +----------------- .../react/src/sidebar-tsx/chat/ChatBubble.tsx | 6 + .../sidebar-tsx/composer/CommandBarInChat.tsx | 219 ++++++++++++++ 3 files changed, 229 insertions(+), 267 deletions(-) create mode 100644 src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/composer/CommandBarInChat.tsx diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx index 2f135cb3d55..27ea474d192 100644 --- a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -6,7 +6,7 @@ import React, { FormEvent, FormHTMLAttributes, Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI, useCommandBarState, useFullChatThreadsStreamState } from '../util/services.js'; +import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI, useFullChatThreadsStreamState } from '../util/services.js'; import { ChatMarkdownRender, ChatMessageLocation, getApplyBoxId } from '../markdown/ChatMarkdownRender.js'; import { URI } from '../../../../../../../base/common/uri.js'; @@ -18,6 +18,7 @@ import { VoidChatArea, ButtonSubmit, ButtonStop } from './composer/VoidChatArea. import { SelectedFiles } from './composer/SelectedFiles.js'; import { ScrollToBottomContainer } from './composer/ScrollToBottomContainer.js'; import { ContextUsageBar } from './composer/ContextUsageBar.js'; +import { CommandBarInChat } from './composer/CommandBarInChat.js'; import { LandingPage } from './landing/LandingPage.js'; import { ComposerTabs } from './chrome/ComposerTabs.js'; import { ThreadHeader } from './chrome/ThreadHeader.js'; @@ -30,7 +31,7 @@ import { getModelCapabilities, getIsReasoningEnabledState, getReservedOutputToke import { AlertTriangle, File, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon, Info, CirclePlus, Ellipsis, CircleEllipsis, Folder, ALargeSmall, TypeOutline, Text } from 'lucide-react'; import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage, PlanMessage, ReviewMessage, PlanStep, StepStatus, PlanApprovalState } from '../../../../common/chatThreadServiceTypes.js'; import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolName, ToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js'; -import { CopyButton, EditToolAcceptRejectButtonsHTML, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js'; +import { CopyButton, EditToolAcceptRejectButtonsHTML, JumpToFileButton, JumpToTerminalButton, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js'; import { IsRunningType } from '../../../chatThreadService.js'; import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js'; import { builtinToolNames, isABuiltinToolName, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME } from '../../../../common/prompt/prompts.js'; @@ -60,276 +61,12 @@ export { getBasename, getFolderName, getRelative, voidOpenFileFn } from './share export { ToolChildrenWrapper, CodeChildren, ListableToolItem } from './tools/ToolPrimitives.js'; export { VoidChatArea, ButtonSubmit, ButtonStop } from './composer/VoidChatArea.js'; export { SelectedFiles } from './composer/SelectedFiles.js'; +export { CommandBarInChat } from './composer/CommandBarInChat.js'; export { ChatBubble } from './chat/ChatBubble.js'; - -const CommandBarInChat = () => { - const { stateOfURI: commandBarStateOfURI, sortedURIs: sortedCommandBarURIs } = useCommandBarState() - const numFilesChanged = sortedCommandBarURIs.length - - const accessor = useAccessor() - const editCodeService = accessor.get('IEditCodeService') - const commandService = accessor.get('ICommandService') - const chatThreadsState = useChatThreadsState() - const commandBarState = useCommandBarState() - const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) - - // ( - // - // ) - - const [fileDetailsOpenedState, setFileDetailsOpenedState] = useState<'auto-opened' | 'auto-closed' | 'user-opened' | 'user-closed'>('auto-closed'); - const isFileDetailsOpened = fileDetailsOpenedState === 'auto-opened' || fileDetailsOpenedState === 'user-opened'; - - - useEffect(() => { - // close the file details if there are no files - // this converts 'user-closed' to 'auto-closed' - if (numFilesChanged === 0) { - setFileDetailsOpenedState('auto-closed') - } - // open the file details if it hasnt been closed - if (numFilesChanged > 0 && fileDetailsOpenedState !== 'user-closed') { - setFileDetailsOpenedState('auto-opened') - } - }, [fileDetailsOpenedState, setFileDetailsOpenedState, numFilesChanged]) - - - const isFinishedMakingThreadChanges = ( - // there are changed files - commandBarState.sortedURIs.length !== 0 - // none of the files are streaming - && commandBarState.sortedURIs.every(uri => !commandBarState.stateOfURI[uri.fsPath]?.isStreaming) - ) - - // ======== status of agent ======== - // This icon answers the question "is the LLM doing work on this thread?" - // assume it is single threaded for now - // green = Running - // orange = Requires action - // dark = Done - - const threadStatus = ( - chatThreadsStreamState?.isRunning === 'awaiting_user' - ? { title: 'Needs Approval', color: 'yellow', } as const - : (chatThreadsStreamState?.isRunning === 'LLM' || chatThreadsStreamState?.isRunning === 'tool' || chatThreadsStreamState?.isRunning === 'preparing') - ? { title: chatThreadsStreamState?.isRunning === 'preparing' ? 'Preparing' : 'Running', color: 'orange', } as const - : { title: 'Done', color: 'dark', } as const - ) - - - const threadStatusHTML = - - - // ======== info about changes ======== - // num files changed - // acceptall + rejectall - // popup info about each change (each with num changes + acceptall + rejectall of their own) - - const numFilesChangedStr = numFilesChanged === 0 ? 'No files with changes' - : `${sortedCommandBarURIs.length} file${numFilesChanged === 1 ? '' : 's'} with changes` - - - - - const acceptRejectAllButtons =
- { - sortedCommandBarURIs.forEach(uri => { - editCodeService.acceptOrRejectAllDiffAreas({ - uri, - removeCtrlKs: true, - behavior: "reject", - _addToHistory: true, - }); - }); - }} - data-tooltip-id='cortex-tooltip' - data-tooltip-place='top' - data-tooltip-content='Reject all' - /> - - { - sortedCommandBarURIs.forEach(uri => { - editCodeService.acceptOrRejectAllDiffAreas({ - uri, - removeCtrlKs: true, - behavior: "accept", - _addToHistory: true, - }); - }); - }} - data-tooltip-id='cortex-tooltip' - data-tooltip-place='top' - data-tooltip-content='Accept all' - /> - - - -
- - - // !select-text cursor-auto - const fileDetailsContent =
- {sortedCommandBarURIs.map((uri, i) => { - const basename = getBasename(uri.fsPath) - - const { sortedDiffIds, isStreaming } = commandBarStateOfURI[uri.fsPath] ?? {} - const isFinishedMakingFileChanges = !isStreaming - - const numDiffs = sortedDiffIds?.length || 0 - - const fileStatus = (isFinishedMakingFileChanges - ? { title: 'Done', color: 'dark', } as const - : { title: 'Running', color: 'orange', } as const - ) - - const fileNameHTML =
voidOpenFileFn(uri, accessor)} - > - {/* */} - {basename} -
- - - - - const detailsContent =
- {numDiffs} diff{numDiffs !== 1 ? 's' : ''} -
- - const acceptRejectButtons =
- {/* */} - { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "reject", _addToHistory: true, }); }} - data-tooltip-id='cortex-tooltip' - data-tooltip-place='top' - data-tooltip-content='Reject file' - - /> - { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "accept", _addToHistory: true, }); }} - data-tooltip-id='cortex-tooltip' - data-tooltip-place='top' - data-tooltip-content='Accept file' - /> - -
- - const fileStatusHTML = - - return ( - // name, details -
-
- {fileNameHTML} - {detailsContent} -
-
- {acceptRejectButtons} - {fileStatusHTML} -
-
- ) - })} -
- - const fileDetailsButton = ( - - ) - - return ( - <> - {/* file details */} -
-
- {fileDetailsContent} -
-
- {/* main content */} -
-
- {fileDetailsButton} -
-
- {acceptRejectAllButtons} - {threadStatusHTML} -
-
- - ) -} - - - export const SidebarChat = () => { const textAreaRef = useRef(null) const textAreaFnsRef = useRef(null) diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/chat/ChatBubble.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/chat/ChatBubble.tsx index 538bf1c83b7..9390a286afa 100644 --- a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/chat/ChatBubble.tsx +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/chat/ChatBubble.tsx @@ -218,9 +218,12 @@ const PlanComponent = React.memo(({ message, isCheckpointGhost, threadId, messag
; + + const detailsContent =
+ {numDiffs} diff{numDiffs !== 1 ? 's' : ''} +
; + + const acceptRejectButtons =
+ { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: 'reject', _addToHistory: true }); }} + data-tooltip-id='cortex-tooltip' + data-tooltip-place='top' + data-tooltip-content='Reject file' + /> + { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: 'accept', _addToHistory: true }); }} + data-tooltip-id='cortex-tooltip' + data-tooltip-place='top' + data-tooltip-content='Accept file' + /> +
; + + const fileStatusHTML = ; + + return ( +
+
+ {fileNameHTML} + {detailsContent} +
+
+ {acceptRejectButtons} + {fileStatusHTML} +
+
+ ); + })} +
; + + const fileDetailsButton = ( + + ); + + return ( + <> +
+
+ {fileDetailsContent} +
+
+
+
+ {fileDetailsButton} +
+
+ {acceptRejectAllButtons} + {threadStatusHTML} +
+
+ + ); +};