mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
styles + fixes
This commit is contained in:
parent
f2caa9876c
commit
f64f6b54b4
7 changed files with 73 additions and 69 deletions
|
|
@ -379,10 +379,10 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
name: 'create_uri',
|
||||
id: 'tool-6',
|
||||
paramsStr: '{"uri": "/Users/username/Project/new-file.js"}',
|
||||
content: 'Successfully created file at /Users/username/Project/new-file.js',
|
||||
content: 'Successfully created file at /Users/username/Project/new-file/',
|
||||
result: {
|
||||
type: 'success',
|
||||
params: { uri: URI.file('Users/andrew/Desktop/void/src/vs/workbench/hi'), isFolder: false },
|
||||
params: { uri: URI.file('Users/andrew/Desktop/void/src/vs/workbench/hi/'), isFolder: true },
|
||||
value: {}
|
||||
},
|
||||
} satisfies ToolMessage<'create_uri'>,
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: stri
|
|||
if (currStreamState === 'streaming') {
|
||||
buttonsHTML = <>
|
||||
<JumpToFileButton uri={uri} />
|
||||
{copyButton}
|
||||
{stopButton}
|
||||
</>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { BlockCodeApplyWrapper } from './ApplyBlockHoverButtons.js'
|
|||
import { useAccessor } from '../util/services.js'
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { getBasename } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { isAbsolute } from '../../../../../../../base/common/path.js'
|
||||
import { separateOutFirstLine } from '../../../../common/helpers/util.js'
|
||||
import { BlockCode } from '../util/inputs.js'
|
||||
|
|
@ -110,7 +109,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
|||
|
||||
|
||||
export type RenderTokenOptions = { isApplyEnabled?: boolean, isLinkDetectionEnabled?: boolean }
|
||||
const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, inPTag?: boolean, codeURI?: URI, chatMessageLocation?: ChatMessageLocation, tokenIdx: string, } & RenderTokenOptions): JSX.Element => {
|
||||
const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, inPTag?: boolean, codeURI?: URI, chatMessageLocation?: ChatMessageLocation, tokenIdx: string, } & RenderTokenOptions): React.ReactNode => {
|
||||
const accessor = useAccessor()
|
||||
const languageService = accessor.get('ILanguageService')
|
||||
|
||||
|
|
@ -118,7 +117,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
|||
const t = token as MarkedToken
|
||||
|
||||
if (t.raw.trim() === '') {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (t.type === "space") {
|
||||
|
|
@ -127,9 +126,11 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
|||
|
||||
if (t.type === "code") {
|
||||
const [firstLine, remainingContents] = separateOutFirstLine(t.text)
|
||||
const firstLineIsURI = isValidUri(firstLine)
|
||||
const firstLineIsURI = isValidUri(firstLine) && !codeURI
|
||||
const contents = firstLineIsURI ? (remainingContents?.trimStart() || '') : t.text // exclude first-line URI from contents
|
||||
|
||||
if (!contents) return null
|
||||
|
||||
// figure out langauge and URI
|
||||
let uri: URI | null
|
||||
let language: string
|
||||
|
|
@ -171,6 +172,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
|||
/>
|
||||
</BlockCodeApplyWrapper>
|
||||
}
|
||||
|
||||
return <BlockCode
|
||||
initValue={contents}
|
||||
language={language}
|
||||
|
|
@ -382,6 +384,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
|||
|
||||
|
||||
export const ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options }: { string: string, inPTag?: boolean, codeURI?: URI, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => {
|
||||
console.log('STRING!!!', string)
|
||||
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -484,7 +484,18 @@ const ScrollToBottomContainer = ({ children, className, style, scrollContainerRe
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getFolderName = (pathStr: string) => {
|
||||
// 'unixify' path
|
||||
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
|
||||
// Get the last two parts
|
||||
const lastTwo = nonEmptyParts.slice(-2)
|
||||
return lastTwo.join('/') + '/'
|
||||
}
|
||||
|
||||
export const getBasename = (pathStr: string) => {
|
||||
// 'unixify' path
|
||||
|
|
@ -711,7 +722,7 @@ const ToolHeaderWrapper = ({
|
|||
<div className="w-full border border-void-border-3 rounded px-2 py-1 bg-void-bg-3 overflow-hidden ">
|
||||
{/* header */}
|
||||
<div
|
||||
className={`select-none flex items-center min-h-[24px] ${isClickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''} ${!isDropdown ? 'mx-1' : ''}`}
|
||||
className={`select-none flex items-center min-h-[24px] ${isClickable ? 'cursor-pointer' : ''} ${!isDropdown ? 'mx-1' : ''}`}
|
||||
onClick={() => {
|
||||
if (isDropdown) { setIsOpen(v => !v); }
|
||||
if (onClick) { onClick(); }
|
||||
|
|
@ -724,7 +735,7 @@ const ToolHeaderWrapper = ({
|
|||
)}
|
||||
<div className={`flex items-center w-full gap-x-2 overflow-hidden justify-between ${isRejected ? 'line-through' : ''}`}>
|
||||
{/* left */}
|
||||
<div className="flex items-center gap-x-2 min-w-0 overflow-hidden">
|
||||
<div className={`flex items-center gap-x-2 min-w-0 overflow-hidden ${isClickable ? 'hover:brightness-125 transition-all duration-150' : ''}`}>
|
||||
<span className="text-void-fg-3 flex-shrink-0">{title}</span>
|
||||
<span className="text-void-fg-4 text-xs italic truncate">{desc1}</span>
|
||||
</div>
|
||||
|
|
@ -1105,16 +1116,19 @@ const ReasoningWrapper = ({ isDoneReasoning, isStreaming, children }: { isDoneRe
|
|||
|
||||
|
||||
// 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 toolNameToTitle: Record<ToolName, { past: string, proposed: string }> = {
|
||||
|
||||
const folderFileStr = (isFolder: boolean) => isFolder ? 'folder' : 'file'
|
||||
const toolNameToTitle = {
|
||||
'read_file': { past: 'Read file', proposed: 'Read file' },
|
||||
'list_dir': { past: 'Inspected folder', proposed: 'Inspect folder' },
|
||||
'pathname_search': { past: 'Searched by file name', proposed: 'Search by file name' },
|
||||
'search': { past: 'Searched', proposed: 'Search' },
|
||||
'create_uri': { past: 'Created file', proposed: 'Create file' },
|
||||
'delete_uri': { past: 'Deleted file', proposed: 'Delete file' },
|
||||
'create_uri': { past: (isFolder: boolean) => `Created ${folderFileStr(isFolder)}`, proposed: (isFolder: boolean) => `Create ${folderFileStr(isFolder)}` },
|
||||
'delete_uri': { past: (isFolder: boolean) => `Deleted ${folderFileStr(isFolder)}`, proposed: (isFolder: boolean) => `Delete ${folderFileStr(isFolder)}` },
|
||||
'edit': { past: 'Edited file', proposed: 'Edit file' },
|
||||
'terminal_command': { past: 'Ran terminal command', proposed: 'Run terminal command' }
|
||||
}
|
||||
} as const satisfies Record<ToolName, { past: any, proposed: any }>
|
||||
|
||||
const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined): string => {
|
||||
|
||||
if (!_toolParams) {
|
||||
|
|
@ -1135,10 +1149,10 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
|
|||
return `"${toolParams.queryStr}"`;
|
||||
} else if (toolName === 'create_uri') {
|
||||
const toolParams = _toolParams as ToolCallParams['create_uri']
|
||||
return getBasename(toolParams.uri.fsPath);
|
||||
return toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) : getBasename(toolParams.uri.fsPath);
|
||||
} else if (toolName === 'delete_uri') {
|
||||
const toolParams = _toolParams as ToolCallParams['delete_uri']
|
||||
return getBasename(toolParams.uri.fsPath);
|
||||
return toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) : getBasename(toolParams.uri.fsPath);
|
||||
} else if (toolName === 'edit') {
|
||||
const toolParams = _toolParams as ToolCallParams['edit']
|
||||
return getBasename(toolParams.uri.fsPath);
|
||||
|
|
@ -1242,31 +1256,6 @@ const EditToolApplyButton = ({ changeDescription, applyBoxId, uri }: { changeDes
|
|||
}
|
||||
|
||||
|
||||
const TerminalToolChildren = ({ command, terminalId, result, resolveReason }: { command: string, terminalId: string, result: string, resolveReason: ResolveReason }) => {
|
||||
const accessor = useAccessor()
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
|
||||
const resultStr = resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null)
|
||||
: resolveReason.type === 'bgtask' ? null :
|
||||
resolveReason.type === 'timeout' ? `\n(partial results; request timed out)` :
|
||||
resolveReason.type === 'toofull' ? `\n(truncated)`
|
||||
: null
|
||||
|
||||
return <ToolContentsWrapper className='bg-void-bg-3 font-mono whitespace-pre text-nowrap overflow-auto text-sm'>
|
||||
<ListableToolItem
|
||||
showDot={false}
|
||||
name={`$ ${command}`}
|
||||
className='w-full overflow-auto py-1'
|
||||
onClick={() => terminalToolsService.openTerminal(terminalId)}
|
||||
/>
|
||||
<div className='!select-text cursor-auto'>
|
||||
{resolveReason.type === 'bgtask' ? 'Result so far:\n' : null}
|
||||
{result}
|
||||
{resultStr}
|
||||
</div>
|
||||
</ToolContentsWrapper>
|
||||
}
|
||||
|
||||
const EditToolChildren = ({ uri, changeDescription }: { uri: URI, changeDescription: string }) => {
|
||||
return <ToolContentsWrapper className='bg-void-bg-3'>
|
||||
<div className='!select-text cursor-auto'>
|
||||
|
|
@ -1334,7 +1323,7 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
: <ToolContentsWrapper>
|
||||
{value.children.map((child, i) => (<ListableToolItem key={i}
|
||||
name={`${child.name}${child.isDirectory ? '/' : ''}`}
|
||||
className='w-full overflow-auto py-1'
|
||||
className='w-full overflow-auto'
|
||||
onClick={() => {
|
||||
commandService.executeCommand('vscode.open', child.uri, { preview: true })
|
||||
// commandService.executeCommand('workbench.view.explorer'); // open in explorer folders view instead
|
||||
|
|
@ -1342,7 +1331,7 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
}}
|
||||
/>))}
|
||||
{value.hasNextPage &&
|
||||
<ListableToolItem name={`Results truncated (${value.itemsRemaining} remaining).`} isSmall={true} className='w-full overflow-auto py-1' />
|
||||
<ListableToolItem name={`Results truncated (${value.itemsRemaining} remaining).`} isSmall={true} className='w-full overflow-auto' />
|
||||
}
|
||||
</ToolContentsWrapper>
|
||||
}
|
||||
|
|
@ -1376,11 +1365,11 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
: <ToolContentsWrapper>
|
||||
{value.uris.map((uri, i) => (<ListableToolItem key={i}
|
||||
name={getBasename(uri.fsPath)}
|
||||
className='w-full overflow-auto py-1'
|
||||
className='w-full overflow-auto'
|
||||
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
|
||||
/>))}
|
||||
{value.hasNextPage &&
|
||||
<ListableToolItem name={'Results truncated.'} isSmall={true} className='w-full overflow-auto py-1' />
|
||||
<ListableToolItem name={'Results truncated.'} isSmall={true} className='w-full overflow-auto' />
|
||||
}
|
||||
|
||||
</ToolContentsWrapper>
|
||||
|
|
@ -1415,11 +1404,11 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
: <ToolContentsWrapper>
|
||||
{value.uris.map((uri, i) => (<ListableToolItem key={i}
|
||||
name={getBasename(uri.fsPath)}
|
||||
className='w-full overflow-auto py-1'
|
||||
className='w-full overflow-auto'
|
||||
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
|
||||
/>))}
|
||||
{value.hasNextPage &&
|
||||
<ListableToolItem name={`Results truncated.`} isSmall={true} className='w-full overflow-auto py-1' />
|
||||
<ListableToolItem name={`Results truncated.`} isSmall={true} className='w-full overflow-auto' />
|
||||
}
|
||||
|
||||
</ToolContentsWrapper>
|
||||
|
|
@ -1440,26 +1429,19 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const explorerService = accessor.get('IExplorerService')
|
||||
const title = toolNameToTitle[toolRequest.name].proposed
|
||||
const title = toolNameToTitle[toolRequest.name].proposed(toolRequest.params.isFolder)
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
const icon = null
|
||||
|
||||
const isError = false
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, }
|
||||
|
||||
const { params } = toolRequest
|
||||
|
||||
// TODO!!! would be cool to open up the lowest parent that exists
|
||||
// componentParams.onClick = () => {
|
||||
// // open the parent
|
||||
// }
|
||||
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolMessage.name].past
|
||||
const title = toolNameToTitle[toolMessage.name].past(toolMessage.result.params?.isFolder ?? false)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1489,7 +1471,7 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
requestWrapper: ({ toolRequest, }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name].proposed
|
||||
const title = toolNameToTitle[toolRequest.name].proposed(toolRequest.params.isFolder)
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1504,7 +1486,8 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].past : toolNameToTitle[toolMessage.name].proposed
|
||||
const isFolder = toolMessage.result.params?.isFolder ?? false
|
||||
const title = toolMessage.result.type === 'success' ? toolNameToTitle[toolMessage.name].past(isFolder) : toolNameToTitle[toolMessage.name].proposed(isFolder)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
|
|
@ -1626,12 +1609,26 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
const { command } = toolMessage.result.params
|
||||
const { terminalId, resolveReason, result } = toolMessage.result.value
|
||||
|
||||
componentParams.children = <TerminalToolChildren
|
||||
command={command}
|
||||
terminalId={terminalId}
|
||||
result={result}
|
||||
resolveReason={resolveReason}
|
||||
/>
|
||||
const resultStr = resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null)
|
||||
: resolveReason.type === 'bgtask' ? null :
|
||||
resolveReason.type === 'timeout' ? `\n(partial results; request timed out)` :
|
||||
resolveReason.type === 'toofull' ? `\n(truncated)`
|
||||
: null
|
||||
|
||||
componentParams.children = <ToolContentsWrapper className='bg-void-bg-3 font-mono whitespace-pre text-nowrap overflow-auto text-sm'>
|
||||
<ListableToolItem
|
||||
showDot={false}
|
||||
name={`$ ${command}`}
|
||||
className='w-full overflow-auto py-1'
|
||||
onClick={() => terminalToolsService.openTerminal(terminalId)}
|
||||
/>
|
||||
<div className='!select-text cursor-auto'>
|
||||
{resolveReason.type === 'bgtask' ? 'Result so far:\n' : null}
|
||||
{result}
|
||||
{resultStr}
|
||||
</div>
|
||||
</ToolContentsWrapper>
|
||||
|
||||
|
||||
if (resolveReason.type === 'bgtask')
|
||||
componentParams.desc2 = '(background task)'
|
||||
|
|
@ -1683,7 +1680,7 @@ const ChatBubble = ({ chatMessage, isCommitted, messageIdx, isLast }: ChatBubble
|
|||
}
|
||||
else if (role === 'tool_request') {
|
||||
const ToolRequestWrapper = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough...
|
||||
if (ToolRequestWrapper && isLast) { // if it's the last message
|
||||
if (ToolRequestWrapper) { // && isLast // if it's the last message
|
||||
return <>
|
||||
<ToolRequestWrapper toolRequest={chatMessage} />
|
||||
<ToolRequestAcceptRejectButtons />
|
||||
|
|
|
|||
|
|
@ -318,8 +318,11 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
// ---
|
||||
|
||||
create_uri: async ({ uri }) => {
|
||||
await fileService.createFile(uri)
|
||||
create_uri: async ({ uri, isFolder }) => {
|
||||
if (isFolder)
|
||||
await fileService.createFolder(uri)
|
||||
else
|
||||
await fileService.createFile(uri)
|
||||
return {}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const editToolDesc_toolDescription = `\
|
|||
A high level description of the change you'd like to make in the file. This description will be handed to a dumber, faster model that will quickly apply the change.\
|
||||
The model does not have ANY context except the file content and this description, so make sure to include all necessary information to make the change here.\
|
||||
Typically the best description you can give is a code block of the form:\n${tripleTick[0]}\n// ... existing code ...\n{{change 1}}\n// ... existing code ...\n{{change2}}\n// ... existing code ...\n{{change 3}}\n...\n${tripleTick[1]}. \
|
||||
Wrap your output in triple ticks. Do NOT output the whole file here if possible, and try to write as LITTLE code as needed to describe the change.`
|
||||
Wrap all code in triple backticks. Do NOT output the whole file here if possible, and try to write as LITTLE code as needed to describe the change.`
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export const voidTools = {
|
|||
|
||||
create_uri: {
|
||||
name: 'create_uri',
|
||||
description: `Creates a file or folder at the given path. To create a folder, ensure the path ends with a trailing slash. Fails gracefully if the file already exists. Missing ancestors in the path will be recursively created automatically.`,
|
||||
description: `Create a file or folder at the given path. To create a folder, ensure the path ends with a trailing slash. Fails gracefully if the file already exists. Missing ancestors in the path will be recursively created automatically.`,
|
||||
params: {
|
||||
uri: { type: 'string', description: undefined },
|
||||
},
|
||||
|
|
@ -92,7 +92,7 @@ export const voidTools = {
|
|||
|
||||
delete_uri: {
|
||||
name: 'delete_uri',
|
||||
description: `Deletes the file or folder at the given path. Fails gracefully if the file or folder does not exist.`,
|
||||
description: `Delete a file or folder at the given path. Fails gracefully if the file or folder does not exist.`,
|
||||
params: {
|
||||
uri: { type: 'string', description: undefined },
|
||||
params: { type: 'string', description: 'Return -r here to delete this URI and all descendants (if applicable). Default is the empty string.' }
|
||||
|
|
|
|||
Loading…
Reference in a new issue