From 0e624946cf399e1175bf6b00bb61c08d827d94bc Mon Sep 17 00:00:00 2001 From: William Viana Date: Wed, 25 Jun 2025 20:51:02 -0700 Subject: [PATCH] chore: format and remove dead code from SidebarChat.tsx --- .../react/src/sidebar-tsx/SidebarChat.tsx | 5076 ++++++++++------- 1 file changed, 2956 insertions(+), 2120 deletions(-) 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 ea24ef38a92..5fcc362b1a3 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 @@ -3,63 +3,134 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; - - -import { useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useActiveURI, useCommandBarState, useFullChatThreadsStreamState } from '../util/services.js'; -import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'; - -import { ChatMarkdownRender, ChatMessageLocation, getApplyBoxId } from '../markdown/ChatMarkdownRender.js'; -import { URI } from '../../../../../../../base/common/uri.js'; -import { IDisposable } from '../../../../../../../base/common/lifecycle.js'; -import { ErrorDisplay } from './ErrorDisplay.js'; -import { BlockCode, TextAreaFns, VoidCustomDropdownBox, VoidInputBox2, VoidSlider, VoidSwitch, VoidDiffEditor } from '../util/inputs.js'; -import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js'; -import { PastThreadsList } from './SidebarThreadSelector.js'; -import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; -import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; -import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; -import { ICommandService } from '../../../../../../../platform/commands/common/commands.js'; -import { WarningBox } from '../void-settings-tsx/WarningBox.js'; -import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js'; -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 } 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 { 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'; -import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js'; -import ErrorBoundary from './ErrorBoundary.js'; -import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js'; - -import { persistentTerminalNameOfId } from '../../../terminalToolService.js'; -import { removeMCPToolNamePrefix } from '../../../../common/mcpServiceTypes.js'; - - - -export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps) => { +import React, { + ButtonHTMLAttributes, + Fragment, + KeyboardEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; + +import { ScrollType } from "../../../../../../../editor/common/editorCommon.js"; +import { + useAccessor, + useActiveURI, + useChatThreadsState, + useChatThreadsStreamState, + useCommandBarState, + useFullChatThreadsStreamState, + useSettingsState, +} from "../util/services.js"; + +import { + AlertTriangle, + Ban, + Check, + ChevronRight, + CircleEllipsis, + File, + Folder, + Pencil, + Text, + X, +} from "lucide-react"; +import { URI } from "../../../../../../../base/common/uri.js"; +import { + ChatMode, + FeatureName, + isFeatureNameDisabled, +} from "../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js"; +import { + ChatMessage, + StagingSelectionItem, + ToolMessage, +} from "../../../../common/chatThreadServiceTypes.js"; +import { + getIsReasoningEnabledState, + getModelCapabilities, +} from "../../../../common/modelCapabilities.js"; +import { + builtinToolNames, + isABuiltinToolName, + MAX_FILE_CHARS_PAGE, +} from "../../../../common/prompt/prompts.js"; +import { RawToolCallObj } from "../../../../common/sendLLMMessageTypes.js"; +import { + approvalTypeOfBuiltinToolName, + BuiltinToolCallParams, + BuiltinToolName, + LintErrorItem, + ToolName, +} from "../../../../common/toolsServiceTypes.js"; +import { VOID_CTRL_L_ACTION_ID } from "../../../actionIDs.js"; +import { IsRunningType } from "../../../chatThreadService.js"; +import { VOID_OPEN_SETTINGS_ACTION_ID } from "../../../voidSettingsPane.js"; +import { + CopyButton, + EditToolAcceptRejectButtonsHTML, + IconShell1, + StatusIndicator, + useEditToolStreamState, +} from "../markdown/ApplyBlockHoverButtons.js"; +import { + ChatMarkdownRender, + ChatMessageLocation, + getApplyBoxId, +} from "../markdown/ChatMarkdownRender.js"; +import { + BlockCode, + TextAreaFns, + VoidCustomDropdownBox, + VoidDiffEditor, + VoidInputBox2, + VoidSlider, + VoidSwitch, +} from "../util/inputs.js"; +import { ModelDropdown } from "../void-settings-tsx/ModelDropdown.js"; +import { ToolApprovalTypeSwitch } from "../void-settings-tsx/Settings.js"; +import { WarningBox } from "../void-settings-tsx/WarningBox.js"; +import ErrorBoundary from "./ErrorBoundary.js"; +import { ErrorDisplay } from "./ErrorDisplay.js"; +import { PastThreadsList } from "./SidebarThreadSelector.js"; + +import { removeMCPToolNamePrefix } from "../../../../common/mcpServiceTypes.js"; +import { persistentTerminalNameOfId } from "../../../terminalToolService.js"; + +export const IconX = ({ + size, + className = "", + ...props +}: { size: number; className?: string } & React.SVGProps) => { return ( ); }; -const IconArrowUp = ({ size, className = '' }: { size: number, className?: string }) => { +const IconArrowUp = ({ + size, + className = "", +}: { + size: number; + className?: string; +}) => { return ( { +const IconSquare = ({ + size, + className = "", +}: { + size: number; + className?: string; +}) => { return ( { +export const IconWarning = ({ + size, + className = "", +}: { + size: number; + className?: string; +}) => { return ( { - - const [loadingText, setLoadingText] = useState('.'); +export const IconLoading = ({ className = "" }: { className?: string }) => { + const [loadingText, setLoadingText] = useState("."); useEffect(() => { let intervalId; // Function to handle the animation const toggleLoadingText = () => { - if (loadingText === '...') { - setLoadingText('.'); + if (loadingText === "...") { + setLoadingText("."); } else { - setLoadingText(loadingText + '.'); + setLoadingText(loadingText + "."); } }; @@ -144,150 +223,209 @@ export const IconLoading = ({ className = '' }: { className?: string }) => { }, [loadingText, setLoadingText]); return
{loadingText}
; - -} - - +}; // SLIDER ONLY: -const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => { - const accessor = useAccessor() - - const voidSettingsService = accessor.get('IVoidSettingsService') - const voidSettingsState = useSettingsState() +const ReasoningOptionSlider = ({ + featureName, +}: { + featureName: FeatureName; +}) => { + const accessor = useAccessor(); - const modelSelection = voidSettingsState.modelSelectionOfFeature[featureName] - const overridesOfModel = voidSettingsState.overridesOfModel + const voidSettingsService = accessor.get("IVoidSettingsService"); + const voidSettingsState = useSettingsState(); - if (!modelSelection) return null + const modelSelection = voidSettingsState.modelSelectionOfFeature[featureName]; + const overridesOfModel = voidSettingsState.overridesOfModel; - const { modelName, providerName } = modelSelection - const { reasoningCapabilities } = getModelCapabilities(providerName, modelName, overridesOfModel) - const { canTurnOffReasoning, reasoningSlider: reasoningBudgetSlider } = reasoningCapabilities || {} + if (!modelSelection) return null; - const modelSelectionOptions = voidSettingsState.optionsOfModelSelection[featureName][providerName]?.[modelName] - const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) + const { modelName, providerName } = modelSelection; + const { reasoningCapabilities } = getModelCapabilities( + providerName, + modelName, + overridesOfModel + ); + const { canTurnOffReasoning, reasoningSlider: reasoningBudgetSlider } = + reasoningCapabilities || {}; + + const modelSelectionOptions = + voidSettingsState.optionsOfModelSelection[featureName][providerName]?.[ + modelName + ]; + const isReasoningEnabled = getIsReasoningEnabledState( + featureName, + providerName, + modelName, + modelSelectionOptions, + overridesOfModel + ); - if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider - return
- Thinking - { - const isOff = canTurnOffReasoning && !newVal - voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff }) - }} - /> -
+ if (canTurnOffReasoning && !reasoningBudgetSlider) { + // if it's just a on/off toggle without a power slider + return ( +
+ + Thinking + + { + const isOff = canTurnOffReasoning && !newVal; + voidSettingsService.setOptionsOfModelSelection( + featureName, + modelSelection.providerName, + modelSelection.modelName, + { reasoningEnabled: !isOff } + ); + }} + /> +
+ ); } - if (reasoningBudgetSlider?.type === 'budget_slider') { // if it's a slider - const { min: min_, max, default: defaultVal } = reasoningBudgetSlider - - const nSteps = 8 // only used in calculating stepSize, stepSize is what actually matters - const stepSize = Math.round((max - min_) / nSteps) - - const valueIfOff = min_ - stepSize - const min = canTurnOffReasoning ? valueIfOff : min_ - const value = isReasoningEnabled ? voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningBudget ?? defaultVal - : valueIfOff - - return
- Thinking - { - const isOff = canTurnOffReasoning && newVal === valueIfOff - voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningBudget: newVal }) - }} - /> - {isReasoningEnabled ? `${value} tokens` : 'Thinking disabled'} -
+ if (reasoningBudgetSlider?.type === "budget_slider") { + // if it's a slider + const { min: min_, max, default: defaultVal } = reasoningBudgetSlider; + + const nSteps = 8; // only used in calculating stepSize, stepSize is what actually matters + const stepSize = Math.round((max - min_) / nSteps); + + const valueIfOff = min_ - stepSize; + const min = canTurnOffReasoning ? valueIfOff : min_; + const value = isReasoningEnabled + ? voidSettingsState.optionsOfModelSelection[featureName][ + modelSelection.providerName + ]?.[modelSelection.modelName]?.reasoningBudget ?? defaultVal + : valueIfOff; + + return ( +
+ + Thinking + + { + const isOff = canTurnOffReasoning && newVal === valueIfOff; + voidSettingsService.setOptionsOfModelSelection( + featureName, + modelSelection.providerName, + modelSelection.modelName, + { reasoningEnabled: !isOff, reasoningBudget: newVal } + ); + }} + /> + + {isReasoningEnabled ? `${value} tokens` : "Thinking disabled"} + +
+ ); } - if (reasoningBudgetSlider?.type === 'effort_slider') { - - const { values, default: defaultVal } = reasoningBudgetSlider - - const min = canTurnOffReasoning ? -1 : 0 - const max = values.length - 1 - - const currentEffort = voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningEffort ?? defaultVal - const valueIfOff = -1 - const value = isReasoningEnabled && currentEffort ? values.indexOf(currentEffort) : valueIfOff - - const currentEffortCapitalized = currentEffort.charAt(0).toUpperCase() + currentEffort.slice(1, Infinity) - - return
- Thinking - { - const isOff = canTurnOffReasoning && newVal === valueIfOff - voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningEffort: values[newVal] ?? undefined }) - }} - /> - {isReasoningEnabled ? `${currentEffortCapitalized}` : 'Thinking disabled'} -
+ if (reasoningBudgetSlider?.type === "effort_slider") { + const { values, default: defaultVal } = reasoningBudgetSlider; + + const min = canTurnOffReasoning ? -1 : 0; + const max = values.length - 1; + + const currentEffort = + voidSettingsState.optionsOfModelSelection[featureName][ + modelSelection.providerName + ]?.[modelSelection.modelName]?.reasoningEffort ?? defaultVal; + const valueIfOff = -1; + const value = + isReasoningEnabled && currentEffort + ? values.indexOf(currentEffort) + : valueIfOff; + + const currentEffortCapitalized = + currentEffort.charAt(0).toUpperCase() + currentEffort.slice(1, Infinity); + + return ( +
+ + Thinking + + { + const isOff = canTurnOffReasoning && newVal === valueIfOff; + voidSettingsService.setOptionsOfModelSelection( + featureName, + modelSelection.providerName, + modelSelection.modelName, + { + reasoningEnabled: !isOff, + reasoningEffort: values[newVal] ?? undefined, + } + ); + }} + /> + + {isReasoningEnabled + ? `${currentEffortCapitalized}` + : "Thinking disabled"} + +
+ ); } - return null -} - - + return null; +}; const nameOfChatMode = { - 'normal': 'Chat', - 'gather': 'Gather', - 'agent': 'Agent', -} + normal: "Chat", + gather: "Gather", + agent: "Agent", +}; const detailOfChatMode = { - 'normal': 'Normal chat', - 'gather': 'Reads files, but can\'t edit', - 'agent': 'Edits files and uses tools', -} - + normal: "Normal chat", + gather: "Reads files, but can't edit", + agent: "Edits files and uses tools", +}; const ChatModeDropdown = ({ className }: { className: string }) => { - const accessor = useAccessor() - - const voidSettingsService = accessor.get('IVoidSettingsService') - const settingsState = useSettingsState() - - const options: ChatMode[] = useMemo(() => ['normal', 'gather', 'agent'], []) - - const onChangeOption = useCallback((newVal: ChatMode) => { - voidSettingsService.setGlobalSetting('chatMode', newVal) - }, [voidSettingsService]) - - return nameOfChatMode[val]} - getOptionDropdownName={(val) => nameOfChatMode[val]} - getOptionDropdownDetail={(val) => detailOfChatMode[val]} - getOptionsEqual={(a, b) => a === b} - /> - -} + const accessor = useAccessor(); + const voidSettingsService = accessor.get("IVoidSettingsService"); + const settingsState = useSettingsState(); + const options: ChatMode[] = useMemo(() => ["normal", "gather", "agent"], []); + const onChangeOption = useCallback( + (newVal: ChatMode) => { + voidSettingsService.setGlobalSetting("chatMode", newVal); + }, + [voidSettingsService] + ); + return ( + nameOfChatMode[val]} + getOptionDropdownName={(val) => nameOfChatMode[val]} + getOptionDropdownDetail={(val) => detailOfChatMode[val]} + getOptionsEqual={(a, b) => a === b} + /> + ); +}; interface VoidChatAreaProps { // Required @@ -307,8 +445,8 @@ interface VoidChatAreaProps { showProspectiveSelections?: boolean; loadingIcon?: React.ReactNode; - selections?: StagingSelectionItem[] - setSelections?: (s: StagingSelectionItem[]) => void + selections?: StagingSelectionItem[]; + setSelections?: (s: StagingSelectionItem[]) => void; // selections?: any[]; // onSelectionsChange?: (selections: any[]) => void; @@ -328,7 +466,7 @@ export const VoidChatArea: React.FC = ({ divRef, isStreaming = false, isDisabled = false, - className = '', + className = "", showModelDropdown = true, showSelections = false, showProspectiveSelections = false, @@ -351,13 +489,13 @@ export const VoidChatArea: React.FC = ({ ${className} `} onClick={(e) => { - onClickAnywhere?.() + onClickAnywhere?.(); }} > {/* Selections section */} {showSelections && selections && setSelections && ( = ({ {/* Close button (X) if onClose is provided */} {onClose && ( -
+
= ({
{/* Bottom row */} -
+
{showModelDropdown && ( -
+
-
- {featureName === 'Chat' && } - +
+ {featureName === "Chat" && ( + + )} +
)}
- {isStreaming && loadingIcon} {isStreaming ? ( ) : ( - + )}
-
); }; - - - -type ButtonProps = ButtonHTMLAttributes +type ButtonProps = ButtonHTMLAttributes; const DEFAULT_BUTTON_SIZE = 22; -export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Required>) => { - - return -} + {...props} + > + + + ); +}; -export const ButtonStop = ({ className, ...props }: ButtonHTMLAttributes) => { - return -} - - + type="button" + {...props} + > + + + ); +}; const scrollToBottom = (divRef: { current: HTMLElement | null }) => { if (divRef.current) { @@ -455,20 +595,27 @@ const scrollToBottom = (divRef: { current: HTMLElement | null }) => { } }; - - -const ScrollToBottomContainer = ({ children, className, style, scrollContainerRef }: { children: React.ReactNode, className?: string, style?: React.CSSProperties, scrollContainerRef: React.MutableRefObject }) => { +const ScrollToBottomContainer = ({ + children, + className, + style, + scrollContainerRef, +}: { + children: React.ReactNode; + className?: string; + style?: React.CSSProperties; + scrollContainerRef: React.MutableRefObject; +}) => { const [isAtBottom, setIsAtBottom] = useState(true); // Start at bottom - const divRef = scrollContainerRef + const divRef = scrollContainerRef; const onScroll = () => { const div = divRef.current; if (!div) return; - const isBottom = Math.abs( - div.scrollHeight - div.clientHeight - div.scrollTop - ) < 4; + const isBottom = + Math.abs(div.scrollHeight - div.clientHeight - div.scrollTop) < 4; setIsAtBottom(isBottom); }; @@ -486,54 +633,54 @@ const ScrollToBottomContainer = ({ children, className, style, scrollContainerRe }, []); return ( -
+
{children}
); }; -export const getRelative = (uri: URI, accessor: ReturnType) => { - const workspaceContextService = accessor.get('IWorkspaceContextService') - let path: string - const isInside = workspaceContextService.isInsideWorkspace(uri) +export const getRelative = ( + uri: URI, + accessor: ReturnType +) => { + const workspaceContextService = accessor.get("IWorkspaceContextService"); + let path: string; + const isInside = workspaceContextService.isInsideWorkspace(uri); if (isInside) { - const f = workspaceContextService.getWorkspace().folders.find(f => uri.fsPath?.startsWith(f.uri.fsPath)) - if (f) { path = uri.fsPath.replace(f.uri.fsPath, '') } - else { path = uri.fsPath } - } - else { - path = uri.fsPath + const f = workspaceContextService + .getWorkspace() + .folders.find((f) => uri.fsPath?.startsWith(f.uri.fsPath)); + if (f) { + path = uri.fsPath.replace(f.uri.fsPath, ""); + } else { + path = uri.fsPath; + } + } else { + path = uri.fsPath; } - return path || undefined -} + return path || undefined; +}; export const getFolderName = (pathStr: string) => { // 'unixify' path - pathStr = pathStr.replace(/[/\\]+/g, '/') // replace any / or \ or \\ with / - const parts = pathStr.split('/') // split on / + pathStr = pathStr.replace(/[/\\]+/g, "/"); // replace any / or \ or \\ with / + const parts = pathStr.split("/"); // split on / // Filter out empty parts (the last element will be empty if path ends with /) - const nonEmptyParts = parts.filter(part => part.length > 0) - if (nonEmptyParts.length === 0) return '/' // Root directory - if (nonEmptyParts.length === 1) return nonEmptyParts[0] + '/' // Only one folder + const nonEmptyParts = parts.filter((part) => part.length > 0); + if (nonEmptyParts.length === 0) return "/"; // Root directory + if (nonEmptyParts.length === 1) return nonEmptyParts[0] + "/"; // Only one folder // Get the last two parts - const lastTwo = nonEmptyParts.slice(-2) - return lastTwo.join('/') + '/' -} + const lastTwo = nonEmptyParts.slice(-2); + return lastTwo.join("/") + "/"; +}; export const getBasename = (pathStr: string, parts: number = 1) => { // 'unixify' path - pathStr = pathStr.replace(/[/\\]+/g, '/') // replace any / or \ or \\ with / - const allParts = pathStr.split('/') // split on / - if (allParts.length === 0) return pathStr - return allParts.slice(-parts).join('/') -} - - + pathStr = pathStr.replace(/[/\\]+/g, "/"); // replace any / or \ or \\ with / + const allParts = pathStr.split("/"); // split on / + if (allParts.length === 0) return pathStr; + return allParts.slice(-parts).join("/"); +}; // Open file utility function export const voidOpenFileFn = ( @@ -541,8 +688,8 @@ export const voidOpenFileFn = ( accessor: ReturnType, range?: [number, number] ) => { - const commandService = accessor.get('ICommandService') - const editorService = accessor.get('ICodeEditorService') + const commandService = accessor.get("ICommandService"); + const editorService = accessor.get("ICodeEditorService"); // Get editor selection from CodeSelection range let editorSelection = undefined; @@ -558,200 +705,240 @@ export const voidOpenFileFn = ( } // open the file - commandService.executeCommand('vscode.open', uri).then(() => { - + commandService.executeCommand("vscode.open", uri).then(() => { // select the text setTimeout(() => { if (!editorSelection) return; - const editor = editorService.getActiveCodeEditor() + const editor = editorService.getActiveCodeEditor(); if (!editor) return; - editor.setSelection(editorSelection) - editor.revealRange(editorSelection, ScrollType.Immediate) - - }, 50) // needed when document was just opened and needs to initialize - - }) - + editor.setSelection(editorSelection); + editor.revealRange(editorSelection, ScrollType.Immediate); + }, 50); // needed when document was just opened and needs to initialize + }); }; - -export const SelectedFiles = ( - { type, selections, setSelections, showProspectiveSelections, messageIdx, }: - | { type: 'past', selections: StagingSelectionItem[]; setSelections?: undefined, showProspectiveSelections?: undefined, messageIdx: number, } - | { type: 'staging', selections: StagingSelectionItem[]; setSelections: ((newSelections: StagingSelectionItem[]) => void), showProspectiveSelections?: boolean, messageIdx?: number } -) => { - - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const modelReferenceService = accessor.get('IVoidModelService') - - - +export const SelectedFiles = ({ + type, + selections, + setSelections, + showProspectiveSelections, + messageIdx, +}: + | { + type: "past"; + selections: StagingSelectionItem[]; + setSelections?: undefined; + showProspectiveSelections?: undefined; + messageIdx: number; + } + | { + type: "staging"; + selections: StagingSelectionItem[]; + setSelections: (newSelections: StagingSelectionItem[]) => void; + showProspectiveSelections?: boolean; + messageIdx?: number; + }) => { + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const modelReferenceService = accessor.get("IVoidModelService"); // state for tracking prospective files - const { uri: currentURI } = useActiveURI() - const [recentUris, setRecentUris] = useState([]) - const maxRecentUris = 10 - const maxProspectiveFiles = 3 - useEffect(() => { // handle recent files - if (!currentURI) return - setRecentUris(prev => { - const withoutCurrent = prev.filter(uri => uri.fsPath !== currentURI.fsPath) // remove duplicates - const withCurrent = [currentURI, ...withoutCurrent] - return withCurrent.slice(0, maxRecentUris) - }) - }, [currentURI]) - const [prospectiveSelections, setProspectiveSelections] = useState([]) - + const { uri: currentURI } = useActiveURI(); + const [recentUris, setRecentUris] = useState([]); + const maxRecentUris = 10; + const maxProspectiveFiles = 3; + useEffect(() => { + // handle recent files + if (!currentURI) return; + setRecentUris((prev) => { + const withoutCurrent = prev.filter( + (uri) => uri.fsPath !== currentURI.fsPath + ); // remove duplicates + const withCurrent = [currentURI, ...withoutCurrent]; + return withCurrent.slice(0, maxRecentUris); + }); + }, [currentURI]); + const [prospectiveSelections, setProspectiveSelections] = useState< + StagingSelectionItem[] + >([]); // handle prospective files useEffect(() => { const computeRecents = async () => { const prospectiveURIs = recentUris - .filter(uri => !selections.find(s => s.type === 'File' && s.uri.fsPath === uri.fsPath)) - .slice(0, maxProspectiveFiles) + .filter( + (uri) => + !selections.find( + (s) => s.type === "File" && s.uri.fsPath === uri.fsPath + ) + ) + .slice(0, maxProspectiveFiles); - const answer: StagingSelectionItem[] = [] + const answer: StagingSelectionItem[] = []; for (const uri of prospectiveURIs) { answer.push({ - type: 'File', + type: "File", uri: uri, - language: (await modelReferenceService.getModelSafe(uri)).model?.getLanguageId() || 'plaintext', + language: + ( + await modelReferenceService.getModelSafe(uri) + ).model?.getLanguageId() || "plaintext", state: { wasAddedAsCurrentFile: false }, - }) + }); } - return answer - } + return answer; + }; // add a prospective file if type === 'staging' and if the user is in a file, and if the file is not selected yet - if (type === 'staging' && showProspectiveSelections) { - computeRecents().then((a) => setProspectiveSelections(a)) - } - else { - setProspectiveSelections([]) + if (type === "staging" && showProspectiveSelections) { + computeRecents().then((a) => setProspectiveSelections(a)); + } else { + setProspectiveSelections([]); } - }, [recentUris, selections, type, showProspectiveSelections]) + }, [recentUris, selections, type, showProspectiveSelections]); - - const allSelections = [...selections, ...prospectiveSelections] + const allSelections = [...selections, ...prospectiveSelections]; if (allSelections.length === 0) { - return null + return null; } return ( -
- +
{allSelections.map((selection, i) => { - - const isThisSelectionProspective = i > selections.length - 1 - - const thisKey = selection.type === 'CodeSelection' ? selection.type + selection.language + selection.range + selection.state.wasAddedAsCurrentFile + selection.uri.fsPath - : selection.type === 'File' ? selection.type + selection.language + selection.state.wasAddedAsCurrentFile + selection.uri.fsPath - : selection.type === 'Folder' ? selection.type + selection.language + selection.state + selection.uri.fsPath - : i - - const SelectionIcon = ( - selection.type === 'File' ? File - : selection.type === 'Folder' ? Folder - : selection.type === 'CodeSelection' ? Text - : (undefined as never) - ) - - return
- {/* summarybox */} -
selections.length - 1; + + const thisKey = + selection.type === "CodeSelection" + ? selection.type + + selection.language + + selection.range + + selection.state.wasAddedAsCurrentFile + + selection.uri.fsPath + : selection.type === "File" + ? selection.type + + selection.language + + selection.state.wasAddedAsCurrentFile + + selection.uri.fsPath + : selection.type === "Folder" + ? selection.type + + selection.language + + selection.state + + selection.uri.fsPath + : i; + + const SelectionIcon = + selection.type === "File" + ? File + : selection.type === "Folder" + ? Folder + : selection.type === "CodeSelection" + ? Text + : (undefined as never); + + return ( +
+ {/* summarybox */} +
{ - if (type !== 'staging') return; // (never) - if (isThisSelectionProspective) { // add prospective selection to selections - setSelections([...selections, selection]) - } - else if (selection.type === 'File') { // open files - voidOpenFileFn(selection.uri, accessor); - - const wasAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile - if (wasAddedAsCurrentFile) { - // make it so the file is added permanently, not just as the current file - const newSelection: StagingSelectionItem = { ...selection, state: { ...selection.state, wasAddedAsCurrentFile: false } } - setSelections([ - ...selections.slice(0, i), - newSelection, - ...selections.slice(i + 1) - ]) + onClick={() => { + if (type !== "staging") return; // (never) + if (isThisSelectionProspective) { + // add prospective selection to selections + setSelections([...selections, selection]); + } else if (selection.type === "File") { + // open files + voidOpenFileFn(selection.uri, accessor); + + const wasAddedAsCurrentFile = + selection.state.wasAddedAsCurrentFile; + if (wasAddedAsCurrentFile) { + // make it so the file is added permanently, not just as the current file + const newSelection: StagingSelectionItem = { + ...selection, + state: { + ...selection.state, + wasAddedAsCurrentFile: false, + }, + }; + setSelections([ + ...selections.slice(0, i), + newSelection, + ...selections.slice(i + 1), + ]); + } + } else if (selection.type === "CodeSelection") { + voidOpenFileFn(selection.uri, accessor, selection.range); + } else if (selection.type === "Folder") { + // TODO!!! reveal in tree } + }} + > + {} + + { + // file name and range + getBasename(selection.uri.fsPath) + + (selection.type === "CodeSelection" + ? ` (${selection.range[0]}-${selection.range[1]})` + : "") } - else if (selection.type === 'CodeSelection') { - voidOpenFileFn(selection.uri, accessor, selection.range); - } - else if (selection.type === 'Folder') { - // TODO!!! reveal in tree - } - }} - > - {} - - { // file name and range - getBasename(selection.uri.fsPath) - + (selection.type === 'CodeSelection' ? ` (${selection.range[0]}-${selection.range[1]})` : '') - } - - {selection.type === 'File' && selection.state.wasAddedAsCurrentFile && messageIdx === undefined && currentURI?.fsPath === selection.uri.fsPath ? - - {`(Current File)`} - - : null - } - {type === 'staging' && !isThisSelectionProspective ? // X button -
{ - e.stopPropagation(); // don't open/close selection - if (type !== 'staging') return; - setSelections([...selections.slice(0, i), ...selections.slice(i + 1)]) - }} - > - -
- : <> - } + {selection.type === "File" && + selection.state.wasAddedAsCurrentFile && + messageIdx === undefined && + currentURI?.fsPath === selection.uri.fsPath ? ( + + {`(Current File)`} + + ) : null} + + {type === "staging" && !isThisSelectionProspective ? ( // X button +
{ + e.stopPropagation(); // don't open/close selection + if (type !== "staging") return; + setSelections([ + ...selections.slice(0, i), + ...selections.slice(i + 1), + ]); + }} + > + +
+ ) : ( + <> + )} +
-
- + ); })} - -
- - ) -} - + ); +}; type ToolHeaderParams = { - icon?: React.ReactNode; title: React.ReactNode; desc1: React.ReactNode; desc1OnClick?: () => void; @@ -768,10 +955,9 @@ type ToolHeaderParams = { desc2OnClick?: () => void; isOpen?: boolean; className?: string; -} +}; const ToolHeaderWrapper = ({ - icon, title, desc1, desc1OnClick, @@ -789,440 +975,514 @@ const ToolHeaderWrapper = ({ isRejected, className, // applies to the main content }: ToolHeaderParams) => { - const [isOpen_, setIsOpen] = useState(false); - const isExpanded = isOpen !== undefined ? isOpen : isOpen_ + const isExpanded = isOpen !== undefined ? isOpen : isOpen_; - const isDropdown = children !== undefined // null ALLOWS dropdown - const isClickable = !!(isDropdown || onClick) + const isDropdown = children !== undefined; // null ALLOWS dropdown + const isClickable = !!(isDropdown || onClick); - const isDesc1Clickable = !!desc1OnClick + const isDesc1Clickable = !!desc1OnClick; - const desc1HTML = {desc1} - - return (
-
- {/* header */} -
-
- {/* left */} -
+ {desc1} + + ); + + return ( +
+
+ {/* header */} +
+
- {/* title eg "> Edited File" */} -
{ - if (isDropdown) { setIsOpen(v => !v); } - if (onClick) { onClick(); } - }} + {/* left */} +
- {isDropdown && ( Edited File" */} +
{ + if (isDropdown) { + setIsOpen((v) => !v); + } + if (onClick) { + onClick(); + } + }} + > + {isDropdown && ( + )} - {title} + /> + )} + {title} - {!isDesc1Clickable && desc1HTML} + {!isDesc1Clickable && desc1HTML} +
+ {isDesc1Clickable && desc1HTML}
- {isDesc1Clickable && desc1HTML} -
- {/* right */} -
- - {info && } - - {isError && } - {isRejected && } - {desc2 && - {desc2} - } - {numResults !== undefined && ( - - {`${numResults}${hasNextPage ? '+' : ''} result${numResults !== 1 ? 's' : ''}`} - - )} + {/* right */} +
+ {info && ( + + )} + + {isError && ( + + )} + {isRejected && ( + + )} + {desc2 && ( + + {desc2} + + )} + {numResults !== undefined && ( + + {`${numResults}${hasNextPage ? "+" : ""} result${ + numResults !== 1 ? "s" : "" + }`} + + )} +
-
- {/* children */} - {
- {children} -
} + > + {children} +
+ } +
+ {bottomChildren}
- {bottomChildren} -
); + ); }; +const EditTool = ({ + toolMessage, + threadId, + messageIdx, + content, +}: Parameters>[0] & { + content: string; +}) => { + const accessor = useAccessor(); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); -const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters>[0] & { content: string }) => { - const accessor = useAccessor() - const isError = false - const isRejected = toolMessage.type === 'rejected' - - const title = getTitle(toolMessage) - - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null - - const { rawParams, params, name } = toolMessage - const desc1OnClick = () => voidOpenFileFn(params.uri, accessor) - const componentParams: ToolHeaderParams = { title, desc1, desc1OnClick, desc1Info, isError, icon, isRejected, } - + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + const icon = null; + + const { rawParams, params, name } = toolMessage; + const desc1OnClick = () => voidOpenFileFn(params.uri, accessor); + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1OnClick, + desc1Info, + isError, + isRejected, + }; - const editToolType = toolMessage.name === 'edit_file' ? 'diff' : 'rewrite' - if (toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') { - componentParams.children = - - + const editToolType = toolMessage.name === "edit_file" ? "diff" : "rewrite"; + if ( + toolMessage.type === "running_now" || + toolMessage.type === "tool_request" + ) { + componentParams.children = ( + + + + ); // JumpToFileButton removed in favor of FileLinkText - } - else if (toolMessage.type === 'success' || toolMessage.type === 'rejected' || toolMessage.type === 'tool_error') { + } else if ( + toolMessage.type === "success" || + toolMessage.type === "rejected" || + toolMessage.type === "tool_error" + ) { // add apply box const applyBoxId = getApplyBoxId({ threadId: threadId, messageIdx: messageIdx, - tokenIdx: 'N/A', - }) - componentParams.desc2 = - - // add children - componentParams.children = - - + ); - if (toolMessage.type === 'success' || toolMessage.type === 'rejected') { - const { result } = toolMessage - componentParams.bottomChildren = - {result?.lintErrors?.map((error, i) => ( -
Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}
- ))} -
- } - else if (toolMessage.type === 'tool_error') { + // add children + componentParams.children = ( + + + + ); + + if (toolMessage.type === "success" || toolMessage.type === "rejected") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result?.lintErrors?.map((error, i) => ( +
+ Lines {error.startLineNumber}-{error.endLineNumber}:{" "} + {error.message} +
+ ))} +
+ ); + } else if (toolMessage.type === "tool_error") { // error - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } } - return -} + return ; +}; -const SimplifiedToolHeader = ({ - title, - children, +const UserMessageComponent = ({ + chatMessage, + messageIdx, + isCheckpointGhost, + currCheckpointIdx, + _scrollToBottom, }: { - title: string; - children?: React.ReactNode; + chatMessage: ChatMessage & { role: "user" }; + messageIdx: number; + currCheckpointIdx: number | undefined; + isCheckpointGhost: boolean; + _scrollToBottom: (() => void) | null; }) => { - const [isOpen, setIsOpen] = useState(false); - const isDropdown = children !== undefined; - return ( -
-
- {/* header */} -
{ - if (isDropdown) { setIsOpen(v => !v); } - }} - > - {isDropdown && ( - - )} -
- {title} -
-
- {/* children */} - {
- {children} -
} -
-
- ); -}; - - - - -const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, currCheckpointIdx, _scrollToBottom }: { chatMessage: ChatMessage & { role: 'user' }, messageIdx: number, currCheckpointIdx: number | undefined, isCheckpointGhost: boolean, _scrollToBottom: (() => void) | null }) => { - - const accessor = useAccessor() - const chatThreadsService = accessor.get('IChatThreadService') + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); // global state - let isBeingEdited = false - let stagingSelections: StagingSelectionItem[] = [] - let setIsBeingEdited = (_: boolean) => { } - let setStagingSelections = (_: StagingSelectionItem[]) => { } + let isBeingEdited = false; + let stagingSelections: StagingSelectionItem[] = []; + let setIsBeingEdited = (_: boolean) => {}; + let setStagingSelections = (_: StagingSelectionItem[]) => {}; if (messageIdx !== undefined) { - const _state = chatThreadsService.getCurrentMessageState(messageIdx) - isBeingEdited = _state.isBeingEdited - stagingSelections = _state.stagingSelections - setIsBeingEdited = (v) => chatThreadsService.setCurrentMessageState(messageIdx, { isBeingEdited: v }) - setStagingSelections = (s) => chatThreadsService.setCurrentMessageState(messageIdx, { stagingSelections: s }) + const _state = chatThreadsService.getCurrentMessageState(messageIdx); + isBeingEdited = _state.isBeingEdited; + stagingSelections = _state.stagingSelections; + setIsBeingEdited = (v) => + chatThreadsService.setCurrentMessageState(messageIdx, { + isBeingEdited: v, + }); + setStagingSelections = (s) => + chatThreadsService.setCurrentMessageState(messageIdx, { + stagingSelections: s, + }); } - // local state - const mode: ChatBubbleMode = isBeingEdited ? 'edit' : 'display' - const [isFocused, setIsFocused] = useState(false) - const [isHovered, setIsHovered] = useState(false) - const [isDisabled, setIsDisabled] = useState(false) - const [textAreaRefState, setTextAreaRef] = useState(null) - const textAreaFnsRef = useRef(null) + const mode: ChatBubbleMode = isBeingEdited ? "edit" : "display"; + const [isFocused, setIsFocused] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const [isDisabled, setIsDisabled] = useState(false); + const [textAreaRefState, setTextAreaRef] = + useState(null); + const textAreaFnsRef = useRef(null); // initialize on first render, and when edit was just enabled - const _mustInitialize = useRef(true) - const _justEnabledEdit = useRef(false) + const _mustInitialize = useRef(true); + const _justEnabledEdit = useRef(false); useEffect(() => { - const canInitialize = mode === 'edit' && textAreaRefState - const shouldInitialize = _justEnabledEdit.current || _mustInitialize.current + const canInitialize = mode === "edit" && textAreaRefState; + const shouldInitialize = + _justEnabledEdit.current || _mustInitialize.current; if (canInitialize && shouldInitialize) { setStagingSelections( - (chatMessage.selections || []).map(s => { // quick hack so we dont have to do anything more - if (s.type === 'File') return { ...s, state: { ...s.state, wasAddedAsCurrentFile: false, } } - else return s + (chatMessage.selections || []).map((s) => { + // quick hack so we dont have to do anything more + if (s.type === "File") + return { + ...s, + state: { ...s.state, wasAddedAsCurrentFile: false }, + }; + else return s; }) - ) + ); if (textAreaFnsRef.current) - textAreaFnsRef.current.setValue(chatMessage.displayContent || '') + textAreaFnsRef.current.setValue(chatMessage.displayContent || ""); textAreaRefState.focus(); - _justEnabledEdit.current = false - _mustInitialize.current = false + _justEnabledEdit.current = false; + _mustInitialize.current = false; } - - }, [chatMessage, mode, _justEnabledEdit, textAreaRefState, textAreaFnsRef.current, _justEnabledEdit.current, _mustInitialize.current]) + }, [ + chatMessage, + mode, + _justEnabledEdit, + textAreaRefState, + textAreaFnsRef.current, + _justEnabledEdit.current, + _mustInitialize.current, + ]); const onOpenEdit = () => { - setIsBeingEdited(true) - chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx) - _justEnabledEdit.current = true - } + setIsBeingEdited(true); + chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx); + _justEnabledEdit.current = true; + }; const onCloseEdit = () => { - setIsFocused(false) - setIsHovered(false) - setIsBeingEdited(false) - chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) - - } - - const EditSymbol = mode === 'display' ? Pencil : X - + setIsFocused(false); + setIsHovered(false); + setIsBeingEdited(false); + chatThreadsService.setCurrentlyFocusedMessageIdx(undefined); + }; - let chatbubbleContents: React.ReactNode - if (mode === 'display') { - chatbubbleContents = <> - - {chatMessage.displayContent} - - } - else if (mode === 'edit') { + const EditSymbol = mode === "display" ? Pencil : X; + let chatbubbleContents: React.ReactNode; + if (mode === "display") { + chatbubbleContents = ( + <> + + {chatMessage.displayContent} + + ); + } else if (mode === "edit") { const onSubmit = async () => { - if (isDisabled) return; if (!textAreaRefState) return; if (messageIdx === undefined) return; // cancel any streams on this thread - const threadId = chatThreadsService.state.currentThreadId + const threadId = chatThreadsService.state.currentThreadId; - await chatThreadsService.abortRunning(threadId) + await chatThreadsService.abortRunning(threadId); // update state - setIsBeingEdited(false) - chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) + setIsBeingEdited(false); + chatThreadsService.setCurrentlyFocusedMessageIdx(undefined); // stream the edit const userMessage = textAreaRefState.value; try { - await chatThreadsService.editUserMessageAndStreamResponse({ userMessage, messageIdx, threadId }) + await chatThreadsService.editUserMessageAndStreamResponse({ + userMessage, + messageIdx, + threadId, + }); } catch (e) { - console.error('Error while editing message:', e) + console.error("Error while editing message:", e); } - await chatThreadsService.focusCurrentChat() - requestAnimationFrame(() => _scrollToBottom?.()) - } + await chatThreadsService.focusCurrentChat(); + requestAnimationFrame(() => _scrollToBottom?.()); + }; const onAbort = async () => { - const threadId = chatThreadsService.state.currentThreadId - await chatThreadsService.abortRunning(threadId) - } + const threadId = chatThreadsService.state.currentThreadId; + await chatThreadsService.abortRunning(threadId); + }; const onKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - onCloseEdit() + if (e.key === "Escape") { + onCloseEdit(); } - if (e.key === 'Enter' && !e.shiftKey) { - onSubmit() + if (e.key === "Enter" && !e.shiftKey) { + onSubmit(); } - } + }; - if (!chatMessage.content) { // don't show if empty and not loading (if loading, want to show). - return null + if (!chatMessage.content) { + // don't show if empty and not loading (if loading, want to show). + return null; } - chatbubbleContents = - setIsDisabled(!text)} - onFocus={() => { - setIsFocused(true) - chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx); - }} - onBlur={() => { - setIsFocused(false) - }} - onKeyDown={onKeyDown} - fnsRef={textAreaFnsRef} - multiline={true} - /> - + chatbubbleContents = ( + + setIsDisabled(!text)} + onFocus={() => { + setIsFocused(true); + chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx); + }} + onBlur={() => { + setIsFocused(false); + }} + onKeyDown={onKeyDown} + fnsRef={textAreaFnsRef} + multiline={true} + /> + + ); } - const isMsgAfterCheckpoint = currCheckpointIdx !== undefined && currCheckpointIdx === messageIdx - 1 + const isMsgAfterCheckpoint = + currCheckpointIdx !== undefined && currCheckpointIdx === messageIdx - 1; - return
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > + return (
{ if (mode === 'display') { onOpenEdit() } }} - > - {chatbubbleContents} -
- - -
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} > - { + if (mode === "display") { + onOpenEdit(); + } + }} + > + {chatbubbleContents} +
+ +
+ { - if (mode === 'display') { - onOpenEdit() - } else if (mode === 'edit') { - onCloseEdit() - } - }} - /> + onClick={() => { + if (mode === "display") { + onOpenEdit(); + } else if (mode === "edit") { + onCloseEdit(); + } + }} + /> +
- - -
- -} + ); +}; const SmallProseWrapper = ({ children }: { children: React.ReactNode }) => { - return
- {children} -
-} +" + > + {children} +
+ ); +}; const ProseWrapper = ({ children }: { children: React.ReactNode }) => { - return
- {children} -
-} -const AssistantMessageComponent = ({ chatMessage, isCheckpointGhost, isCommitted, messageIdx }: { chatMessage: ChatMessage & { role: 'assistant' }, isCheckpointGhost: boolean, messageIdx: number, isCommitted: boolean }) => { - - const accessor = useAccessor() - const chatThreadsService = accessor.get('IChatThreadService') - - const reasoningStr = chatMessage.reasoning?.trim() || null - const hasReasoning = !!reasoningStr - const isDoneReasoning = !!chatMessage.displayContent - const thread = chatThreadsService.getCurrentThread() +" + > + {children} +
+ ); +}; +const AssistantMessageComponent = ({ + chatMessage, + isCheckpointGhost, + isCommitted, + messageIdx, +}: { + chatMessage: ChatMessage & { role: "assistant" }; + isCheckpointGhost: boolean; + messageIdx: number; + isCommitted: boolean; +}) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + const reasoningStr = chatMessage.reasoning?.trim() || null; + const hasReasoning = !!reasoningStr; + const isDoneReasoning = !!chatMessage.displayContent; + const thread = chatThreadsService.getCurrentThread(); const chatMessageLocation: ChatMessageLocation = { threadId: thread.id, messageIdx: messageIdx, - } + }; + + const isEmpty = !chatMessage.displayContent && !chatMessage.reasoning; + if (isEmpty) return null; - const isEmpty = !chatMessage.displayContent && !chatMessage.reasoning - if (isEmpty) return null + return ( + <> + {/* reasoning token */} + {hasReasoning && ( +
+ + + + + +
+ )} - return <> - {/* reasoning token */} - {hasReasoning && -
- - + {/* assistant message */} + {chatMessage.displayContent && ( +
+ - - -
- } - - {/* assistant message */} - {chatMessage.displayContent && -
- - - -
- } - - -} + +
+ )} + + ); +}; -const ReasoningWrapper = ({ isDoneReasoning, isStreaming, children }: { isDoneReasoning: boolean, isStreaming: boolean, children: React.ReactNode }) => { - const isDone = isDoneReasoning || !isStreaming - const isWriting = !isDone - const [isOpen, setIsOpen] = useState(isWriting) +const ReasoningWrapper = ({ + isDoneReasoning, + isStreaming, + children, +}: { + isDoneReasoning: boolean; + isStreaming: boolean; + children: React.ReactNode; +}) => { + const isDone = isDoneReasoning || !isStreaming; + const isWriting = !isDone; + const [isOpen, setIsOpen] = useState(isWriting); useEffect(() => { - if (!isWriting) setIsOpen(false) // if just finished reasoning, close - }, [isWriting]) - return : ''} isOpen={isOpen} onClick={() => setIsOpen(v => !v)}> - -
- {children} -
-
-
-} - - - + if (!isWriting) setIsOpen(false); // if just finished reasoning, close + }, [isWriting]); + return ( + : ""} + isOpen={isOpen} + onClick={() => setIsOpen((v) => !v)} + > + +
{children}
+
+
+ ); +}; // should either be past or "-ing" tense, not present tense. Eg. when the LLM searches for something, the user expects it to say "I searched for X" or "I am searching for X". Not "I search X". const loadingTitleWrapper = (item: React.ReactNode): React.ReactNode => { - return - {item} - - -} + return ( + + {item} + + + ); +}; const titleOfBuiltinToolName = { - 'read_file': { done: 'Read file', proposed: 'Read file', running: loadingTitleWrapper('Reading file') }, - 'ls_dir': { done: 'Inspected folder', proposed: 'Inspect folder', running: loadingTitleWrapper('Inspecting folder') }, - 'get_dir_tree': { done: 'Inspected folder tree', proposed: 'Inspect folder tree', running: loadingTitleWrapper('Inspecting folder tree') }, - 'search_pathnames_only': { done: 'Searched by file name', proposed: 'Search by file name', running: loadingTitleWrapper('Searching by file name') }, - 'search_for_files': { done: 'Searched', proposed: 'Search', running: loadingTitleWrapper('Searching') }, - 'create_file_or_folder': { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) }, - 'delete_file_or_folder': { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) }, - 'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') }, - 'rewrite_file': { done: `Wrote file`, proposed: 'Write file', running: loadingTitleWrapper('Writing file') }, - 'run_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') }, - 'run_persistent_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') }, - - 'open_persistent_terminal': { done: `Opened terminal`, proposed: 'Open terminal', running: loadingTitleWrapper('Opening terminal') }, - 'kill_persistent_terminal': { done: `Killed terminal`, proposed: 'Kill terminal', running: loadingTitleWrapper('Killing terminal') }, - - 'read_lint_errors': { done: `Read lint errors`, proposed: 'Read lint errors', running: loadingTitleWrapper('Reading lint errors') }, - 'search_in_file': { done: 'Searched in file', proposed: 'Search in file', running: loadingTitleWrapper('Searching in file') }, -} as const satisfies Record + read_file: { + done: "Read file", + proposed: "Read file", + running: loadingTitleWrapper("Reading file"), + }, + ls_dir: { + done: "Inspected folder", + proposed: "Inspect folder", + running: loadingTitleWrapper("Inspecting folder"), + }, + get_dir_tree: { + done: "Inspected folder tree", + proposed: "Inspect folder tree", + running: loadingTitleWrapper("Inspecting folder tree"), + }, + search_pathnames_only: { + done: "Searched by file name", + proposed: "Search by file name", + running: loadingTitleWrapper("Searching by file name"), + }, + search_for_files: { + done: "Searched", + proposed: "Search", + running: loadingTitleWrapper("Searching"), + }, + create_file_or_folder: { + done: `Created`, + proposed: `Create`, + running: loadingTitleWrapper(`Creating`), + }, + delete_file_or_folder: { + done: `Deleted`, + proposed: `Delete`, + running: loadingTitleWrapper(`Deleting`), + }, + edit_file: { + done: `Edited file`, + proposed: "Edit file", + running: loadingTitleWrapper("Editing file"), + }, + rewrite_file: { + done: `Wrote file`, + proposed: "Write file", + running: loadingTitleWrapper("Writing file"), + }, + run_command: { + done: `Ran terminal`, + proposed: "Run terminal", + running: loadingTitleWrapper("Running terminal"), + }, + run_persistent_command: { + done: `Ran terminal`, + proposed: "Run terminal", + running: loadingTitleWrapper("Running terminal"), + }, + open_persistent_terminal: { + done: `Opened terminal`, + proposed: "Open terminal", + running: loadingTitleWrapper("Opening terminal"), + }, + kill_persistent_terminal: { + done: `Killed terminal`, + proposed: "Kill terminal", + running: loadingTitleWrapper("Killing terminal"), + }, -const getTitle = (toolMessage: Pick): React.ReactNode => { - const t = toolMessage + read_lint_errors: { + done: `Read lint errors`, + proposed: "Read lint errors", + running: loadingTitleWrapper("Reading lint errors"), + }, + search_in_file: { + done: "Searched in file", + proposed: "Search in file", + running: loadingTitleWrapper("Searching in file"), + }, +} as const satisfies Record< + BuiltinToolName, + { done: any; proposed: any; running: any } +>; + +const getTitle = ( + toolMessage: Pick< + ChatMessage & { role: "tool" }, + "name" | "type" | "mcpServerName" + > +): React.ReactNode => { + const t = toolMessage; // non-built-in title if (!builtinToolNames.includes(t.name as BuiltinToolName)) { // descriptor of Running or Ran etc const descriptor = - t.type === 'success' ? 'Called' - : t.type === 'running_now' ? 'Calling' - : t.type === 'tool_request' ? 'Call' - : t.type === 'rejected' ? 'Call' - : t.type === 'invalid_params' ? 'Call' - : t.type === 'tool_error' ? 'Call' - : 'Call' - - - const title = `${descriptor} ${toolMessage.mcpServerName || 'MCP'}` - if (t.type === 'running_now' || t.type === 'tool_request') - return loadingTitleWrapper(title) - return title + t.type === "success" + ? "Called" + : t.type === "running_now" + ? "Calling" + : t.type === "tool_request" + ? "Call" + : t.type === "rejected" + ? "Call" + : t.type === "invalid_params" + ? "Call" + : t.type === "tool_error" + ? "Call" + : "Call"; + + const title = `${descriptor} ${toolMessage.mcpServerName || "MCP"}`; + if (t.type === "running_now" || t.type === "tool_request") + return loadingTitleWrapper(title); + return title; } // built-in title else { - const toolName = t.name as BuiltinToolName - if (t.type === 'success') return titleOfBuiltinToolName[toolName].done - if (t.type === 'running_now') return titleOfBuiltinToolName[toolName].running - return titleOfBuiltinToolName[toolName].proposed + const toolName = t.name as BuiltinToolName; + if (t.type === "success") return titleOfBuiltinToolName[toolName].done; + if (t.type === "running_now") + return titleOfBuiltinToolName[toolName].running; + return titleOfBuiltinToolName[toolName].proposed; } -} - +}; -const toolNameToDesc = (toolName: BuiltinToolName, _toolParams: BuiltinToolCallParams[BuiltinToolName] | undefined, accessor: ReturnType): { - desc1: React.ReactNode, - desc1Info?: string, +const toolNameToDesc = ( + toolName: BuiltinToolName, + _toolParams: BuiltinToolCallParams[BuiltinToolName] | undefined, + accessor: ReturnType +): { + desc1: React.ReactNode; + desc1Info?: string; } => { - if (!_toolParams) { - return { desc1: '', }; + return { desc1: "" }; } const x = { - 'read_file': () => { - const toolParams = _toolParams as BuiltinToolCallParams['read_file'] + read_file: () => { + const toolParams = _toolParams as BuiltinToolCallParams["read_file"]; return { desc1: getBasename(toolParams.uri.fsPath), desc1Info: getRelative(toolParams.uri, accessor), }; }, - 'ls_dir': () => { - const toolParams = _toolParams as BuiltinToolCallParams['ls_dir'] + ls_dir: () => { + const toolParams = _toolParams as BuiltinToolCallParams["ls_dir"]; return { desc1: getFolderName(toolParams.uri.fsPath), desc1Info: getRelative(toolParams.uri, accessor), }; }, - 'search_pathnames_only': () => { - const toolParams = _toolParams as BuiltinToolCallParams['search_pathnames_only'] + search_pathnames_only: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["search_pathnames_only"]; return { desc1: `"${toolParams.query}"`, - } + }; }, - 'search_for_files': () => { - const toolParams = _toolParams as BuiltinToolCallParams['search_for_files'] + search_for_files: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["search_for_files"]; return { desc1: `"${toolParams.query}"`, - } + }; }, - 'search_in_file': () => { - const toolParams = _toolParams as BuiltinToolCallParams['search_in_file']; + search_in_file: () => { + const toolParams = _toolParams as BuiltinToolCallParams["search_in_file"]; return { desc1: `"${toolParams.query}"`, desc1Info: getRelative(toolParams.uri, accessor), }; }, - 'create_file_or_folder': () => { - const toolParams = _toolParams as BuiltinToolCallParams['create_file_or_folder'] + create_file_or_folder: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["create_file_or_folder"]; return { - desc1: toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) ?? '/' : getBasename(toolParams.uri.fsPath), + desc1: toolParams.isFolder + ? getFolderName(toolParams.uri.fsPath) ?? "/" + : getBasename(toolParams.uri.fsPath), desc1Info: getRelative(toolParams.uri, accessor), - } + }; }, - 'delete_file_or_folder': () => { - const toolParams = _toolParams as BuiltinToolCallParams['delete_file_or_folder'] + delete_file_or_folder: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["delete_file_or_folder"]; return { - desc1: toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) ?? '/' : getBasename(toolParams.uri.fsPath), + desc1: toolParams.isFolder + ? getFolderName(toolParams.uri.fsPath) ?? "/" + : getBasename(toolParams.uri.fsPath), desc1Info: getRelative(toolParams.uri, accessor), - } + }; }, - 'rewrite_file': () => { - const toolParams = _toolParams as BuiltinToolCallParams['rewrite_file'] + rewrite_file: () => { + const toolParams = _toolParams as BuiltinToolCallParams["rewrite_file"]; return { desc1: getBasename(toolParams.uri.fsPath), desc1Info: getRelative(toolParams.uri, accessor), - } + }; }, - 'edit_file': () => { - const toolParams = _toolParams as BuiltinToolCallParams['edit_file'] + edit_file: () => { + const toolParams = _toolParams as BuiltinToolCallParams["edit_file"]; return { desc1: getBasename(toolParams.uri.fsPath), desc1Info: getRelative(toolParams.uri, accessor), - } + }; }, - 'run_command': () => { - const toolParams = _toolParams as BuiltinToolCallParams['run_command'] + run_command: () => { + const toolParams = _toolParams as BuiltinToolCallParams["run_command"]; return { desc1: `"${toolParams.command}"`, - } + }; }, - 'run_persistent_command': () => { - const toolParams = _toolParams as BuiltinToolCallParams['run_persistent_command'] + run_persistent_command: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["run_persistent_command"]; return { desc1: `"${toolParams.command}"`, - } + }; }, - 'open_persistent_terminal': () => { - const toolParams = _toolParams as BuiltinToolCallParams['open_persistent_terminal'] - return { desc1: '' } + open_persistent_terminal: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["open_persistent_terminal"]; + return { desc1: "" }; }, - 'kill_persistent_terminal': () => { - const toolParams = _toolParams as BuiltinToolCallParams['kill_persistent_terminal'] - return { desc1: toolParams.persistentTerminalId } + kill_persistent_terminal: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["kill_persistent_terminal"]; + return { desc1: toolParams.persistentTerminalId }; }, - 'get_dir_tree': () => { - const toolParams = _toolParams as BuiltinToolCallParams['get_dir_tree'] + get_dir_tree: () => { + const toolParams = _toolParams as BuiltinToolCallParams["get_dir_tree"]; return { - desc1: getFolderName(toolParams.uri.fsPath) ?? '/', + desc1: getFolderName(toolParams.uri.fsPath) ?? "/", desc1Info: getRelative(toolParams.uri, accessor), - } + }; }, - 'read_lint_errors': () => { - const toolParams = _toolParams as BuiltinToolCallParams['read_lint_errors'] + read_lint_errors: () => { + const toolParams = + _toolParams as BuiltinToolCallParams["read_lint_errors"]; return { desc1: getBasename(toolParams.uri.fsPath), desc1Info: getRelative(toolParams.uri, accessor), - } - } - } + }; + }, + }; try { - return x[toolName]?.() || { desc1: '' } + return x[toolName]?.() || { desc1: "" }; + } catch { + return { desc1: "" }; } - catch { - return { desc1: '' } - } -} +}; -const ToolRequestAcceptRejectButtons = ({ toolName }: { toolName: ToolName }) => { - const accessor = useAccessor() - const chatThreadsService = accessor.get('IChatThreadService') - const metricsService = accessor.get('IMetricsService') - const voidSettingsService = accessor.get('IVoidSettingsService') - const voidSettingsState = useSettingsState() +const ToolRequestAcceptRejectButtons = ({ + toolName, +}: { + toolName: ToolName; +}) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + const metricsService = accessor.get("IMetricsService"); + const voidSettingsService = accessor.get("IVoidSettingsService"); + const voidSettingsState = useSettingsState(); const onAccept = useCallback(() => { - try { // this doesn't need to be wrapped in try/catch anymore - const threadId = chatThreadsService.state.currentThreadId - chatThreadsService.approveLatestToolRequest(threadId) - metricsService.capture('Tool Request Accepted', {}) - } catch (e) { console.error('Error while approving message in chat:', e) } - }, [chatThreadsService, metricsService]) + try { + // this doesn't need to be wrapped in try/catch anymore + const threadId = chatThreadsService.state.currentThreadId; + chatThreadsService.approveLatestToolRequest(threadId); + metricsService.capture("Tool Request Accepted", {}); + } catch (e) { + console.error("Error while approving message in chat:", e); + } + }, [chatThreadsService, metricsService]); const onReject = useCallback(() => { try { - const threadId = chatThreadsService.state.currentThreadId - chatThreadsService.rejectLatestToolRequest(threadId) - } catch (e) { console.error('Error while approving message in chat:', e) } - metricsService.capture('Tool Request Rejected', {}) - }, [chatThreadsService, metricsService]) + const threadId = chatThreadsService.state.currentThreadId; + chatThreadsService.rejectLatestToolRequest(threadId); + } catch (e) { + console.error("Error while approving message in chat:", e); + } + metricsService.capture("Tool Request Rejected", {}); + }, [chatThreadsService, metricsService]); const approveButton = ( - ) + ); const cancelButton = ( - ) - - const approvalType = isABuiltinToolName(toolName) ? approvalTypeOfBuiltinToolName[toolName] : 'MCP tools' - const approvalToggle = approvalType ?
- -
: null - - return
- {approveButton} - {cancelButton} - {approvalToggle} -
-} + ); -export const ToolChildrenWrapper = ({ children, className }: { children: React.ReactNode, className?: string }) => { - return
-
- {children} -
-
-} -export const CodeChildren = ({ children, className }: { children: React.ReactNode, className?: string }) => { - return
-
- {children} + const approvalType = isABuiltinToolName(toolName) + ? approvalTypeOfBuiltinToolName[toolName] + : "MCP tools"; + const approvalToggle = approvalType ? ( +
+
-
-} - -export const ListableToolItem = ({ name, onClick, isSmall, className, showDot }: { name: React.ReactNode, onClick?: () => void, isSmall?: boolean, className?: string, showDot?: boolean }) => { - return
- {showDot === false ? null :
} -
{name}
-
-} - - - -const EditToolChildren = ({ uri, code, type }: { uri: URI | undefined, code: string, type: 'diff' | 'rewrite' }) => { - - const content = type === 'diff' ? - - : - - return
- - {content} - -
- -} + ) : null; + return ( +
+ {approveButton} + {cancelButton} + {approvalToggle} +
+ ); +}; -const LintErrorChildren = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => { - return
- {lintErrors.map((error, i) => ( -
Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}
- ))} -
-} +export const ToolChildrenWrapper = ({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) => { + return ( +
+
{children}
+
+ ); +}; +export const CodeChildren = ({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) => { + return ( +
+
{children}
+
+ ); +}; + +export const ListableToolItem = ({ + name, + onClick, + isSmall, + className, + showDot, +}: { + name: React.ReactNode; + onClick?: () => void; + isSmall?: boolean; + className?: string; + showDot?: boolean; +}) => { + return ( +
+ {showDot === false ? null : ( +
+ + + +
+ )} +
+ {name} +
+
+ ); +}; -const BottomChildren = ({ children, title }: { children: React.ReactNode, title: string }) => { +const EditToolChildren = ({ + uri, + code, + type, +}: { + uri: URI | undefined; + code: string; + type: "diff" | "rewrite"; +}) => { + const content = + type === "diff" ? ( + + ) : ( + + ); + + return ( +
+ {content} +
+ ); +}; + +const LintErrorChildren = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => { + return ( +
+ {lintErrors.map((error, i) => ( +
+ Lines {error.startLineNumber}-{error.endLineNumber}: {error.message} +
+ ))} +
+ ); +}; + +const BottomChildren = ({ + children, + title, +}: { + children: React.ReactNode; + title: string; +}) => { const [isOpen, setIsOpen] = useState(false); if (!children) return null; return (
setIsOpen(o => !o)} - style={{ background: 'none' }} + onClick={() => setIsOpen((o) => !o)} + style={{ background: "none" }} > - {title} + + {title} +
{children} @@ -1707,914 +2172,1214 @@ const BottomChildren = ({ children, title }: { children: React.ReactNode, title:
); -} - - -const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr, toolName, threadId }: { threadId: string, applyBoxId: string, uri: URI, codeStr: string, toolName: 'edit_file' | 'rewrite_file' }) => { - const { streamState } = useEditToolStreamState({ applyBoxId, uri }) - return
- {/* */} - {/* */} - {streamState === 'idle-no-changes' && } - -
-} - - - -const InvalidTool = ({ toolName, message, mcpServerName }: { toolName: ToolName, message: string, mcpServerName: string | undefined }) => { - const accessor = useAccessor() - const title = getTitle({ name: toolName, type: 'invalid_params', mcpServerName }) - const desc1 = 'Invalid parameters' - const icon = null - const isError = true - const componentParams: ToolHeaderParams = { title, desc1, isError, icon } - - componentParams.children = - - {message} - - - return -} - -const CanceledTool = ({ toolName, mcpServerName }: { toolName: ToolName, mcpServerName: string | undefined }) => { - const accessor = useAccessor() - const title = getTitle({ name: toolName, type: 'rejected', mcpServerName }) - const desc1 = '' - const icon = null - const isRejected = true - const componentParams: ToolHeaderParams = { title, desc1, icon, isRejected } - return -} +}; +const EditToolHeaderButtons = ({ + applyBoxId, + uri, + codeStr, + toolName, + threadId, +}: { + threadId: string; + applyBoxId: string; + uri: URI; + codeStr: string; + toolName: "edit_file" | "rewrite_file"; +}) => { + const { streamState } = useEditToolStreamState({ applyBoxId, uri }); + return ( +
+ {/* */} + {/* */} + {streamState === "idle-no-changes" && ( + + )} + +
+ ); +}; -const CommandTool = ({ toolMessage, type, threadId }: { threadId: string } & ({ - toolMessage: Exclude, { type: 'invalid_params' }> - type: 'run_command' -} | { - toolMessage: Exclude, { type: 'invalid_params' }> - type: | 'run_persistent_command' -})) => { - const accessor = useAccessor() +const InvalidTool = ({ + toolName, + message, + mcpServerName, +}: { + toolName: ToolName; + message: string; + mcpServerName: string | undefined; +}) => { + const title = getTitle({ + name: toolName, + type: "invalid_params", + mcpServerName, + }); + const desc1 = "Invalid parameters"; + const isError = true; + const componentParams: ToolHeaderParams = { title, desc1, isError }; + + componentParams.children = ( + + {message} + + ); + return ; +}; - const commandService = accessor.get('ICommandService') - const terminalToolsService = accessor.get('ITerminalToolService') - const toolsService = accessor.get('IToolsService') - const isError = false - const title = getTitle(toolMessage) - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null - const streamState = useChatThreadsStreamState(threadId) +const CanceledTool = ({ + toolName, + mcpServerName, +}: { + toolName: ToolName; + mcpServerName: string | undefined; +}) => { + const accessor = useAccessor(); + const title = getTitle({ name: toolName, type: "rejected", mcpServerName }); + const desc1 = ""; + const isRejected = true; + const componentParams: ToolHeaderParams = { title, desc1, isRejected }; + return ; +}; - const divRef = useRef(null) +const CommandTool = ({ + toolMessage, + type, + threadId, +}: { threadId: string } & ( + | { + toolMessage: Exclude< + ToolMessage<"run_command">, + { type: "invalid_params" } + >; + type: "run_command"; + } + | { + toolMessage: Exclude< + ToolMessage<"run_persistent_command">, + { type: "invalid_params" } + >; + type: "run_persistent_command"; + } +)) => { + const accessor = useAccessor(); + + const terminalToolsService = accessor.get("ITerminalToolService"); + const toolsService = accessor.get("IToolsService"); + const isError = false; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + const streamState = useChatThreadsStreamState(threadId); - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const divRef = useRef(null); + const isRejected = toolMessage.type === "rejected"; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; const effect = async () => { - if (streamState?.isRunning !== 'tool') return - if (type !== 'run_command' || toolMessage.type !== 'running_now') return; + if (streamState?.isRunning !== "tool") return; + if (type !== "run_command" || toolMessage.type !== "running_now") return; // wait for the interruptor so we know it's running - await streamState?.interrupt + await streamState?.interrupt; const container = divRef.current; if (!container) return; - const terminal = terminalToolsService.getTemporaryTerminal(toolMessage.params.terminalId); + const terminal = terminalToolsService.getTemporaryTerminal( + toolMessage.params.terminalId + ); if (!terminal) return; try { terminal.attachToElement(container); - terminal.setVisible(true) - } catch { - } + terminal.setVisible(true); + } catch {} // Listen for size changes of the container and keep the terminal layout in sync. const resizeObserver = new ResizeObserver((entries) => { const height = entries[0].borderBoxSize[0].blockSize; const width = entries[0].borderBoxSize[0].inlineSize; - if (typeof terminal.layout === 'function') { + if (typeof terminal.layout === "function") { terminal.layout({ width, height }); } }); resizeObserver.observe(container); - return () => { terminal.detachFromElement(); resizeObserver?.disconnect(); } - } + return () => { + terminal.detachFromElement(); + resizeObserver?.disconnect(); + }; + }; useEffect(() => { - effect() + effect(); }, [terminalToolsService, toolMessage, toolMessage.type, type]); - if (toolMessage.type === 'success') { - const { result } = toolMessage + if (toolMessage.type === "success") { + const { result } = toolMessage; // it's unclear that this is a button and not an icon. - // componentParams.desc2 = { terminalToolsService.openTerminal(terminalId) }} - // /> - - let msg: string - if (type === 'run_command') msg = toolsService.stringOfResult['run_command'](toolMessage.params, result) - else msg = toolsService.stringOfResult['run_persistent_command'](toolMessage.params, result) - if (type === 'run_persistent_command') { - componentParams.info = persistentTerminalNameOfId(toolMessage.params.persistentTerminalId) + let msg: string; + if (type === "run_command") + msg = toolsService.stringOfResult["run_command"]( + toolMessage.params, + result + ); + else + msg = toolsService.stringOfResult["run_persistent_command"]( + toolMessage.params, + result + ); + + if (type === "run_persistent_command") { + componentParams.info = persistentTerminalNameOfId( + toolMessage.params.persistentTerminalId + ); } - componentParams.children = -
- -
-
- } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - - } - else if (toolMessage.type === 'running_now') { - if (type === 'run_command') - componentParams.children =
- } - else if (toolMessage.type === 'rejected' || toolMessage.type === 'tool_request') { + componentParams.children = ( + +
+ +
+
+ ); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); + } else if (toolMessage.type === "running_now") { + if (type === "run_command") + componentParams.children = ( +
+ ); + } else if ( + toolMessage.type === "rejected" || + toolMessage.type === "tool_request" + ) { } - return <> - - -} + return ( + <> + + + ); +}; -type WrapperProps = { toolMessage: Exclude, { type: 'invalid_params' }>, messageIdx: number, threadId: string } +type WrapperProps = { + toolMessage: Exclude, { type: "invalid_params" }>; + messageIdx: number; + threadId: string; +}; const MCPToolWrapper = ({ toolMessage }: WrapperProps) => { - const accessor = useAccessor() - const mcpService = accessor.get('IMCPService') - - const title = getTitle(toolMessage) - const desc1 = removeMCPToolNamePrefix(toolMessage.name) - const icon = null - - - if (toolMessage.type === 'running_now') return null // do not show running - - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isRejected, } + const accessor = useAccessor(); + const mcpService = accessor.get("IMCPService"); + + const title = getTitle(toolMessage); + const desc1 = removeMCPToolNamePrefix(toolMessage.name); + + if (toolMessage.type === "running_now") return null; // do not show running + + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + isError, + isRejected, + }; - const paramsStr = JSON.stringify(params, null, 2) - componentParams.desc2 = + const paramsStr = JSON.stringify(params, null, 2); + componentParams.desc2 = ( + + ); - componentParams.info = !toolMessage.mcpServerName ? 'MCP tool not found' : undefined + componentParams.info = !toolMessage.mcpServerName + ? "MCP tool not found" + : undefined; // Add copy inputs button in desc2 - - if (toolMessage.type === 'success' || toolMessage.type === 'tool_request') { - const { result } = toolMessage - const resultStr = result ? mcpService.stringifyResult(result) : 'null' - componentParams.children = - - - - - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + if (toolMessage.type === "success" || toolMessage.type === "tool_request") { + const { result } = toolMessage; + const resultStr = result ? mcpService.stringifyResult(result) : "null"; + componentParams.children = ( + + + + + + ); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } - return - -} + return ; +}; -type ResultWrapper = (props: WrapperProps) => React.ReactNode +type ResultWrapper = ( + props: WrapperProps +) => React.ReactNode; -const builtinToolNameToComponent: { [T in BuiltinToolName]: { resultWrapper: ResultWrapper, } } = { - 'read_file': { +const builtinToolNameToComponent: { + [T in BuiltinToolName]: { resultWrapper: ResultWrapper }; +} = { + read_file: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - - const title = getTitle(toolMessage) - - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); - const icon = null + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running + const title = getTitle(toolMessage); - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; - let range: [number, number] | undefined = undefined - if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) { - const start = toolMessage.params.startLine === null ? `1` : `${toolMessage.params.startLine}` - const end = toolMessage.params.endLine === null ? `` : `${toolMessage.params.endLine}` - const addStr = `(${start}-${end})` - componentParams.desc1 += ` ${addStr}` - range = [params.startLine || 1, params.endLine || 1] + let range: [number, number] | undefined = undefined; + if ( + toolMessage.params.startLine !== null || + toolMessage.params.endLine !== null + ) { + const start = + toolMessage.params.startLine === null + ? `1` + : `${toolMessage.params.startLine}`; + const end = + toolMessage.params.endLine === null + ? `` + : `${toolMessage.params.endLine}`; + const addStr = `(${start}-${end})`; + componentParams.desc1 += ` ${addStr}`; + range = [params.startLine || 1, params.endLine || 1]; } - if (toolMessage.type === 'success') { - const { result } = toolMessage - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor, range) } - if (result.hasNextPage && params.pageNumber === 1) // first page - componentParams.desc2 = `(truncated after ${Math.round(MAX_FILE_CHARS_PAGE) / 1000}k)` - else if (params.pageNumber > 1) // subsequent pages - componentParams.desc2 = `(part ${params.pageNumber})` - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor, range); + }; + if (result.hasNextPage && params.pageNumber === 1) + // first page + componentParams.desc2 = `(truncated after ${ + Math.round(MAX_FILE_CHARS_PAGE) / 1000 + }k)`; + else if (params.pageNumber > 1) + // subsequent pages + componentParams.desc2 = `(part ${params.pageNumber})`; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; // JumpToFileButton removed in favor of FileLinkText - componentParams.bottomChildren = - - {result} - - + componentParams.bottomChildren = ( + + {result} + + ); } - return + return ; }, }, - 'get_dir_tree': { + get_dir_tree: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - - const title = getTitle(toolMessage) - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null - - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; if (params.uri) { - const rel = getRelative(params.uri, accessor) - if (rel) componentParams.info = `Only search in ${rel}` + const rel = getRelative(params.uri, accessor); + if (rel) componentParams.info = `Only search in ${rel}`; } - if (toolMessage.type === 'success') { - const { result } = toolMessage - componentParams.children = - - - - - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.children = ( + + + + + + ); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } - return - - } + return ; + }, }, - 'ls_dir': { + ls_dir: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const explorerService = accessor.get('IExplorerService') - const title = getTitle(toolMessage) - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null - - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running - - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const explorerService = accessor.get("IExplorerService"); + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; if (params.uri) { - const rel = getRelative(params.uri, accessor) - if (rel) componentParams.info = `Only search in ${rel}` + const rel = getRelative(params.uri, accessor); + if (rel) componentParams.info = `Only search in ${rel}`; } - if (toolMessage.type === 'success') { - const { result } = toolMessage - componentParams.numResults = result.children?.length - componentParams.hasNextPage = result.hasNextPage - componentParams.children = !result.children || (result.children.length ?? 0) === 0 ? undefined - : - {result.children.map((child, i) => ( { - voidOpenFileFn(child.uri, accessor) - // commandService.executeCommand('workbench.view.explorer'); // open in explorer folders view instead - // explorerService.select(child.uri, true); - }} - />))} - {result.hasNextPage && - - } - - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.numResults = result.children?.length; + componentParams.hasNextPage = result.hasNextPage; + componentParams.children = + !result.children || + (result.children.length ?? 0) === 0 ? undefined : ( + + {result.children.map((child, i) => ( + { + voidOpenFileFn(child.uri, accessor); + }} + /> + ))} + {result.hasNextPage && ( + + )} + + ); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } - return - } + return ; + }, }, - 'search_pathnames_only': { + search_pathnames_only: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const isError = false - const isRejected = toolMessage.type === 'rejected' - const title = getTitle(toolMessage) - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null - - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running - - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; if (params.includePattern) { - componentParams.info = `Only search in ${params.includePattern}` + componentParams.info = `Only search in ${params.includePattern}`; } - if (toolMessage.type === 'success') { - const { result, rawParams } = toolMessage - componentParams.numResults = result.uris.length - componentParams.hasNextPage = result.hasNextPage - componentParams.children = result.uris.length === 0 ? undefined - : - {result.uris.map((uri, i) => ( { voidOpenFileFn(uri, accessor) }} - />))} - {result.hasNextPage && - - } - - - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + if (toolMessage.type === "success") { + const { result, rawParams } = toolMessage; + componentParams.numResults = result.uris.length; + componentParams.hasNextPage = result.hasNextPage; + componentParams.children = + result.uris.length === 0 ? undefined : ( + + {result.uris.map((uri, i) => ( + { + voidOpenFileFn(uri, accessor); + }} + /> + ))} + {result.hasNextPage && ( + + )} + + ); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } - return - } + return ; + }, }, - 'search_for_files': { + search_for_files: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const isError = false - const isRejected = toolMessage.type === 'rejected' - const title = getTitle(toolMessage) - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const { rawParams, params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; if (params.searchInFolder || params.isRegex) { - let info: string[] = [] + let info: string[] = []; if (params.searchInFolder) { - const rel = getRelative(params.searchInFolder, accessor) - if (rel) info.push(`Only search in ${rel}`) + const rel = getRelative(params.searchInFolder, accessor); + if (rel) info.push(`Only search in ${rel}`); } - if (params.isRegex) { info.push(`Uses regex search`) } - componentParams.info = info.join('; ') + if (params.isRegex) { + info.push(`Uses regex search`); + } + componentParams.info = info.join("; "); } - if (toolMessage.type === 'success') { - const { result, rawParams } = toolMessage - componentParams.numResults = result.uris.length - componentParams.hasNextPage = result.hasNextPage - componentParams.children = result.uris.length === 0 ? undefined - : - {result.uris.map((uri, i) => ( { voidOpenFileFn(uri, accessor) }} - />))} - {result.hasNextPage && - - } - - - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + if (toolMessage.type === "success") { + const { result, rawParams } = toolMessage; + componentParams.numResults = result.uris.length; + componentParams.hasNextPage = result.hasNextPage; + componentParams.children = + result.uris.length === 0 ? undefined : ( + + {result.uris.map((uri, i) => ( + { + voidOpenFileFn(uri, accessor); + }} + /> + ))} + {result.hasNextPage && ( + + )} + + ); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } - return - } + return ; + }, }, - 'search_in_file': { + search_in_file: { resultWrapper: ({ toolMessage }) => { const accessor = useAccessor(); - const toolsService = accessor.get('IToolsService'); + const toolsService = accessor.get("IToolsService"); const title = getTitle(toolMessage); - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); - const icon = null; - - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running - - const { rawParams, params } = toolMessage; - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected }; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; - const infoarr: string[] = [] - const uriStr = getRelative(params.uri, accessor) - if (uriStr) infoarr.push(uriStr) - if (params.isRegex) infoarr.push('Uses regex search') - componentParams.info = infoarr.join('; ') + const infoarr: string[] = []; + const uriStr = getRelative(params.uri, accessor); + if (uriStr) infoarr.push(uriStr); + if (params.isRegex) infoarr.push("Uses regex search"); + componentParams.info = infoarr.join("; "); - if (toolMessage.type === 'success') { + if (toolMessage.type === "success") { const { result } = toolMessage; // result is array of snippets componentParams.numResults = result.lines.length; - componentParams.children = result.lines.length === 0 ? undefined : - - -
-								{toolsService.stringOfResult['search_in_file'](params, result)}
-							
-
-
- } - else if (toolMessage.type === 'tool_error') { + componentParams.children = + result.lines.length === 0 ? undefined : ( + + +
+									{toolsService.stringOfResult["search_in_file"](
+										params,
+										result
+									)}
+								
+
+
+ ); + } else if (toolMessage.type === "tool_error") { const { result } = toolMessage; - componentParams.bottomChildren = - - {result} - - + componentParams.bottomChildren = ( + + {result} + + ); } return ; - } + }, }, - 'read_lint_errors': { + read_lint_errors: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - - const title = getTitle(toolMessage) - - const { uri } = toolMessage.params ?? {} - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running + const title = getTitle(toolMessage); - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const { uri } = toolMessage.params ?? {}; + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; - componentParams.info = getRelative(uri, accessor) // full path + componentParams.info = getRelative(uri, accessor); // full path - if (toolMessage.type === 'success') { - const { result } = toolMessage - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; if (result.lintErrors) - componentParams.children = - else - componentParams.children = `No lint errors found.` - - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage + componentParams.children = ( + + ); + else componentParams.children = `No lint errors found.`; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; // JumpToFileButton removed in favor of FileLinkText - componentParams.bottomChildren = - - {result} - - + componentParams.bottomChildren = ( + + {result} + + ); } - return + return ; }, }, // --- - 'create_file_or_folder': { + create_file_or_folder: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const isError = false - const isRejected = toolMessage.type === 'rejected' - const title = getTitle(toolMessage) - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null - - - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const accessor = useAccessor(); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; - componentParams.info = getRelative(params.uri, accessor) // full path + componentParams.info = getRelative(params.uri, accessor); // full path - if (toolMessage.type === 'success') { - const { result } = toolMessage - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } - } - else if (toolMessage.type === 'rejected') { - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - if (params) { componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } - componentParams.bottomChildren = - - {result} - - - } - else if (toolMessage.type === 'running_now') { + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "rejected") { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + if (params) { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } + componentParams.bottomChildren = ( + + {result} + + ); + } else if (toolMessage.type === "running_now") { // nothing more is needed - } - else if (toolMessage.type === 'tool_request') { + } else if (toolMessage.type === "tool_request") { // nothing more is needed } - return - } + return ; + }, }, - 'delete_file_or_folder': { + delete_file_or_folder: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const isFolder = toolMessage.params?.isFolder ?? false - const isError = false - const isRejected = toolMessage.type === 'rejected' - const title = getTitle(toolMessage) - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const icon = null - - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } - - componentParams.info = getRelative(params.uri, accessor) // full path - - if (toolMessage.type === 'success') { - const { result } = toolMessage - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } - } - else if (toolMessage.type === 'rejected') { - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - if (params) { componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } } - componentParams.bottomChildren = - - {result} - - - } - else if (toolMessage.type === 'running_now') { - const { result } = toolMessage - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } - } - else if (toolMessage.type === 'tool_request') { - const { result } = toolMessage - componentParams.onClick = () => { voidOpenFileFn(params.uri, accessor) } + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const isFolder = toolMessage.params?.isFolder ?? false; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; + + componentParams.info = getRelative(params.uri, accessor); // full path + + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "rejected") { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + if (params) { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } + componentParams.bottomChildren = ( + + {result} + + ); + } else if (toolMessage.type === "running_now") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "tool_request") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; } - return - } + return ; + }, }, - 'rewrite_file': { + rewrite_file: { resultWrapper: (params) => { - return - } + return ( + + ); + }, }, - 'edit_file': { + edit_file: { resultWrapper: (params) => { - return - } + return ( + + ); + }, }, // --- - 'run_command': { + run_command: { resultWrapper: (params) => { - return - } + return ; + }, }, - 'run_persistent_command': { + run_persistent_command: { resultWrapper: (params) => { - return - } + return ; + }, }, - 'open_persistent_terminal': { + open_persistent_terminal: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const terminalToolsService = accessor.get('ITerminalToolService') - - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const title = getTitle(toolMessage) - const icon = null + const accessor = useAccessor(); + const terminalToolsService = accessor.get("ITerminalToolService"); - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + const title = getTitle(toolMessage); - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; - const relativePath = params.cwd ? getRelative(URI.file(params.cwd), accessor) : '' - componentParams.info = relativePath ? `Running in ${relativePath}` : undefined + const relativePath = params.cwd + ? getRelative(URI.file(params.cwd), accessor) + : ""; + componentParams.info = relativePath + ? `Running in ${relativePath}` + : undefined; - if (toolMessage.type === 'success') { - const { result } = toolMessage - const { persistentTerminalId } = result - componentParams.desc1 = persistentTerminalNameOfId(persistentTerminalId) - componentParams.onClick = () => terminalToolsService.focusPersistentTerminal(persistentTerminalId) - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + if (toolMessage.type === "success") { + const { result } = toolMessage; + const { persistentTerminalId } = result; + componentParams.desc1 = + persistentTerminalNameOfId(persistentTerminalId); + componentParams.onClick = () => + terminalToolsService.focusPersistentTerminal(persistentTerminalId); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } - return + return ; }, }, - 'kill_persistent_terminal': { + kill_persistent_terminal: { resultWrapper: ({ toolMessage }) => { - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const terminalToolsService = accessor.get('ITerminalToolService') - - const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) - const title = getTitle(toolMessage) - const icon = null - - if (toolMessage.type === 'tool_request') return null // do not show past requests - if (toolMessage.type === 'running_now') return null // do not show running - - const isError = false - const isRejected = toolMessage.type === 'rejected' - const { rawParams, params } = toolMessage - const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } - - if (toolMessage.type === 'success') { - const { persistentTerminalId } = params - componentParams.desc1 = persistentTerminalNameOfId(persistentTerminalId) - componentParams.onClick = () => terminalToolsService.focusPersistentTerminal(persistentTerminalId) - } - else if (toolMessage.type === 'tool_error') { - const { result } = toolMessage - componentParams.bottomChildren = - - {result} - - + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const terminalToolsService = accessor.get("ITerminalToolService"); + + const { desc1, desc1Info } = toolNameToDesc( + toolMessage.name, + toolMessage.params, + accessor + ); + const title = getTitle(toolMessage); + + if (toolMessage.type === "tool_request") return null; // do not show past requests + if (toolMessage.type === "running_now") return null; // do not show running + + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { params } = toolMessage; + const componentParams: ToolHeaderParams = { + title, + desc1, + desc1Info, + isError, + isRejected, + }; + + if (toolMessage.type === "success") { + const { persistentTerminalId } = params; + componentParams.desc1 = + persistentTerminalNameOfId(persistentTerminalId); + componentParams.onClick = () => + terminalToolsService.focusPersistentTerminal(persistentTerminalId); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = ( + + {result} + + ); } - return + return ; }, }, }; +const Checkpoint = ({ + threadId, + messageIdx, + isCheckpointGhost, + threadIsRunning, +}: { + threadId: string; + messageIdx: number; + isCheckpointGhost: boolean; + threadIsRunning: boolean; +}) => { + const accessor = useAccessor(); + const chatThreadService = accessor.get("IChatThreadService"); + const streamState = useFullChatThreadsStreamState(); -const Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIsRunning }: { message: CheckpointEntry, threadId: string; messageIdx: number, isCheckpointGhost: boolean, threadIsRunning: boolean }) => { - const accessor = useAccessor() - const chatThreadService = accessor.get('IChatThreadService') - const streamState = useFullChatThreadsStreamState() - - const isRunning = useChatThreadsStreamState(threadId)?.isRunning + const isRunning = useChatThreadsStreamState(threadId)?.isRunning; const isDisabled = useMemo(() => { - if (isRunning) return true - return !!Object.keys(streamState).find((threadId2) => streamState[threadId2]?.isRunning) - }, [isRunning, streamState]) + if (isRunning) return true; + return !!Object.keys(streamState).find( + (threadId2) => streamState[threadId2]?.isRunning + ); + }, [isRunning, streamState]); - return
-
+
{ - if (threadIsRunning) return - if (isDisabled) return - chatThreadService.jumpToCheckpointBeforeMessageIdx({ - threadId, - messageIdx, - jumpToUserModified: messageIdx === (chatThreadService.state.allThreads[threadId]?.messages.length ?? 0) - 1 - }) - }} - {...isDisabled ? { - 'data-tooltip-id': 'void-tooltip', - 'data-tooltip-content': `Disabled ${isRunning ? 'when running' : 'because another thread is running'}`, - 'data-tooltip-place': 'top', - } : {}} - > - Checkpoint + ${isCheckpointGhost ? "opacity-50" : "opacity-100"} + ${isDisabled ? "cursor-default" : "cursor-pointer"} + `} + style={{ position: "relative", display: "inline-block" }} // allow absolute icon + onClick={() => { + if (threadIsRunning) return; + if (isDisabled) return; + chatThreadService.jumpToCheckpointBeforeMessageIdx({ + threadId, + messageIdx, + jumpToUserModified: + messageIdx === + (chatThreadService.state.allThreads[threadId]?.messages.length ?? + 0) - + 1, + }); + }} + {...(isDisabled + ? { + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": `Disabled ${ + isRunning ? "when running" : "because another thread is running" + }`, + "data-tooltip-place": "top", + } + : {})} + > + Checkpoint +
-
-} - + ); +}; -type ChatBubbleMode = 'display' | 'edit' +type ChatBubbleMode = "display" | "edit"; type ChatBubbleProps = { - chatMessage: ChatMessage, - messageIdx: number, - isCommitted: boolean, - chatIsRunning: IsRunningType, - threadId: string, - currCheckpointIdx: number | undefined, - _scrollToBottom: (() => void) | null, -} + chatMessage: ChatMessage; + messageIdx: number; + isCommitted: boolean; + chatIsRunning: IsRunningType; + threadId: string; + currCheckpointIdx: number | undefined; + _scrollToBottom: (() => void) | null; +}; const ChatBubble = (props: ChatBubbleProps) => { - return - <_ChatBubble {...props} /> - -} - -const _ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, messageIdx, chatIsRunning, _scrollToBottom }: ChatBubbleProps) => { - const role = chatMessage.role - - const isCheckpointGhost = messageIdx > (currCheckpointIdx ?? Infinity) && !chatIsRunning // whether to show as gray (if chat is running, for good measure just dont show any ghosts) - - if (role === 'user') { - return - } - else if (role === 'assistant') { - return - } - else if (role === 'tool') { - - if (chatMessage.type === 'invalid_params') { - return
- -
- } - - const toolName = chatMessage.name - const isBuiltInTool = isABuiltinToolName(toolName) - const ToolResultWrapper = isBuiltInTool ? builtinToolNameToComponent[toolName]?.resultWrapper as ResultWrapper - : MCPToolWrapper as ResultWrapper + return ( + + <_ChatBubble {...props} /> + + ); +}; - if (ToolResultWrapper) - return <> -
- { + const role = chatMessage.role; + + const isCheckpointGhost = + messageIdx > (currCheckpointIdx ?? Infinity) && !chatIsRunning; // whether to show as gray (if chat is running, for good measure just dont show any ghosts) + + if (role === "user") { + return ( + + ); + } else if (role === "assistant") { + return ( + + ); + } else if (role === "tool") { + if (chatMessage.type === "invalid_params") { + return ( +
+
- {chatMessage.type === 'tool_request' ? -
- -
: null} - - return null - } + ); + } - else if (role === 'interrupted_streaming_tool') { - return
- -
- } + const toolName = chatMessage.name; + const isBuiltInTool = isABuiltinToolName(toolName); + const ToolResultWrapper = isBuiltInTool + ? (builtinToolNameToComponent[toolName] + ?.resultWrapper as ResultWrapper) + : (MCPToolWrapper as ResultWrapper); - else if (role === 'checkpoint') { - return + if (ToolResultWrapper) + return ( + <> +
+ +
+ {chatMessage.type === "tool_request" ? ( +
+ +
+ ) : null} + + ); + return null; + } else if (role === "interrupted_streaming_tool") { + return ( +
+ +
+ ); + } else if (role === "checkpoint") { + return ( + + ); } - -} +}; 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'; + 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') + setFileDetailsOpenedState("auto-closed"); } // open the file details if it hasnt been closed - if (numFilesChanged > 0 && fileDetailsOpenedState !== 'user-closed') { - setFileDetailsOpenedState('auto-opened') + if (numFilesChanged > 0 && fileDetailsOpenedState !== "user-closed") { + setFileDetailsOpenedState("auto-opened"); } - }, [fileDetailsOpenedState, setFileDetailsOpenedState, numFilesChanged]) + }, [fileDetailsOpenedState, setFileDetailsOpenedState, numFilesChanged]); - - const isFinishedMakingThreadChanges = ( + const isFinishedMakingThreadChanges = // there are changed files - commandBarState.sortedURIs.length !== 0 + commandBarState.sortedURIs.length !== 0 && // none of the files are streaming - && commandBarState.sortedURIs.every(uri => !commandBarState.stateOfURI[uri.fsPath]?.isStreaming) - ) + 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?" @@ -2623,177 +3388,213 @@ const CommandBarInChat = () => { // orange = Requires action // dark = Done - const threadStatus = ( - chatThreadsStreamState?.isRunning === 'awaiting_user' ? { title: 'Needs Approval', color: 'yellow', } as const - : chatThreadsStreamState?.isRunning ? { title: 'Running', color: 'orange', } as const - : { title: 'Done', color: 'dark', } as const - ) - - - const threadStatusHTML = - + const threadStatus = + chatThreadsStreamState?.isRunning === "awaiting_user" + ? ({ title: "Needs Approval", color: "yellow" } as const) + : chatThreadsStreamState?.isRunning + ? ({ title: "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 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, + const acceptRejectAllButtons = ( +
+ { + sortedCommandBarURIs.forEach((uri) => { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "reject", + _addToHistory: true, + }); }); - }); - }} - data-tooltip-id='void-tooltip' - data-tooltip-place='top' - data-tooltip-content='Reject all' - /> + }} + data-tooltip-id="void-tooltip" + data-tooltip-place="top" + data-tooltip-content="Reject all" + /> - { - sortedCommandBarURIs.forEach(uri => { - editCodeService.acceptOrRejectAllDiffAreas({ - uri, - removeCtrlKs: true, - behavior: "accept", - _addToHistory: true, + { + sortedCommandBarURIs.forEach((uri) => { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "accept", + _addToHistory: true, + }); }); - }); - }} - data-tooltip-id='void-tooltip' - data-tooltip-place='top' - data-tooltip-content='Accept all' - /> - - - -
- + }} + data-tooltip-id="void-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 fileDetailsContent = ( +
+ {sortedCommandBarURIs.map((uri, i) => { + const basename = getBasename(uri.fsPath); - const fileNameHTML =
voidOpenFileFn(uri, accessor)} - > - {/* */} - {basename} -
+ 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 detailsContent = ( +
+ + {numDiffs} diff{numDiffs !== 1 ? "s" : ""} + +
+ ); - const acceptRejectButtons =
- {/* */} - { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "reject", _addToHistory: true, }); }} - data-tooltip-id='void-tooltip' - data-tooltip-place='top' - data-tooltip-content='Reject file' - - /> - { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "accept", _addToHistory: true, }); }} - data-tooltip-id='void-tooltip' - data-tooltip-place='top' - data-tooltip-content='Accept file' - /> - -
- - const fileStatusHTML = - - return ( - // name, details -
-
- {fileNameHTML} - {detailsContent} + > + { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "reject", + _addToHistory: true, + }); + }} + data-tooltip-id="void-tooltip" + data-tooltip-place="top" + data-tooltip-content="Reject file" + /> + { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "accept", + _addToHistory: true, + }); + }} + data-tooltip-id="void-tooltip" + data-tooltip-place="top" + data-tooltip-content="Accept file" + />
-
- {acceptRejectButtons} - {fileStatusHTML} + ); + + const fileStatusHTML = ( + + ); + + return ( + // name, details +
+
+ {fileNameHTML} + {detailsContent} +
+
+ {acceptRejectButtons} + {fileStatusHTML} +
-
- ) - })} -
+ ); + })} +
+ ); const fileDetailsButton = ( - ) + ); return ( <> {/* file details */} -
+
{ text-void-fg-3 text-xs text-nowrap overflow-hidden transition-all duration-200 ease-in-out - ${isFileDetailsOpened ? 'max-h-24' : 'max-h-0'} + ${isFileDetailsOpened ? "max-h-24" : "max-h-0"} `} > {fileDetailsContent} @@ -2819,360 +3620,395 @@ const CommandBarInChat = () => { justify-between `} > -
- {fileDetailsButton} -
+
{fileDetailsButton}
{acceptRejectAllButtons} {threadStatusHTML}
- ) -} - - - -const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => { + ); +}; - if (!isABuiltinToolName(toolCallSoFar.name)) return null +const EditToolSoFar = ({ + toolCallSoFar, +}: { + toolCallSoFar: RawToolCallObj; +}) => { + if (!isABuiltinToolName(toolCallSoFar.name)) return null; - const accessor = useAccessor() + const accessor = useAccessor(); - const uri = toolCallSoFar.rawParams.uri ? URI.file(toolCallSoFar.rawParams.uri) : undefined + const uri = toolCallSoFar.rawParams.uri + ? URI.file(toolCallSoFar.rawParams.uri) + : undefined; - const title = titleOfBuiltinToolName[toolCallSoFar.name].proposed + const title = titleOfBuiltinToolName[toolCallSoFar.name].proposed; - const uriDone = toolCallSoFar.doneParams.includes('uri') - const desc1 = - {uriDone ? - getBasename(toolCallSoFar.rawParams['uri'] ?? 'unknown') - : `Generating`} - - + const uriDone = toolCallSoFar.doneParams.includes("uri"); + const desc1 = ( + + {uriDone + ? getBasename(toolCallSoFar.rawParams["uri"] ?? "unknown") + : `Generating`} + + + ); - const desc1OnClick = () => { uri && voidOpenFileFn(uri, accessor) } + const desc1OnClick = () => { + uri && voidOpenFileFn(uri, accessor); + }; // If URI has not been specified - return - - - - -} - + return ( + + + + + ); +}; export const SidebarChat = () => { - const textAreaRef = useRef(null) - const textAreaFnsRef = useRef(null) + const textAreaRef = useRef(null); + const textAreaFnsRef = useRef(null); - const accessor = useAccessor() - const commandService = accessor.get('ICommandService') - const chatThreadsService = accessor.get('IChatThreadService') + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const chatThreadsService = accessor.get("IChatThreadService"); - const settingsState = useSettingsState() + const settingsState = useSettingsState(); // ----- HIGHER STATE ----- // threads state - const chatThreadsState = useChatThreadsState() + const chatThreadsState = useChatThreadsState(); - const currentThread = chatThreadsService.getCurrentThread() - const previousMessages = currentThread?.messages ?? [] + const currentThread = chatThreadsService.getCurrentThread(); + const previousMessages = currentThread?.messages ?? []; - const selections = currentThread.state.stagingSelections - const setSelections = (s: StagingSelectionItem[]) => { chatThreadsService.setCurrentThreadState({ stagingSelections: s }) } + const selections = currentThread.state.stagingSelections; + const setSelections = (s: StagingSelectionItem[]) => { + chatThreadsService.setCurrentThreadState({ stagingSelections: s }); + }; // stream state - const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId) - const isRunning = currThreadStreamState?.isRunning - const latestError = currThreadStreamState?.error - const { displayContentSoFar, toolCallSoFar, reasoningSoFar } = currThreadStreamState?.llmInfo ?? {} + const currThreadStreamState = useChatThreadsStreamState( + chatThreadsState.currentThreadId + ); + const isRunning = currThreadStreamState?.isRunning; + const latestError = currThreadStreamState?.error; + const { displayContentSoFar, toolCallSoFar, reasoningSoFar } = + currThreadStreamState?.llmInfo ?? {}; // this is just if it's currently being generated, NOT if it's currently running - const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone // show loading for slow tools (right now just edit) + const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone; // show loading for slow tools (right now just edit) // ----- SIDEBAR CHAT state (local) ----- // state of current message - const initVal = '' - const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!initVal) + const initVal = ""; + const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!initVal); - const isDisabled = instructionsAreEmpty || !!isFeatureNameDisabled('Chat', settingsState) + const isDisabled = + instructionsAreEmpty || !!isFeatureNameDisabled("Chat", settingsState); - const sidebarRef = useRef(null) - const scrollContainerRef = useRef(null) - const onSubmit = useCallback(async (_forceSubmit?: string) => { + const sidebarRef = useRef(null); + const scrollContainerRef = useRef(null); + const onSubmit = useCallback( + async (_forceSubmit?: string) => { + if (isDisabled && !_forceSubmit) return; + if (isRunning) return; - if (isDisabled && !_forceSubmit) return - if (isRunning) return + const threadId = chatThreadsService.state.currentThreadId; - const threadId = chatThreadsService.state.currentThreadId - - // send message to LLM - const userMessage = _forceSubmit || textAreaRef.current?.value || '' - - try { - await chatThreadsService.addUserMessageAndStreamResponse({ userMessage, threadId }) - } catch (e) { - console.error('Error while sending message in chat:', e) - } + // send message to LLM + const userMessage = _forceSubmit || textAreaRef.current?.value || ""; - setSelections([]) // clear staging - textAreaFnsRef.current?.setValue('') - textAreaRef.current?.focus() // focus input after submit + try { + await chatThreadsService.addUserMessageAndStreamResponse({ + userMessage, + threadId, + }); + } catch (e) { + console.error("Error while sending message in chat:", e); + } - }, [chatThreadsService, isDisabled, isRunning, textAreaRef, textAreaFnsRef, setSelections, settingsState]) + setSelections([]); // clear staging + textAreaFnsRef.current?.setValue(""); + textAreaRef.current?.focus(); // focus input after submit + }, + [ + chatThreadsService, + isDisabled, + isRunning, + textAreaRef, + textAreaFnsRef, + setSelections, + settingsState, + ] + ); const onAbort = async () => { - const threadId = currentThread.id - await chatThreadsService.abortRunning(threadId) - } - - const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_L_ACTION_ID)?.getLabel() - - const threadId = currentThread.id - const currCheckpointIdx = chatThreadsState.allThreads[threadId]?.state?.currCheckpointIdx ?? undefined // if not exist, treat like checkpoint is last message (infinity) + const threadId = currentThread.id; + await chatThreadsService.abortRunning(threadId); + }; + const keybindingString = accessor + .get("IKeybindingService") + .lookupKeybinding(VOID_CTRL_L_ACTION_ID) + ?.getLabel(); + const threadId = currentThread.id; + const currCheckpointIdx = + chatThreadsState.allThreads[threadId]?.state?.currCheckpointIdx ?? + undefined; // if not exist, treat like checkpoint is last message (infinity) // resolve mount info - const isResolved = chatThreadsState.allThreads[threadId]?.state.mountedInfo?.mountedIsResolvedRef.current + const isResolved = + chatThreadsState.allThreads[threadId]?.state.mountedInfo + ?.mountedIsResolvedRef.current; useEffect(() => { - if (isResolved) return - chatThreadsState.allThreads[threadId]?.state.mountedInfo?._whenMountedResolver?.({ + if (isResolved) return; + chatThreadsState.allThreads[ + threadId + ]?.state.mountedInfo?._whenMountedResolver?.({ textAreaRef: textAreaRef, scrollToBottom: () => scrollToBottom(scrollContainerRef), - }) - - }, [chatThreadsState, threadId, textAreaRef, scrollContainerRef, isResolved]) - - - + }); + }, [chatThreadsState, threadId, textAreaRef, scrollContainerRef, isResolved]); const previousMessagesHTML = useMemo(() => { // const lastMessageIdx = previousMessages.findLastIndex(v => v.role !== 'checkpoint') // tool request shows up as Editing... if in progress return previousMessages.map((message, i) => { - return scrollToBottom(scrollContainerRef)} + /> + ); + }); + }, [previousMessages, threadId, currCheckpointIdx, isRunning]); + + const streamingChatIdx = previousMessagesHTML.length; + const currStreamingMessageHTML = + reasoningSoFar || displayContentSoFar || isRunning ? ( + scrollToBottom(scrollContainerRef)} + _scrollToBottom={null} /> - }) - }, [previousMessages, threadId, currCheckpointIdx, isRunning]) - - const streamingChatIdx = previousMessagesHTML.length - const currStreamingMessageHTML = reasoningSoFar || displayContentSoFar || isRunning ? - : null - + ) : null; // the tool currently being generated - const generatingTool = toolIsGenerating ? - toolCallSoFar.name === 'edit_file' || toolCallSoFar.name === 'rewrite_file' ? - : null - : null + const generatingTool = toolIsGenerating ? ( + toolCallSoFar.name === "edit_file" || + toolCallSoFar.name === "rewrite_file" ? ( + + ) : null + ) : null; - const messagesHTML = - {/* previous messages */} - {previousMessagesHTML} - {currStreamingMessageHTML} - - {/* Generating tool */} - {generatingTool} - - {/* loading indicator */} - {isRunning === 'LLM' || isRunning === 'idle' && !toolIsGenerating ? - {} - : null} - - - {/* error message */} - {latestError === undefined ? null : -
- { chatThreadsService.dismissStreamError(currentThread.id) }} - showDismiss={true} - /> + > + {/* previous messages */} + {previousMessagesHTML} + {currStreamingMessageHTML} - { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }} text='Open settings' /> -
- } -
+ {/* Generating tool */} + {generatingTool} + {/* loading indicator */} + {isRunning === "LLM" || (isRunning === "idle" && !toolIsGenerating) ? ( + + {} + + ) : null} + + {/* error message */} + {latestError === undefined ? null : ( +
+ { + chatThreadsService.dismissStreamError(currentThread.id); + }} + showDismiss={true} + /> - const onChangeText = useCallback((newStr: string) => { - setInstructionsAreEmpty(!newStr) - }, [setInstructionsAreEmpty]) - const onKeyDown = useCallback((e: KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - onSubmit() - } else if (e.key === 'Escape' && isRunning) { - onAbort() - } - }, [onSubmit, onAbort, isRunning]) - - const inputChatArea = onSubmit()} - onAbort={onAbort} - isStreaming={!!isRunning} - isDisabled={isDisabled} - showSelections={true} - // showProspectiveSelections={previousMessagesHTML.length === 0} - selections={selections} - setSelections={setSelections} - onClickAnywhere={() => { textAreaRef.current?.focus() }} - > - { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }} - ref={textAreaRef} - fnsRef={textAreaFnsRef} - multiline={true} - /> + { + commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID); + }} + text="Open settings" + /> +
+ )} + + ); - + const onChangeText = useCallback( + (newStr: string) => { + setInstructionsAreEmpty(!newStr); + }, + [setInstructionsAreEmpty] + ); + const onKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + onSubmit(); + } else if (e.key === "Escape" && isRunning) { + onAbort(); + } + }, + [onSubmit, onAbort, isRunning] + ); + const inputChatArea = ( + onSubmit()} + onAbort={onAbort} + isStreaming={!!isRunning} + isDisabled={isDisabled} + showSelections={true} + selections={selections} + setSelections={setSelections} + onClickAnywhere={() => { + textAreaRef.current?.focus(); + }} + > + { + chatThreadsService.setCurrentlyFocusedMessageIdx(undefined); + }} + ref={textAreaRef} + fnsRef={textAreaFnsRef} + multiline={true} + /> + + ); - const isLandingPage = previousMessages.length === 0 + const isLandingPage = previousMessages.length === 0; + const initiallySuggestedPromptsHTML = ( +
+ {[ + "Summarize my codebase", + "How do types work in Rust?", + "Create a .voidrules file for me", + ].map((text, index) => ( +
onSubmit(text)} + > + {text} +
+ ))} +
+ ); - const initiallySuggestedPromptsHTML =
- {[ - 'Summarize my codebase', - 'How do types work in Rust?', - 'Create a .voidrules file for me' - ].map((text, index) => ( -
onSubmit(text)} - > - {text} + const threadPageInput = ( +
+
+
- ))} -
- - - - const threadPageInput =
-
- +
{inputChatArea}
-
- {inputChatArea} -
-
+ ); - const landingPageInput =
-
- {inputChatArea} + const landingPageInput = ( +
+
{inputChatArea}
-
- - const landingPageContent =
- - {landingPageInput} - + ); - {Object.keys(chatThreadsState.allThreads).length > 1 ? // show if there are threads - -
Previous Threads
- -
- : - -
Suggestions
- {initiallySuggestedPromptsHTML} -
- } -
- - - // const threadPageContent =
- // {/* Thread content */} - //
- //
- // - // {messagesHTML} - // - //
- // - // {inputForm} - // - //
- //
- const threadPageContent =
+ const landingPageContent = ( +
+ {landingPageInput} - - {messagesHTML} - - - {threadPageInput} - -
+ {Object.keys(chatThreadsState.allThreads).length > 1 ? ( // show if there are threads + +
+ Previous Threads +
+ +
+ ) : ( + +
+ Suggestions +
+ {initiallySuggestedPromptsHTML} +
+ )} +
+ ); + const threadPageContent = ( +
+ {messagesHTML} + {threadPageInput} +
+ ); return ( - - {isLandingPage ? - landingPageContent - : threadPageContent} + {isLandingPage ? landingPageContent : threadPageContent} - ) -} + ); +};