make apply actually work

This commit is contained in:
Mathew Pareles 2025-03-12 00:01:52 -07:00
parent d6d5f77183
commit 967f7dc85e
5 changed files with 280 additions and 122 deletions

View file

@ -3,6 +3,8 @@ import { useAccessor, useURIStreamState, useSettingsState } from '../util/servic
import { useRefState } from '../util/helpers.js'
import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js'
import { URI } from '../../../../../../../base/common/uri.js'
import { LucideIcon, RotateCw } from 'lucide-react'
import { Check, X, Square, Copy, Play, } from 'lucide-react'
enum CopyButtonText {
Idle = 'Copy',
@ -10,6 +12,53 @@ enum CopyButtonText {
Error = 'Could not copy',
}
type IconButtonProps = {
onClick: () => void
title: string
Icon: LucideIcon
disabled?: boolean
className?: string
}
export const IconShell1 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => (
<button
title={title}
disabled={disabled}
onClick={onClick}
className={`
size-6
flex items-center justify-center
text-sm bg-void-bg-3 text-void-fg-1
hover:brightness-110
border border-void-border-1 rounded
disabled:opacity-50 disabled:cursor-not-allowed
${className}
`}
>
<Icon size={14} />
</button>
)
export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => (
<button
title={title}
disabled={disabled}
onClick={onClick}
className={`
size-6
flex items-center justify-center
text-sm
hover:opacity-80
disabled:opacity-50 disabled:cursor-not-allowed
${className}
`}
>
<Icon size={14} />
</button>
)
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const CopyButton = ({ codeStr }: { codeStr: string }) => {
@ -26,7 +75,6 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
}, COPY_FEEDBACK_TIMEOUT)
}, [copyButtonText])
const onCopy = useCallback(() => {
clipboardService.writeText(codeStr)
.then(() => { setCopyButtonText(CopyButtonText.Copied) })
@ -34,26 +82,20 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
metricsService.capture('Copy Code', { length: codeStr.length }) // capture the length only
}, [metricsService, clipboardService, codeStr, setCopyButtonText])
const isSingleLine = false //!codeStr.includes('\n')
return <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
return <IconShell1
Icon={copyButtonText === CopyButtonText.Copied ? Check : copyButtonText === CopyButtonText.Error ? X : Copy}
onClick={onCopy}
>
{copyButtonText}
</button>
title={copyButtonText}
/>
}
// state persisted for duration of react only
// TODO change this to use type `ChatThreads.applyBoxState[applyBoxId]`
const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} }
export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: string, applyBoxId: string }) => {
export const useApplyButtonHTML = ({ codeStr, applyBoxId }: { codeStr: string, applyBoxId: string }) => {
const settingsState = useSettingsState()
const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId
@ -64,21 +106,21 @@ export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: strin
const [_, rerender] = useState(0)
const applyingUri = useCallback(() => applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null, [applyBoxId])
const streamState = useCallback(() => editCodeService.getURIStreamState({ uri: applyingUri() }), [editCodeService, applyingUri])
const getUriBeingApplied = useCallback(() => applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null, [applyBoxId])
const getStreamState = useCallback(() => editCodeService.getURIStreamState({ uri: getUriBeingApplied() }), [editCodeService, getUriBeingApplied])
// listen for stream updates
useURIStreamState(
useCallback((uri, newStreamState) => {
const shouldUpdate = applyingUri()?.fsPath !== uri.fsPath
if (shouldUpdate) return
const shouldUpdate = getUriBeingApplied()?.fsPath === uri.fsPath
if (!shouldUpdate) return
rerender(c => c + 1)
}, [applyBoxId, editCodeService, applyingUri])
}, [applyBoxId, editCodeService, getUriBeingApplied])
)
const onSubmit = useCallback(() => {
if (isDisabled) return
if (streamState() === 'streaming') return
if (getStreamState() === 'streaming') return
const [newApplyingUri, _] = editCodeService.startApplying({
from: 'ClickApply',
type: 'searchReplace',
@ -88,61 +130,122 @@ export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: strin
applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined
rerender(c => c + 1)
metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only
}, [isDisabled, streamState, editCodeService, codeStr, applyBoxId, metricsService])
}, [isDisabled, getStreamState, editCodeService, codeStr, applyBoxId, metricsService])
const onInterrupt = useCallback(() => {
if (streamState() !== 'streaming') return
const uri = applyingUri()
if (getStreamState() !== 'streaming') return
const uri = getUriBeingApplied()
if (!uri) return
editCodeService.interruptURIStreaming({ uri })
metricsService.capture('Stop Apply', {})
}, [streamState, applyingUri, editCodeService, metricsService])
}, [getStreamState, getUriBeingApplied, editCodeService, metricsService])
const onAccept = useCallback(() => {
const uri = getUriBeingApplied()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
}, [getUriBeingApplied, editCodeService])
const onReject = useCallback(() => {
const uri = getUriBeingApplied()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
}, [getUriBeingApplied, editCodeService])
const onReapply = useCallback(() => {
onReject()
onSubmit()
}, [onReject, onSubmit])
const currStreamState = getStreamState()
const copyButton = (
<CopyButton codeStr={codeStr} />
)
const playButton = (
<IconShell1
Icon={Play}
onClick={onSubmit}
title="Apply changes"
/>
)
const stopButton = (
<IconShell1
Icon={Square}
onClick={onInterrupt}
title="Stop applying"
/>
)
const reapplyButton = (
<IconShell1
Icon={RotateCw}
onClick={onReapply}
title="Reapply changes"
/>
)
const acceptButton = (
<IconShell1
Icon={Check}
onClick={onAccept}
title="Accept changes"
className="text-green-600"
/>
)
const rejectButton = (
<IconShell1
Icon={X}
onClick={onReject}
title="Reject changes"
className="text-red-600"
/>
)
const isSingleLine = false //!codeStr.includes('\n')
let buttonsHTML = <></>
const applyButton = <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={onSubmit}
>
Apply
</button>
if (currStreamState === 'streaming') {
buttonsHTML = <>
{stopButton}
</>
}
const stopButton = <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={onInterrupt}
>
Stop
</button>
if (currStreamState === 'idle') {
buttonsHTML = <>
{copyButton}
{playButton}
</>
}
const acceptRejectButtons = <>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={() => {
const uri = applyingUri()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
}}
if (currStreamState === 'acceptRejectAll') {
buttonsHTML = <>
{reapplyButton}
{rejectButton}
{acceptButton}
</>
}
const statusIndicatorHTML = <div className='flex flex-row gap-2 items-center'>
<div
className={`size-1.5 rounded-full border
${currStreamState === 'idle' ? 'bg-void-bg-3 border-void-border-1' :
currStreamState === 'streaming' ? 'bg-orange-500 border-orange-500 shadow-[0_0_4px_0px_rgba(234,88,12,0.6)]' :
currStreamState === 'acceptRejectAll' ? 'bg-green-500 border-green-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
'bg-void-border-1 border-void-border-1'
}`
}
>
Accept
</button>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={() => {
const uri = applyingUri()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
}}
>
Reject
</button>
</>
</div>
</div>
return {
statusIndicatorHTML,
buttonsHTML
}
const currStreamState = streamState()
return <>
{currStreamState !== 'streaming' && <CopyButton codeStr={codeStr} />}
{currStreamState === 'idle' && !isDisabled && applyButton}
{currStreamState === 'streaming' && stopButton}
{currStreamState === 'acceptRejectAll' && acceptRejectButtons}
</>
}

View file

@ -3,17 +3,44 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import React from 'react';
import { VoidCodeEditor, VoidCodeEditorProps } from '../util/inputs.js';
import { useApplyButtonHTML } from './ApplyBlockHoverButtons.js';
export const BlockCodeWithApply = ({ initValue, language, applyBoxId }: { initValue: string, language?: string, applyBoxId: string }) => {
const { statusIndicatorHTML, buttonsHTML } = useApplyButtonHTML({ codeStr: initValue, applyBoxId })
return (
<div className="border border-void-border-3 rounded-sm overflow-hidden bg-void-bg-2">
<div className="flex justify-between items-center px-2 py-1 border-b border-void-border-3">
<div className="flex items-center gap-2">
<div className="text-sm opacity-50">{language || 'text'}</div>
{statusIndicatorHTML}
</div>
<div className="flex gap-1">
{buttonsHTML}
</div>
</div>
<BlockCode
initValue={initValue}
language={language}
/>
</div>
)
}
export const BlockCode = ({ buttonsOnHover, ...codeEditorProps }: { buttonsOnHover?: React.ReactNode } & VoidCodeEditorProps) => {
export const BlockCode = ({ ...codeEditorProps }: VoidCodeEditorProps) => {
const isSingleLine = !codeEditorProps.initValue.includes('\n')
return (
<>
<div className="relative group w-full overflow-hidden">
<VoidCodeEditor {...codeEditorProps} />
{/* <div className="relative group w-full overflow-hidden">
{buttonsOnHover === null ? null : (
<div className={`z-[1] absolute top-0 right-0 opacity-0 group-hover:opacity-100 duration-200 ${isSingleLine ? 'h-full flex items-center' : ''}`}>
<div className={`flex space-x-1 ${isSingleLine ? 'pr-2' : 'p-2'}`}>
@ -23,7 +50,8 @@ export const BlockCode = ({ buttonsOnHover, ...codeEditorProps }: { buttonsOnHov
)}
<VoidCodeEditor {...codeEditorProps} />
</div>
</div> */}
</>
)
}

View file

@ -5,9 +5,9 @@
import React, { JSX, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
import { BlockCode, BlockCodeWithApply } from './BlockCode.js'
import { nameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js'
import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js'
import { useApplyButtonHTML } from './ApplyBlockHoverButtons.js'
import { useAccessor, useChatThreadsState } from '../util/services.js'
import { Range } from '../../../../../../services/search/common/searchExtTypes.js'
import { IRange } from '../../../../../../../base/common/range.js'
@ -56,7 +56,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
if (link === undefined) {
// generate link and add to cache
// if no link, generate link and add to cache
(chatThreadService.generateCodespanLink(text)
.then(link => {
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
@ -99,7 +99,9 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
/>
}
const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token: Token | string, nested?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {
export type RenderTokenOptions = { isApplyEnabled?: boolean, isLinkDetectionEnabled?: boolean }
const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, nested?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: string, } & RenderTokenOptions): JSX.Element => {
// deal with built-in tokens first (assume marked token)
const t = token as MarkedToken
@ -114,21 +116,29 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
if (t.type === "code") {
const applyBoxId = chatMessageLocation ? getApplyBoxId({
threadId: chatMessageLocation.threadId,
messageIdx: chatMessageLocation.messageIdx,
tokenIdx: tokenIdx,
}) : null
const language = t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]
// TODO user should only be able to apply this when the code has been closed (t.raw ends with "```")
return <div>
<BlockCode
if (options.isApplyEnabled && chatMessageLocation) {
const applyBoxId = getApplyBoxId({
threadId: chatMessageLocation.threadId,
messageIdx: chatMessageLocation.messageIdx,
tokenIdx: tokenIdx,
})
return <BlockCodeWithApply
initValue={t.text}
language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]}
buttonsOnHover={applyBoxId && <ApplyBlockHoverButtons applyBoxId={applyBoxId} codeStr={t.text} />}
language={language}
applyBoxId={applyBoxId}
/>
</div>
}
return <BlockCode
initValue={t.text}
language={language}
/>
}
if (t.type === "heading") {
@ -213,7 +223,7 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
return <li>
<input type="checkbox" checked={t.checked} readOnly />
<span>
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} nested={true} />
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} nested={true} {...options} />
</span>
</li>
}
@ -229,7 +239,7 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
<input type="checkbox" checked={item.checked} readOnly />
)}
<span>
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} nested={true} />
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} nested={true} {...options} />
</span>
</li>
))}
@ -244,6 +254,7 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
token={token}
tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} // assign a unique tokenId to nested components
chatMessageLocation={chatMessageLocation}
{...options}
/>
))}
</>
@ -304,12 +315,15 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
// inline code
if (t.type === "codespan") {
if (chatMessageLocation) {
console.log('isLinkDetectionEnabled', options.isLinkDetectionEnabled)
if (options.isLinkDetectionEnabled && chatMessageLocation) {
return <CodespanWithLink
text={t.text}
rawText={t.raw}
chatMessageLocation={chatMessageLocation}
/>
}
return <Codespan text={t.text} />
@ -331,12 +345,12 @@ const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token:
)
}
export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocation }: { string: string, nested?: boolean, chatMessageLocation: ChatMessageLocation | undefined }) => {
export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocation, ...options }: { string: string, nested?: boolean, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => {
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
return (
<>
{tokens.map((token, index) => (
<RenderToken key={index} token={token} nested={nested} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} />
<RenderToken key={index} token={token} nested={nested} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} {...options} />
))}
</>
)

View file

@ -714,7 +714,7 @@ const DropdownComponent = ({
// the py-1 here makes sure all elements in the container have py-2 total. this makes a nice animation effect during transition.
className={`overflow-hidden transition-all duration-200 ease-in-out ${isExpanded ? 'opacity-100 py-1' : 'max-h-0 opacity-0'}`}
>
<div className="text-void-fg-4 px-2 py-1 bg-black bg-opacity-20 border border-void-border-4 border-opacity-50 rounded-sm">
<div className="text-xs text-void-fg-4 px-2 py-1 bg-black bg-opacity-20 border border-void-border-4 border-opacity-50 rounded-sm">
{children}
</div>
</div>
@ -971,6 +971,8 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast
<ChatMarkdownRender
string={reasoningStr}
chatMessageLocation={chatMessageLocation}
isApplyEnabled={false}
isLinkDetectionEnabled={true}
/>
</DropdownComponent>}
@ -978,6 +980,8 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast
<ChatMarkdownRender
string={chatMessage.content || ''}
chatMessageLocation={chatMessageLocation}
isApplyEnabled={true}
isLinkDetectionEnabled={true}
/>
{/* loading indicator */}
@ -1009,7 +1013,7 @@ const ToolError = ({ title, desc1, errorMessage }: { title: string, desc1: strin
</span>
}
>
<div className='text-xs text-wrap whitespace-pre-wrap break-all break-words'>{errorMessage}</div>
<div className='text-wrap whitespace-pre-wrap break-all break-words'>{errorMessage}</div>
</DropdownComponent>
)
@ -1028,34 +1032,34 @@ const toolNameToTitle: Record<ToolName, string> = {
}
const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined): string => {
if (_toolParams === undefined) {
if (!_toolParams) {
return '';
}
if (toolName === 'read_file') {
const toolParams = _toolParams as ToolCallParams['read_file']
return toolParams ? getBasename(toolParams.uri.fsPath) : '';
return getBasename(toolParams.uri.fsPath);
} else if (toolName === 'list_dir') {
const toolParams = _toolParams as ToolCallParams['list_dir']
return toolParams ? `${getBasename(toolParams.rootURI.fsPath)}/` : '';
return `${getBasename(toolParams.rootURI.fsPath)}/`;
} else if (toolName === 'pathname_search') {
const toolParams = _toolParams as ToolCallParams['pathname_search']
return toolParams ? `"${toolParams.queryStr}"` : '';
return `"${toolParams.queryStr}"`;
} else if (toolName === 'search') {
const toolParams = _toolParams as ToolCallParams['search']
return toolParams ? `"${toolParams.queryStr}"` : '';
return `"${toolParams.queryStr}"`;
} else if (toolName === 'create_uri') {
const toolParams = _toolParams as ToolCallParams['create_uri']
return toolParams ? getBasename(toolParams.uri.fsPath) : '';
return getBasename(toolParams.uri.fsPath);
} else if (toolName === 'delete_uri') {
const toolParams = _toolParams as ToolCallParams['delete_uri']
return toolParams ? getBasename(toolParams.uri.fsPath) + ' (deleted)' : '';
return getBasename(toolParams.uri.fsPath) + ' (deleted)';
} else if (toolName === 'edit') {
const toolParams = _toolParams as ToolCallParams['edit']
return toolParams ? getBasename(toolParams.uri.fsPath) : '';
return getBasename(toolParams.uri.fsPath);
} else if (toolName === 'terminal_command') {
const toolParams = _toolParams as ToolCallParams['terminal_command']
return toolParams ? `"${toolParams.command}"` : '';
return `"${toolParams.command}"`;
} else {
return ''
}
@ -1063,13 +1067,22 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
const ToolRequestAcceptRejectButtons = ({ toolRequest }: { toolRequest: ToolRequestApproval<ToolName> }) => {
const ToolRequestAcceptRejectButtons = ({ toolRequest, messageIdx, isLast, }: { toolRequest: ToolRequestApproval<ToolName> } & Omit<ChatBubbleProps, 'chatMessage'>) => {
const accessor = useAccessor()
const chatThreadsService = accessor.get('IChatThreadService')
return <>
<div className='text-void-fg-4 italic' onClick={() => { chatThreadsService.approveTool(toolRequest.voidToolId) }}>Accept</div>
<div className='text-void-fg-4 italic' onClick={() => { chatThreadsService.rejectTool(toolRequest.voidToolId) }}>Reject</div>
</>
const initRequestState = isLast ? 'awaiting_response' : 'rejected'
const [requestState, setRequestState] = useState<'accepted' | 'rejected' | 'awaiting_response'>(initRequestState)
if (requestState === 'awaiting_response') {
return <>
<div className='text-void-fg-4 italic' onClick={() => { chatThreadsService.approveTool(toolRequest.voidToolId); setRequestState('accepted') }}>Accept</div>
<div className='text-void-fg-4 italic' onClick={() => { chatThreadsService.rejectTool(toolRequest.voidToolId); setRequestState('rejected') }}>Reject</div>
</>
}
}
const toolNameToComponent: { [T in ToolName]: {
@ -1306,7 +1319,10 @@ const toolNameToComponent: { [T in ToolName]: {
return <DropdownComponent title={title} desc1={desc1}
onClick={() => { commandService.executeCommand('vscode.open', toolRequest.params.uri, { preview: true }) }}
>
<ChatMarkdownRender string={toolRequest.params.changeDescription} chatMessageLocation={undefined} />
<ChatMarkdownRender
string={toolRequest.params.changeDescription}
chatMessageLocation={undefined}
/>
</DropdownComponent>
},
resultWrapper: ({ toolMessage }) => {
@ -1412,10 +1428,10 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubblePr
else if (role === 'tool_request') {
const isLastMessage = true // TODO!!! fix this
if (!isLastMessage) return null
const ToolRequestComponent = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough...
const ToolRequestWrapper = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough...
return <>
<ToolRequestComponent toolRequest={chatMessage} />
<ToolRequestAcceptRejectButtons toolRequest={chatMessage} />
<ToolRequestWrapper toolRequest={chatMessage} />
<ToolRequestAcceptRejectButtons toolRequest={chatMessage} messageIdx={messageIdx} isLast={isLast} />
</>
}
else if (role === 'tool') {
@ -1423,8 +1439,8 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubblePr
const title = toolNameToTitle[chatMessage.name]
// if (chatMessage.result.type === 'error') return <ToolError title={title} params={chatMessage.result.params} errorMessage={chatMessage.result.value} />
const ToolResultComponent = toolNameToComponent[chatMessage.name].resultWrapper as React.FC<{ toolMessage: any }> // ts isnt smart enough...
return <ToolResultComponent toolMessage={chatMessage} />
const ToolResultWrapper = toolNameToComponent[chatMessage.name].resultWrapper as React.FC<{ toolMessage: any }> // ts isnt smart enough...
return <ToolResultWrapper toolMessage={chatMessage} />
}
@ -1524,19 +1540,19 @@ export const SidebarChat = () => {
scrollContainerRef.current?.scrollTo({ top: 0, left: 0 })
}, [isHistoryOpen, currentThread.id])
const numMessages = previousMessages.length + (isStreaming ? 1 : 0)
const pastMessagesHTML = useMemo(() => {
const previousMessagesHTML = useMemo(() => {
return previousMessages.map((message, i) =>
<ChatBubble key={getChatBubbleId(currentThread.id, i)} chatMessage={message} messageIdx={i} isLast={!isStreaming} />
<ChatBubble key={getChatBubbleId(currentThread.id, i)} chatMessage={message} messageIdx={i} isLast={i === numMessages - 1} />
)
}, [previousMessages, currentThread])
const streamingChatIdx = pastMessagesHTML.length
const streamingChatIdx = previousMessagesHTML.length
const currStreamingMessageHTML = !!(reasoningSoFar || messageSoFar || isStreaming) ?
<ChatBubble key={getChatBubbleId(currentThread.id, streamingChatIdx)}
messageIdx={streamingChatIdx} chatMessage={{
messageIdx={streamingChatIdx}
chatMessage={{
role: 'assistant',
content: messageSoFar ?? '',
reasoning: reasoningSoFar ?? '',
@ -1546,8 +1562,7 @@ export const SidebarChat = () => {
isLast={true}
/> : null
const allMessagesHTML = [...pastMessagesHTML, currStreamingMessageHTML]
const allMessagesHTML = [...previousMessagesHTML, currStreamingMessageHTML]
const threadSelector = <div ref={historyRef}
className={`w-full h-auto ${isHistoryOpen ? '' : 'hidden'} ring-2 ring-widget-shadow ring-inset z-10`}
@ -1555,8 +1570,6 @@ export const SidebarChat = () => {
<SidebarThreadSelector />
</div>
const messagesHTML = <ScrollToBottomContainer
key={currentThread.id} // force rerender on all children if id changes
scrollContainerRef={scrollContainerRef}
@ -1566,7 +1579,7 @@ export const SidebarChat = () => {
w-full h-auto
overflow-x-hidden
overflow-y-auto
${pastMessagesHTML.length === 0 && !messageSoFar ? 'hidden' : ''}
${previousMessagesHTML.length === 0 && !messageSoFar ? 'hidden' : ''}
`}
style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - chatAreaDimensions.height - (25) }} // the height of the previousMessages is determined by all other heights
>
@ -1608,7 +1621,7 @@ export const SidebarChat = () => {
isStreaming={isStreaming}
isDisabled={isDisabled}
showSelections={true}
showProspectiveSelections={pastMessagesHTML.length === 0}
showProspectiveSelections={previousMessagesHTML.length === 0}
selections={selections}
setSelections={setSelections}
onClickAnywhere={() => { textAreaRef.current?.focus() }}

View file

@ -39,12 +39,12 @@ export enum ThemeSettings {
}
export enum ThemeSettingDefaults {
COLOR_THEME_DARK = 'Default Dark+',
COLOR_THEME_DARK = 'Default Dark+', // Void changed this from 'Default Dark Modern'
COLOR_THEME_LIGHT = 'Default Light Modern',
COLOR_THEME_HC_DARK = 'Default High Contrast',
COLOR_THEME_HC_LIGHT = 'Default High Contrast Light',
COLOR_THEME_DARK_OLD = 'Default Dark Modern',
COLOR_THEME_DARK_OLD = 'Default Dark Modern', // Void changed this from 'Default Dark+'
COLOR_THEME_LIGHT_OLD = 'Default Light+',
FILE_ICON_THEME = 'vs-seti',