From c43a23478878bfcbcf51177186af8563c73d3d29 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Wed, 26 Mar 2025 03:46:45 -0700 Subject: [PATCH] commandbar (needs styling for accept/reject) --- src/bootstrap-fork.ts | 3 + .../src/markdown/ApplyBlockHoverButtons.tsx | 38 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 359 +++++++++++++++--- .../void/browser/react/src/util/services.tsx | 4 +- .../void-command-bar-tsx/VoidCommandBar.tsx | 72 ++-- 5 files changed, 374 insertions(+), 102 deletions(-) diff --git a/src/bootstrap-fork.ts b/src/bootstrap-fork.ts index a92290a2..d9f424af 100644 --- a/src/bootstrap-fork.ts +++ b/src/bootstrap-fork.ts @@ -3,6 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// This bootstrap-fork module handles the initialization of a forked process in VS Code. +// It sets up logging, exception handling, and loads the ESM module system. + import * as performance from './vs/base/common/performance.js'; import { removeGlobalNodeJsModuleLookupPaths, devInjectNodeModuleLookupPath } from './bootstrap-node.js'; import { bootstrapESM } from './bootstrap-esm.js'; diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 1069b635..bc68e66f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -267,17 +267,14 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri } - const statusIndicatorHTML =
-
-
+ const color = ( + currStreamState === 'idle-no-changes' ? 'dark' : + currStreamState === 'streaming' ? 'orange' : + currStreamState === 'idle-has-changes' ? 'green' : + null + ) + + const statusIndicatorHTML = return { statusIndicatorHTML, @@ -286,9 +283,22 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri } - - - +export const StatusIndicator = ({ color, title, className }: { color: 'green' | 'orange' | 'dark' | null, title?: React.ReactNode, className?: string }) => { + return ( +
+ {title && {title}} +
+
+ ); +}; export const BlockCodeApplyWrapper = ({ children, diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 48b37593..82ee4e49 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -25,11 +25,12 @@ import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; import { ChatMode, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; import { getModelCapabilities, getIsResoningEnabledState } from '../../../../common/modelCapabilities.js'; -import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, X } from 'lucide-react'; +import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, X } from 'lucide-react'; import { ChatMessage, StagingSelectionItem, ToolMessage, ToolRequestApproval } from '../../../../common/chatThreadServiceTypes.js'; import { ToolCallParams, ToolName, toolNames, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js'; -import { JumpToFileButton, useApplyButtonHTML } from '../markdown/ApplyBlockHoverButtons.js'; +import { JumpToFileButton, StatusIndicator, useApplyButtonHTML } from '../markdown/ApplyBlockHoverButtons.js'; import { IsRunningType } from '../../../chatThreadService.js'; +import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js'; @@ -714,23 +715,26 @@ const ToolHeaderWrapper = ({ return (
{/* header */} -
{ - if (isDropdown) { setIsOpen(v => !v); } - if (onClick) { onClick(); } - }} - > - {isDropdown && ( - - )} +
{/* left */} -
+
{ + if (isDropdown) { setIsOpen(v => !v); } + if (onClick) { onClick(); } + }} + > + {isDropdown && ()} {title} - {desc1} + {desc1}
{/* right */} @@ -1865,28 +1869,266 @@ const ChatBubble = ({ chatMessage, isCommitted, messageIdx, isLast, chatIsRunnin +export const AcceptAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => ( + +) + +export const RejectAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => ( + +) + + + const CommandBarInChat = () => { - const { state: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState() - const [isExpanded, setIsExpanded] = useState(false) + 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 chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) - if (!sortedCommandBarURIs || sortedCommandBarURIs.length === 0) { - return null - } + const [isFileDetailsOpened, setFileDetailsOpened] = useState(false); + + // close the file details if there are no files + useEffect(() => { + if (isFileDetailsOpened && numFilesChanged === 0) { + setFileDetailsOpened(false) + } + }, [isFileDetailsOpened, numFilesChanged, setFileDetailsOpened]) + + + const isFinishedMakingThreadChanges = chatThreadsStreamState && !chatThreadsStreamState.isRunning && numFilesChanged !== 0 + + // ======== 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: 'orange', } as const + : chatThreadsStreamState?.isRunning ? { title: 'Running', color: 'green', } 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'} changed` + + const acceptAllButton = ( + { + sortedCommandBarURIs.forEach(uri => { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "accept", + _addToHistory: true, + }) + }) + }} + /> + ) + + const rejectAllButton = ( + { + sortedCommandBarURIs.forEach(uri => { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "reject", + _addToHistory: true, + }) + }) + }} + /> + ) + + + const acceptRejectAllButtons = isFinishedMakingThreadChanges &&
+ {acceptAllButton} + {rejectAllButton} +
+ + + // !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: 'green', } as const + ) + + const acceptButton = { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "accept", _addToHistory: true, }) }} + /> + + const rejectButton = { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "reject", _addToHistory: true, }) }} + /> + + const fileNameHTML =
commandService.executeCommand('vscode.open', uri, { preview: true })} + > + + {basename} +
+ + const detailsContent = <> + {numDiffs} change{numDiffs !== 1 ? 's' : ''} + + + const acceptRejectButtons = isFinishedMakingFileChanges &&
+ {acceptButton} + {rejectButton} +
+ + const fileStatusHTML = + + return ( + // name, details +
+
+ {fileNameHTML} + {detailsContent} +
+
+ {acceptRejectButtons} + {fileStatusHTML} +
+
+ ) + })} +
+ + const fileDetailsButton = ( + + ) + + + const changesContent = <> + {/*
+
{'chatThreadsStreamState' + chatThreadsStreamState}
+
{'isRunning' + chatThreadsStreamState?.isRunning}
+
{'isFinishedWithChanges' + isFinishedMakingThreadChanges}
+
*/} + {fileDetailsButton} + return ( - - {sortedCommandBarURIs.map((uri, i) => ( - { commandService.executeCommand('vscode.open', uri, { preview: true }) }} - /> - ))} - + <> + {/* file details */} +
+
+ {fileDetailsContent} +
+
+ {/* main content */} +
+
+ {changesContent} +
+
+ {acceptRejectAllButtons} + {threadStatusHTML} +
+
+ ) } @@ -2079,33 +2321,40 @@ export const SidebarChat = () => { } }, [onSubmit, onAbort, isRunning]) - const inputForm =
- { textAreaRef.current?.focus() }} + const inputForm =
+
+ {previousMessages.length > 0 && + + } +
+
- { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }} - ref={textAreaRef} - fnsRef={textAreaFnsRef} - multiline={true} - /> + { textAreaRef.current?.focus() }} + > + { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }} + ref={textAreaRef} + fnsRef={textAreaFnsRef} + multiline={true} + /> - + +
return ( diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 95060cb2..27254bbd 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -348,9 +348,9 @@ export const useCommandBarURIListener = (listener: (uri: URI) => void) => { export const useCommandBarState = () => { const accessor = useAccessor() const commandBarService = accessor.get('IVoidCommandBarService') - const [s, ss] = useState({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs }); + const [s, ss] = useState({ stateOfURI: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs }); const listener = useCallback(() => { - ss({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs }); + ss({ stateOfURI: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs }); }, [commandBarService]) useCommandBarURIListener(listener) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx index 1bd5cdfd..b144a34d 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx @@ -9,8 +9,9 @@ import { useAccessor, useCommandBarState, useIsDark } from '../util/services.js' import '../styles.css' import { useCallback, useEffect, useState, useRef } from 'react'; import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'; -import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBorder } from '../../../../common/helpers/colors.js'; +import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js'; import { VoidCommandBarProps } from '../../../voidCommandBarService.js'; +import { AcceptAllButtonWrapper, RejectAllButtonWrapper } from '../sidebar-tsx/SidebarChat.js'; export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => { const isDark = useIsDark() @@ -39,7 +40,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => { const commandService = accessor.get('ICommandService') const commandBarService = accessor.get('IVoidCommandBarService') const voidModelService = accessor.get('IVoidModelService') - const { state: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState() + const { stateOfURI: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState() // useEffect(() => { @@ -211,38 +212,47 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => { if (!isADiffZoneInAnyFile) return null - const acceptAllButton = + + // const rejectAllButton = + + const acceptAllButton = - Accept File - + /> - - const rejectAllButton = + /> const acceptRejectAllButtons =
{acceptAllButton}