style updates progress

This commit is contained in:
Andrew Pareles 2025-03-15 01:17:13 -07:00
parent dbdd40bd3d
commit 8a6b75b6bb
8 changed files with 458 additions and 208 deletions

View file

@ -237,7 +237,228 @@ class ChatThreadService extends Disposable implements IChatThreadService {
if (!threadsStr) {
return null
}
return this._convertThreadDataFromStorage(threadsStr);
const threads = this._convertThreadDataFromStorage(threadsStr);
// threads['abc'] = {
// id: 'abc',
// createdAt: new Date().toISOString(),
// lastModified: new Date().toISOString(),
// messages: [
// {
// role: 'tool',
// name: 'pathname_search',
// id: 'tool-1',
// paramsStr: '{"query": "hello", "pageNumber": 0}',
// content: '/users/andrew/void/Desktop/etc/abc.txt',
// result: { type: 'success', params: { queryStr: 'hello', pageNumber: 0 }, value: { uris: [URI.file('/Users/username/Downloads/helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.txt'), URI.file('/Users/username/Downloads/hello1.txt'), URI.file('/Users/username/Downloads/hello2.txt'), URI.file('/Users/username/Downloads/hello3.txt'), URI.file('/Users/username/hello.txt')], hasNextPage: true } },
// } satisfies ToolMessage<'pathname_search'>,
// {
// role: 'tool',
// name: 'pathname_search',
// id: 'tool-1',
// paramsStr: '{"query": "hello", "pageNumber": 0}',
// content: '/users/andrew/void/Desktop/etc/abc.txt',
// result: { type: 'success', params: { queryStr: 'hello', pageNumber: 0 }, value: { uris: [], hasNextPage: false } },
// } satisfies ToolMessage<'pathname_search'>,
// // {
// // role: 'tool_request',
// // name: 'pathname_search',
// // params: { queryStr: 'hello', pageNumber: 0 },
// // paramsStr: '{"query": "hello", "pageNumber": 0}',
// // voidToolId: 'request-1',
// // } satisfies ToolRequestApproval<'pathname_search'>,
// {
// role: 'tool',
// name: 'list_dir',
// id: 'tool-2',
// paramsStr: '{"uri": "/Users/username/Documents"}',
// content: 'Directory listing of /Users/username/Documents',
// result: {
// type: 'success',
// params: { rootURI: URI.file('/Users/username/Documents'), pageNumber: 1, },
// value: {
// children: [
// { uri: URI.file('/Users/username/Documents/file1.txt'), name: 'file1.txt', isDirectory: false, isSymbolicLink: false },
// { uri: URI.file('/Users/username/Documents/folder1'), name: 'folder1', isDirectory: true, isSymbolicLink: false }
// ],
// hasNextPage: true,
// hasPrevPage: true,
// itemsRemaining: 5,
// }
// },
// } satisfies ToolMessage<'list_dir'>,
// // {
// // role: 'tool_request',
// // name: 'list_dir',
// // params: { rootURI: URI.file('/Users/username/Documents'), pageNumber: 0 },
// // paramsStr: '{"uri": "/Users/username/Documents"}',
// // voidToolId: 'request-2',
// // } satisfies ToolRequestApproval<'list_dir'>,
// {
// role: 'tool',
// name: 'read_file',
// id: 'tool-3',
// paramsStr: '{"uri": "/Users/username/Documents/file1.txt"}',
// content: 'Content of file1.txt\nThis is a sample file.\nHello world!',
// result: {
// type: 'success',
// params: { uri: URI.file('/Users/username/Documents/file1.txt'), pageNumber: 0 },
// value: { fileContents: 'Content of file1.txt\nThis is a sample file.\nHello world!', hasNextPage: false }
// },
// } satisfies ToolMessage<'read_file'>,
// // {
// // role: 'tool_request',
// // name: 'read_file',
// // params: { uri: URI.file('/Users/username/Documents/file1.txt'), pageNumber: 0 },
// // paramsStr: '{"uri": "/Users/username/Documents/file1.txt"}',
// // voidToolId: 'request-3',
// // } satisfies ToolRequestApproval<'read_file'>,
// {
// role: 'tool',
// name: 'search',
// id: 'tool-4',
// paramsStr: '{"query": "function main"}',
// content: 'Found matches in 3 files',
// result: {
// type: 'success',
// params: { queryStr: 'function main', pageNumber: 0 },
// value: {
// uris: [
// URI.file('/Users/username/Project/main.js'),
// URI.file('/Users/username/Project/src/app.js'),
// URI.file('/Users/username/Project/test/test.js')
// ],
// hasNextPage: false
// }
// },
// } satisfies ToolMessage<'search'>,
// // {
// // role: 'tool_request',
// // name: 'search',
// // params: { queryStr: 'function main', pageNumber: 0 },
// // paramsStr: '{"query": "function main"}',
// // voidToolId: 'request-4',
// // } satisfies ToolRequestApproval<'search'>,
// // ---
// {
// role: 'tool',
// name: 'edit',
// id: 'tool-5',
// paramsStr: '{"uri": "/Users/username/Project/main.js", "changeDescription": "Add console.log statement"}',
// content: 'Successfully edited the file at /Users/username/Project/main.js',
// result: {
// type: 'success',
// params: { uri: URI.file('/Users/username/Project/main.js'), changeDescription: 'Add console.log statement' },
// value: {}
// },
// } satisfies ToolMessage<'edit'>,
// {
// role: 'tool_request',
// name: 'edit',
// params: { uri: URI.file('/Users/username/Project/main.js'), changeDescription: 'Add console.log statement' },
// paramsStr: '{"uri": "/Users/username/Project/main.js", "changeDescription": "Add console.log statement"}',
// voidToolId: 'request-5',
// } satisfies ToolRequestApproval<'edit'>,
// {
// role: 'tool',
// 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',
// result: {
// type: 'success',
// params: { uri: URI.file('/Users/username/Project/new-file.js'), isFolder: false },
// value: {}
// },
// } satisfies ToolMessage<'create_uri'>,
// {
// role: 'tool_request',
// name: 'create_uri',
// params: { uri: URI.file('/Users/username/Project/new-file.js'), isFolder: false },
// paramsStr: '{"uri": "/Users/username/Project/new-file.js"}',
// voidToolId: 'request-6',
// } satisfies ToolRequestApproval<'create_uri'>,
// {
// role: 'tool',
// name: 'delete_uri',
// id: 'tool-7',
// paramsStr: '{"uri": "/Users/username/Project/old-file.js", "params": ""}',
// content: 'Successfully deleted file at /Users/username/Project/old-file.js',
// result: {
// type: 'success',
// params: { uri: URI.file('/Users/username/Project/old-file.js'), isRecursive: false, isFolder: false },
// value: {}
// },
// } satisfies ToolMessage<'delete_uri'>,
// {
// role: 'tool_request',
// name: 'delete_uri',
// params: { uri: URI.file('/Users/username/Project/old-file.js'), isRecursive: false, isFolder: false },
// paramsStr: '{"uri": "/Users/username/Project/old-file.js", "params": ""}',
// voidToolId: 'request-7',
// } satisfies ToolRequestApproval<'delete_uri'>,
// {
// role: 'tool',
// name: 'terminal_command',
// id: 'tool-8',
// paramsStr: '{"command": "npm install", "waitForCompletion": "true"}',
// content: 'Command executed: npm install\nAdded 123 packages in 3.5s',
// result: {
// type: 'success',
// params: { command: 'npm install', proposedTerminalId: '1', waitForCompletion: true },
// value: {
// terminalId: '1',
// didCreateTerminal: false,
// result: 'Added 123 packages in 3.5s',
// resolveReason: { type: 'done', exitCode: 0 }
// }
// },
// } satisfies ToolMessage<'terminal_command'>,
// {
// role: 'tool_request',
// name: 'terminal_command',
// params: { command: 'npm install', proposedTerminalId: '1', waitForCompletion: true },
// paramsStr: '{"command": "npm install", "waitForCompletion": "true"}',
// voidToolId: 'request-8',
// } satisfies ToolRequestApproval<'terminal_command'>,
// // Examples of error and rejected states
// {
// role: 'tool',
// name: 'pathname_search',
// id: 'tool-error',
// paramsStr: '{"query": "invalid**query"}',
// content: 'Error: Invalid search pattern',
// result: { type: 'error', params: { queryStr: 'invalid**query', pageNumber: 0 }, value: 'Error: Invalid search pattern' },
// } satisfies ToolMessage<'pathname_search'>,
// {
// role: 'tool',
// name: 'pathname_search',
// id: 'tool-rejected',
// paramsStr: '{"query": "sensitive-data"}',
// content: 'Tool call was rejected by the user.',
// result: { type: 'rejected', params: { queryStr: 'sensitive-data', pageNumber: 0 } },
// } satisfies ToolMessage<'pathname_search'>,
// ],
// state: defaultThreadState,
// }
return threads
}
private _storeAllThreads(threads: ChatThreads) {

View file

@ -83,7 +83,7 @@
.void-scrollable-element::-webkit-scrollbar,
.void-scrollable-element *::-webkit-scrollbar {
width: 14px !important;
height: 14px !important;
height: 4px !important;
}
.void-scrollable-element::-webkit-scrollbar-track,

View file

@ -5,6 +5,8 @@ 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'
import { ToolContentsWrapper } from '../sidebar-tsx/SidebarChat.js'
import { ChatMarkdownRender } from './ChatMarkdownRender.js'
enum CopyButtonText {
Idle = 'Copy',
@ -27,7 +29,8 @@ export const IconShell1 = ({ onClick, title, Icon, disabled, className }: IconBu
disabled={disabled}
onClick={onClick}
className={`
size-[24px]
size-[22px]
p-[4px]
flex items-center justify-center
text-sm bg-void-bg-3 text-void-fg-1
hover:brightness-110
@ -36,28 +39,28 @@ export const IconShell1 = ({ onClick, title, Icon, disabled, className }: IconBu
${className}
`}
>
<Icon size={16} />
<Icon />
</button>
)
export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => (
<button
title={title}
disabled={disabled}
onClick={onClick}
className={`
size-[24px]
flex items-center justify-center
text-sm
hover:opacity-80
disabled:opacity-50 disabled:cursor-not-allowed
${className}
`}
>
<Icon size={16} />
</button>
)
// export const IconShell2 = ({ onClick, title, Icon, disabled, className }: IconButtonProps) => (
// <button
// title={title}
// disabled={disabled}
// onClick={onClick}
// className={`
// size-[24px]
// flex items-center justify-center
// text-sm
// hover:opacity-80
// disabled:opacity-50 disabled:cursor-not-allowed
// ${className}
// `}
// >
// <Icon size={16} />
// </button>
// )
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
@ -230,17 +233,16 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId }: { codeStr: string, a
</>
}
const statusIndicatorHTML = <div className='flex flex-row gap-2 items-center'>
const statusIndicatorHTML = <div className='flex flex-row items-center size-4'>
<div
className={`size-1.5 rounded-full border
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'
}`
}
>
</div>
/>
</div>
return {
@ -248,5 +250,52 @@ export const useApplyButtonHTML = ({ codeStr, applyBoxId }: { codeStr: string, a
buttonsHTML
}
}
export const BlockCodeApplyWrapper = ({
children,
initValue,
applyBoxId,
language,
canApply,
}: {
initValue: string;
children: React.ReactNode;
applyBoxId: string;
canApply: boolean;
language: string;
}) => {
const { statusIndicatorHTML, buttonsHTML } = useApplyButtonHTML({ codeStr: initValue, applyBoxId })
return <div
className='border border-void-border-3 rounded overflow-hidden bg-void-bg-3'
>
{/* header */}
<div className=" select-none flex justify-between items-center py-1 px-2 border-b border-void-border-3 cursor-default">
<div className="flex items-center">
{statusIndicatorHTML}
<span className="text-[13px] font-light text-void-fg-3">
{language || 'text'}
</span>
</div>
<div className={`${canApply ? '' : 'hidden'} flex gap-1`}>
{buttonsHTML}
</div>
</div>
{/* contents */}
<ToolContentsWrapper>
{children}
</ToolContentsWrapper>
</div>
}

View file

@ -4,54 +4,10 @@
*--------------------------------------------------------------------------------------*/
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>
)
}
import { BlockCodeApplyWrapper, useApplyButtonHTML } from './ApplyBlockHoverButtons.js';
export const BlockCode = ({ ...codeEditorProps }: VoidCodeEditorProps) => {
const isSingleLine = !codeEditorProps.initValue.includes('\n')
return (
<>
<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'}`}>
{buttonsOnHover}
</div>
</div>
)}
<VoidCodeEditor {...codeEditorProps} />
</div> */}
</>
)
return <VoidCodeEditor {...codeEditorProps} />
}

View file

@ -5,12 +5,10 @@
import React, { JSX, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode, BlockCodeWithApply } from './BlockCode.js'
import { BlockCode } from './BlockCode.js'
import { convertToVscodeLang, getFirstLine, getLanguage } from '../../../../common/helpers/getLanguage.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'
import { BlockCodeApplyWrapper, useApplyButtonHTML } from './ApplyBlockHoverButtons.js'
import { useAccessor } from '../util/services.js'
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'
import { URI } from '../../../../../../../base/common/uri.js'
@ -102,7 +100,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
export type RenderTokenOptions = { isApplyEnabled?: boolean, isLinkDetectionEnabled?: boolean }
const RenderToken = ({ token, inPTag, chatMessageLocation, tokenIdx, ...options }: { token: Token | string, inPTag?: boolean, 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): JSX.Element => {
const accessor = useAccessor()
const languageService = accessor.get('ILanguageService')
@ -120,36 +118,45 @@ const RenderToken = ({ token, inPTag, chatMessageLocation, tokenIdx, ...options
if (t.type === "code") {
const [firstLine, remainingContents] = getFirstLine(t.text)
const firstLineIsURI = URI.isUri(firstLine)
let language: string | undefined = undefined
if (t.lang !== undefined) {
// convert markdown language to language that vscode recognizes (eg markdown doesn't know bash but it does know shell)
language = convertToVscodeLang(languageService, t.lang)
}
else if (!language) { // if still no lang
if (firstLineIsURI) { // get lang from the uri
const uri = URI.file(firstLine)
language = getLanguage(languageService, { uri, fileContents: remainingContents ?? undefined })
}
else { // get lang from the contents
language = getLanguage(languageService, { uri: null, fileContents: remainingContents ?? undefined })
}
}
const contents = firstLineIsURI ? (remainingContents || '') : t.text // exclude first-line URI from contents
// TODO!!! user should only be able to apply this when the code has been closed (t.raw ends with "```")
// figure out langauge
let language: string | undefined = undefined
let uri: URI | undefined = undefined
if (t.lang) { // a language was provided. empty string is common so check truthy, not just undefined
uri = codeURI
language = convertToVscodeLang(languageService, t.lang) // convert markdown language to language that vscode recognizes (eg markdown doesn't know bash but it does know shell)
}
else { // no language provided - fallback
if (firstLineIsURI) { // get lang from the uri in the markdown
uri = codeURI ?? URI.file(firstLine)
language = getLanguage(languageService, { uri, fileContents: remainingContents ?? undefined })
}
else { // get lang from the given URI and contents
uri = codeURI
language = getLanguage(languageService, { uri: codeURI ?? null, fileContents: remainingContents ?? undefined })
}
}
if (options.isApplyEnabled && chatMessageLocation) {
const isCodeblockClosed = t.raw.trimEnd().endsWith('```') // user should only be able to Apply when the code has been closed (t.raw ends with "```")
const applyBoxId = getApplyBoxId({
threadId: chatMessageLocation.threadId,
messageIdx: chatMessageLocation.messageIdx,
tokenIdx: tokenIdx,
})
return <BlockCodeWithApply
return <BlockCodeApplyWrapper
canApply={isCodeblockClosed}
applyBoxId={applyBoxId}
initValue={contents}
language={language}
applyBoxId={applyBoxId}
/>
>
<BlockCode
initValue={contents}
language={language}
/>
</BlockCodeApplyWrapper>
}
return <BlockCode
initValue={contents}
@ -239,7 +246,7 @@ const RenderToken = ({ token, inPTag, chatMessageLocation, tokenIdx, ...options
return <li>
<input type="checkbox" checked={t.checked} readOnly />
<span>
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} {...options} />
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
</span>
</li>
}
@ -361,7 +368,7 @@ const RenderToken = ({ token, inPTag, chatMessageLocation, tokenIdx, ...options
}
export const ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options }: { string: string, inPTag?: boolean, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => {
export const ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options }: { string: string, inPTag?: boolean, codeURI?: URI, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => {
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
return (
<>

View file

@ -696,8 +696,7 @@ const ToolHeaderComponent = ({
const isClickable = !!(isDropdown || onClick)
return (<div className=''>
<div className="border border-void-border-3 rounded px-2 py-1 bg-void-bg-2-alt overflow-hidden">
<div className="w-full border border-void-border-3 rounded px-2 py-1 bg-void-bg-2-alt 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' : ''}`}
@ -711,12 +710,14 @@ const ToolHeaderComponent = ({
className={`text-void-fg-3 mr-0.5 h-4 w-4 flex-shrink-0 transition-transform duration-100 ease-[cubic-bezier(0.4,0,0.2,1)] ${isExpanded ? 'rotate-90' : ''}`}
/>
)}
<div className="flex items-center justify-between w-full flex-nowrap whitespace-nowrap gap-x-2">
<div className="flex items-center gap-x-2">
<span className="text-void-fg-3">{title}</span>
<span className="text-void-fg-4 text-xs italic">{desc1}</span>
<div className="flex items-center w-full gap-x-2 overflow-hidden">
<div className="flex items-center gap-x-2 min-w-0 overflow-hidden">
<span className="text-void-fg-3 flex-shrink-0">{title}</span>
{/* Fixed description with proper ellipsis */}
<span className="text-void-fg-4 text-xs italic truncate">{desc1}</span>
</div>
<div className="flex items-center gap-x-2">
<div className="flex items-center gap-x-2 flex-shrink-0">
{desc2 && <span className="text-void-fg-4 text-xs">
{desc2}
</span>}
@ -731,11 +732,10 @@ const ToolHeaderComponent = ({
</div>
{/* children */}
{<div
// 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' : 'max-h-0 opacity-0'}
text-void-fg-4 bg-black bg-opacity-20 border border-void-border-4 border-opacity-50 rounded-sm`}
text-void-fg-4 bg-black bg-opacity-20 border border-void-border-4 border-opacity-50 rounded-sm`}
>
{children || '(no results)'}
{children}
</div>}
</div>
</div>
@ -938,6 +938,29 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubble
}
export const ToolContentsWrapper = ({ children, className }: { children: React.ReactNode, className?: string }) => {
return <div className={`${className ? className : ''} max-h-64 overflow-x-auto cursor-default select-none`}>
<div className='px-2 py-1 min-w-full'>
{children}
</div>
</div>
}
const ListableToolItem = ({ name, onClick, isSmall, className }: { name: string, onClick?: () => void, isSmall?: boolean, className?: string }) => {
return <div
className={`
${onClick ? 'hover:brightness-125 hover:cursor-pointer transition-all duration-200 ' : ''}
flex items-center flex-nowrap whitespace-nowrap
${className ? className : ''}
`}
onClick={onClick}
>
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
<div className={`${isSmall ? 'italic text-sm leading-4 flex items-center' : ''}`}>{name}</div>
</div>
}
const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubbleProps & { chatMessage: ChatMessage & { role: 'assistant' } }) => {
const accessor = useAccessor()
@ -963,7 +986,6 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast
className='
text-void-fg-2
prose
prose-sm
break-words
@ -1014,15 +1036,15 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast
// 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, string> = {
'read_file': 'Read file', // past tense
'list_dir': 'Inspected folder', // past tense
'pathname_search': 'Searched by file name', // past tense
'search': 'Searched', // past tense
'create_uri': 'Created file', // past tense
'delete_uri': 'Deleted file', // past tense
'edit': 'Edited file', // past tense
'terminal_command': 'Ran terminal command' // past tense
const toolNameToTitle: Record<ToolName, { past: string, current: string, proposed: string }> = {
'read_file': { past: 'Read file', current: 'Reading file', proposed: 'Read file' },
'list_dir': { past: 'Inspected folder', current: 'Inspecting folder', proposed: 'Inspect folder' },
'pathname_search': { past: 'Searched by file name', current: 'Searching by file name', proposed: 'Search by file name' },
'search': { past: 'Searched', current: 'Searching', proposed: 'Search' },
'create_uri': { past: 'Created file', current: 'Creating file', proposed: 'Create file' },
'delete_uri': { past: 'Deleted file', current: 'Deleting file', proposed: 'Delete file' },
'edit': { past: 'Edited file', current: 'Editing file', proposed: 'Edit file' },
'terminal_command': { past: 'Ran terminal command', current: 'Running terminal command', proposed: 'Run terminal command' }
}
const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined): string => {
@ -1128,17 +1150,17 @@ const toolNameToComponent: { [T in ToolName]: {
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const { uri } = toolMessage.result.params ?? {}
const desc1 = uri ? getBasename(uri.fsPath) : '';
const icon = null
if (toolMessage.result.type === 'rejected') return null
if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable
const isError = toolMessage.result.type === 'error'
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.result.type !== 'error') {
if (toolMessage.result.type === 'success') {
const { value, params } = toolMessage.result
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
if (toolMessage.result.value.hasNextPage) componentParams.desc2 = `(AI can scroll for more)`
@ -1158,38 +1180,34 @@ const toolNameToComponent: { [T in ToolName]: {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const explorerService = accessor.get('IExplorerService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
const icon = null
if (toolMessage.result.type === 'rejected') return null
if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable
const isError = toolMessage.result.type === 'error'
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.result.type !== 'error') {
if (toolMessage.result.type === 'success') {
const { value, params } = toolMessage.result
componentParams.numResults = value.children?.length
componentParams.children = (value.children?.length ?? 0) === 0 ? null : <>
{value.children?.map((child, i) => (
<div
key={i}
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
componentParams.children = <ToolContentsWrapper>
{!value.children || (value.children.length ?? 0) === 0 ? <>
<ListableToolItem name={'No results found.'} isSmall={true} />
</> : <>
{value.children.map((child, i) => (<ListableToolItem key={i}
name={`${child.name}${child.isDirectory ? '/' : ''}`}
onClick={() => {
commandService.executeCommand('workbench.view.explorer');
explorerService.select(child.uri, true);
}}
>
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
{`${child.name}${child.isDirectory ? '/' : ''}`}
</div>
))}
{value.hasNextPage && (
<div className="italic">
{value.itemsRemaining} more items...
</div>
)}
</>
/>))}
{value.hasNextPage &&
<ListableToolItem name={`Results truncated (${value.itemsRemaining} remaining).`} isSmall={true} />
}
</>}
</ToolContentsWrapper>
}
else {
componentParams.children = <>
@ -1205,37 +1223,31 @@ const toolNameToComponent: { [T in ToolName]: {
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
const icon = null
if (toolMessage.result.type === 'rejected') return null
if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable
const isError = toolMessage.result.type === 'error'
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.result.type !== 'error') {
if (toolMessage.result.type === 'success') {
const { value, params } = toolMessage.result
componentParams.numResults = value.uris.length
componentParams.children = value.uris.length === 0 ? null : <>
{value.uris.map((uri, i) => (
<div
key={i}
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
onClick={() => {
commandService.executeCommand('vscode.open', uri, { preview: true })
}}
>
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
{uri.fsPath.split('/').pop()}
</div>
))}
{value.hasNextPage && (
<div className="italic">
More results available...
</div>
)}
</>
componentParams.children = <ToolContentsWrapper>
{value.uris.length === 0 ? <>
<ListableToolItem name={'No results found.'} isSmall={true} />
</> : <>
{value.uris.map((uri, i) => (<ListableToolItem key={i}
name={getBasename(uri.fsPath)}
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
/>))}
{value.hasNextPage &&
<ListableToolItem name={'Results truncated.'} isSmall={true} />
}
</>}
</ToolContentsWrapper>
}
else {
componentParams.children = <>
@ -1251,30 +1263,31 @@ const toolNameToComponent: { [T in ToolName]: {
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
const icon = null
if (toolMessage.result.type === 'rejected') return null
if (toolMessage.result.type === 'rejected') return null // will never happen, not rejectable
const isError = toolMessage.result.type === 'error'
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.result.type !== 'error') {
if (toolMessage.result.type === 'success') {
const { value, params } = toolMessage.result
componentParams.numResults = value.uris.length
componentParams.children = value.uris.length === 0 ? null : <>
{value.uris.map((uri, i) => (
<div key={i}
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
componentParams.children = <ToolContentsWrapper>
{value.uris.length === 0 ? <>
<ListableToolItem name={'No results found.'} isSmall={true} />
</> : <>
{value.uris.map((uri, i) => (<ListableToolItem key={i}
name={getBasename(uri.fsPath)}
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
>
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
{uri.fsPath.split('/').pop()}
</div>
))}
{value.hasNextPage && (<div className="italic">More results available...</div>)}
</>
/>))}
{value.hasNextPage &&
<ListableToolItem name={`Results truncated.`} isSmall={true} />
}
</>}
</ToolContentsWrapper>
}
else {
componentParams.children = <>
@ -1291,7 +1304,7 @@ const toolNameToComponent: { [T in ToolName]: {
requestWrapper: ({ toolRequest }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolRequest.name]
const title = toolNameToTitle[toolRequest.name].proposed
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
const icon = null
@ -1306,7 +1319,7 @@ const toolNameToComponent: { [T in ToolName]: {
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
const icon = null
@ -1335,7 +1348,7 @@ const toolNameToComponent: { [T in ToolName]: {
requestWrapper: ({ toolRequest, }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolRequest.name]
const title = toolNameToTitle[toolRequest.name].proposed
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
const icon = null
@ -1350,7 +1363,7 @@ const toolNameToComponent: { [T in ToolName]: {
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
const icon = null
@ -1378,7 +1391,7 @@ const toolNameToComponent: { [T in ToolName]: {
requestWrapper: ({ toolRequest, }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolRequest.name]
const title = toolNameToTitle[toolRequest.name].proposed
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
const icon = null
@ -1386,21 +1399,22 @@ const toolNameToComponent: { [T in ToolName]: {
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, }
const { params } = toolRequest
componentParams.children = <div>
<div
className=''
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}>
{getBasename(params.uri.fsPath)}
componentParams.children = <ToolContentsWrapper className='bg-void-bg-3'>
<ListableToolItem
name={getBasename(params.uri.fsPath)}
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
/>
<div className='select-auto cursor-auto'>
<ChatMarkdownRender string={params.changeDescription} codeURI={params.uri} chatMessageLocation={undefined} />
</div>
<ChatMarkdownRender string={params.changeDescription} chatMessageLocation={undefined} />
</div>
</ToolContentsWrapper>
return <ToolHeaderComponent {...componentParams} />
},
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
const icon = null
@ -1409,12 +1423,12 @@ const toolNameToComponent: { [T in ToolName]: {
if (toolMessage.result.type === 'success') {
const { params } = toolMessage.result
componentParams.children = <ChatMarkdownRender string={params.changeDescription} chatMessageLocation={undefined} />
componentParams.children = <ChatMarkdownRender string={params.changeDescription} codeURI={params.uri} chatMessageLocation={undefined} />
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
}
else if (toolMessage.result.type === 'rejected') {
const { params } = toolMessage.result
componentParams.children = <ChatMarkdownRender string={params.changeDescription} chatMessageLocation={undefined} />
componentParams.children = <ChatMarkdownRender string={params.changeDescription} codeURI={params.uri} chatMessageLocation={undefined} />
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
}
else if (toolMessage.result.type === 'error') {
@ -1431,7 +1445,7 @@ const toolNameToComponent: { [T in ToolName]: {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const terminalToolsService = accessor.get('ITerminalToolService')
const title = toolNameToTitle[toolRequest.name]
const title = toolNameToTitle[toolRequest.name].proposed
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
const icon = null
@ -1451,7 +1465,7 @@ const toolNameToComponent: { [T in ToolName]: {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const terminalToolsService = accessor.get('ITerminalToolService')
const title = toolNameToTitle[toolMessage.name]
const title = toolNameToTitle[toolMessage.name].past
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
const icon = null
@ -1529,7 +1543,8 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubblePr
}
else if (role === 'tool_request') {
const ToolRequestWrapper = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough...
if (!isLast) return null
// if (!isLast) return null
if (!ToolRequestWrapper) return null
return <>
<ToolRequestWrapper toolRequest={chatMessage} />
<ToolRequestAcceptRejectButtons voidToolId={chatMessage.voidToolId} />

View file

@ -75,7 +75,7 @@ const directoryResultToString = (params: ToolCallParams['list_dir'], result: Too
let output = '';
const entries = result.children;
if (!result.hasPrevPage) {
if (!result.hasPrevPage) { // is first page
output += `${params.rootURI}\n`;
}
@ -351,7 +351,7 @@ export class ToolsService implements IToolsService {
},
list_dir: (params, result) => {
const dirTreeStr = directoryResultToString(params, result)
return dirTreeStr + nextPageStr(result.hasNextPage)
return dirTreeStr // + nextPageStr(result.hasNextPage) // already handles num results remaining
},
pathname_search: (params, result) => {
return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage)

View file

@ -14,8 +14,9 @@ import { CodeSelection, FileSelection, StagingSelectionItem } from '../chatThrea
export const tripleTick = ['```', '```']
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. \
Typically the best description you can give here is a single 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]}. \
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]}. \
Do NOT output the whole file here if possible, and try to write as LITTLE code as needed to describe the change.`
@ -29,22 +30,23 @@ The user's system information is as follows:
- ${os}
- Open workspace(s): ${workspaces.join(', ') || 'NO WORKSPACE OPEN'}
${(mode === 'agent' || mode === 'gather') && runningTerminalIds.length !== 0 ? `\
- Running terminal IDs: ${runningTerminalIds.join(', ')}
- Existing terminal IDs: ${runningTerminalIds.join(', ')}
`: '\n'}
${mode === 'agent' || mode === 'gather' /* tool use */ ? `\
You will be given tools you can call.
- Only use tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT tools.
- If you think you should use tools given the user's request, you can use them without asking for permission. Feel free to use tools to gather context, understand the codebase, ${mode === 'agent' ? 'edit files, ' : ''}etc.
- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not refer to "pages" of results, just say you're getting more results.
- Some tools only work if the user has a workspace open. ${mode === 'gather' ? '' : `
- NEVER modify a file outside one of the the user's workspaces without confirmation from the user.`}
- Only use tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT use tools.
- If you think you should use tools, you do not need to ask for permission. Feel free to call tools whenever you'd like. You can use them to understand the codebase, ${mode === 'agent' ? 'run terminal commands, edit files, ' : 'gather relevant files and information, '}etc.
- NEVER refer to a tool by name when speaking with the user (NEVER say something like "I'm going to use \`tool_name\`"). Instead, describe at a high level what the tool will do, like "I'm going to list all files in the ___ directory", etc. Also do not refer to "pages" of results, just say you're getting more results.
- Some tools only work if the user has a workspace open.${mode === 'agent' ? `
- NEVER modify a file outside the user's workspace(s) without permission from the user.` : ''}
\
`: `\
You're allowed to ask for more context. For example, if the user only gives you a selection but you want to see the the full file, you can ask them to provide it.\
`}
${mode === 'agent' /* code blocks */ ? `\
If you have a change to make, you should almost always use a tool to edit the file. Even if you don't (e.g. if the user asks you not to), you should still NEVER re-write the entire file for the user. Instead, you should write comments like "// ... existing code" to indicate how to change the existing code. \
- Prioritize editing files and running commands over simply making suggestions.
- Prioritize taking as many steps as you need to complete your request over stopping early.\
`: `\
If you think it's appropriate to suggest an edit to a file, then you must describe your suggestion in CODE BLOCK(S) (wrapped in triple backticks).
- The first line of the code block must be the FULL PATH of the file you want to change. If the path does not already exist, it will be created.
@ -53,8 +55,8 @@ If you think it's appropriate to suggest an edit to a file, then you must descri
- Do NOT re-write the entire file in the code block(s). Instead, write comments like "// ... existing code" to indicate how to change the existing code.`}
Misc:
- Always wrap any code you produce in triple backticks.
\
- Do not make things up.
- Always wrap any code you produce in triple backticks, and specify a language if possible. For example, ${tripleTick[0]}typescript\n...\n${tripleTick[1]}.\
`