Add CodeBlock component

This commit is contained in:
Saoud Rizwan
2024-07-09 20:17:07 -04:00
parent 6f5b0565e0
commit 97faff3ba5
6 changed files with 571 additions and 53 deletions

View File

@@ -69,7 +69,8 @@ const tools: Tool[] = [
properties: {
command: {
type: "string",
description: "The CLI command to execute. This should be a valid command-line instruction for the current operating system. For example, 'ls -l' on Unix-like systems or 'dir' on Windows. Ensure the command is properly formatted and does not contain any harmful instructions. Avoid commands that run indefinitely (like servers) that don't terminate on their own.",
description:
"The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions. Avoid commands that run indefinitely (like servers) that don't terminate on their own.",
},
},
required: ["command"],
@@ -77,7 +78,8 @@ const tools: Tool[] = [
},
{
name: "list_files",
description: "List all files and directories at the top level of the specified directory. Use this to understand the contents and structure of a directory by examining file names and extensions. This information can guide decision-making on which files to process or which subdirectories to explore further. To investigate subdirectories, call this tool again with the path of the subdirectory.",
description:
"List all files and directories at the top level of the specified directory. Use this to understand the contents and structure of a directory by examining file names and extensions. This information can guide decision-making on which files to process or which subdirectories to explore further. To investigate subdirectories, call this tool again with the path of the subdirectory.",
input_schema: {
type: "object",
properties: {
@@ -99,7 +101,8 @@ const tools: Tool[] = [
properties: {
path: {
type: "string",
description: "The path of the file to read. Do not use absolute paths or attempt to access files outside of the current working directory.",
description:
"The path of the file to read. Do not use absolute paths or attempt to access files outside of the current working directory.",
},
},
required: ["path"],
@@ -114,7 +117,8 @@ const tools: Tool[] = [
properties: {
path: {
type: "string",
description: "The path of the file to write to. Do not use absolute paths or attempt to write to files outside of the current working directory.",
description:
"The path of the file to write to. Do not use absolute paths or attempt to write to files outside of the current working directory.",
},
content: {
type: "string",
@@ -133,7 +137,8 @@ const tools: Tool[] = [
properties: {
question: {
type: "string",
description: "The question to ask the user. This should be a clear, specific question that addresses the information you need.",
description:
"The question to ask the user. This should be a clear, specific question that addresses the information you need.",
},
},
required: ["question"],
@@ -148,11 +153,13 @@ const tools: Tool[] = [
properties: {
command: {
type: "string",
description: "The CLI command to execute to show a live demo of the result to the user. For example, use 'open -a \"Google Chrome\" index.html' to display a created website. Avoid commands that run indefinitely (like servers) that don't terminate on their own. Instead, if such a command is needed, include instructions for the user to run it in the 'result' parameter.",
description:
"The CLI command to execute to show a live demo of the result to the user. For example, use 'open -a \"Google Chrome\" index.html' to display a created website. Avoid commands that run indefinitely (like servers) that don't terminate on their own. Instead, if such a command is needed, include instructions for the user to run it in the 'result' parameter.",
},
result: {
type: "string",
description: "The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance.",
description:
"The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance.",
},
},
required: ["result"],
@@ -227,7 +234,7 @@ Default Shell: ${defaultShell}
Current Working Directory: ${process.cwd()}
## Files in Current Directory
${filesInCurrentDir}`
const activeEditorContents = vscode.window.activeTextEditor?.document.getText()
if (activeEditorContents) {
userPrompt += `
@@ -305,7 +312,37 @@ ${activeEditorContents}`
const diffResult = diff.createPatch(filePath, originalContent, newContent)
if (diffResult) {
await fs.writeFile(filePath, newContent)
this.say("tool", JSON.stringify({ tool: "editedExistingFile", path: filePath, diff: diffResult } as ClaudeSayTool))
// Create diff for DiffCodeView.tsx
const diffStringRaw = diff.diffLines(originalContent, newContent)
const diffStringConverted = diffStringRaw
.map((part, index) => {
const prefix = part.added ? "+ " : part.removed ? "- " : " "
return part.value
.split("\n")
.map((line, lineIndex) => {
// avoid adding an extra empty line at the very end of the diff output
if (
line === "" &&
index === diffStringRaw.length - 1 &&
lineIndex === part.value.split("\n").length - 1
) {
return null
}
return prefix + line + "\n"
})
.join("")
})
.join("")
this.say(
"tool",
JSON.stringify({
tool: "editedExistingFile",
path: filePath,
diff: diffStringConverted,
} as ClaudeSayTool)
)
return `Changes applied to ${filePath}:\n${diffResult}`
} else {
this.say("tool", JSON.stringify({ tool: "editedExistingFile", path: filePath } as ClaudeSayTool))
@@ -314,12 +351,15 @@ ${activeEditorContents}`
} else {
await fs.mkdir(path.dirname(filePath), { recursive: true })
await fs.writeFile(filePath, newContent)
this.say("tool", JSON.stringify({ tool: "newFileCreated", path: filePath, content: newContent } as ClaudeSayTool))
this.say(
"tool",
JSON.stringify({ tool: "newFileCreated", path: filePath, content: newContent } as ClaudeSayTool)
)
return `New file created and content written to ${filePath}`
}
} catch (error) {
const errorString = `Error writing file: ${JSON.stringify(serializeError(error))}`
this.say("error", JSON.stringify(serializeError(error)))
this.say("error", `Error writing file:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`)
return errorString
}
}
@@ -327,11 +367,11 @@ ${activeEditorContents}`
async readFile(filePath: string): Promise<string> {
try {
const content = await fs.readFile(filePath, "utf-8")
this.say("tool", JSON.stringify({ tool: "readFile", path: filePath } as ClaudeSayTool))
this.say("tool", JSON.stringify({ tool: "readFile", path: filePath, content } as ClaudeSayTool))
return content
} catch (error) {
const errorString = `Error reading file: ${JSON.stringify(serializeError(error))}`
this.say("error", JSON.stringify(serializeError(error)))
this.say("error", `Error reading file:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`)
return errorString
}
}
@@ -343,7 +383,7 @@ ${activeEditorContents}`
const isRoot = cwd === root
if (isRoot) {
if (shouldLog) {
this.say("tool", JSON.stringify({ tool: "listFiles", path: dirPath } as ClaudeSayTool))
this.say("tool", JSON.stringify({ tool: "listFiles", path: dirPath, content: "/" } as ClaudeSayTool))
}
return "Currently in the root directory. Cannot list all files."
}
@@ -356,13 +396,14 @@ ${activeEditorContents}`
}
// * globs all files in one dir, ** globs files in nested directories
const entries = await glob("*", options)
const result = entries.slice(0, 500).join("\n") // truncate to 500 entries
if (shouldLog) {
this.say("tool", JSON.stringify({ tool: "listFiles", path: dirPath } as ClaudeSayTool))
this.say("tool", JSON.stringify({ tool: "listFiles", path: dirPath, content: result } as ClaudeSayTool))
}
return entries.slice(0, 500).join("\n") // truncate to 500 entries
return result
} catch (error) {
const errorString = `Error listing files and directories: ${JSON.stringify(serializeError(error))}`
this.say("error", JSON.stringify(serializeError(error)))
this.say("error", `Error listing files and directories:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`)
return errorString
}
}
@@ -384,9 +425,9 @@ ${activeEditorContents}`
return `Command executed successfully. Output:\n${result}`
} catch (e) {
const error = e as any
let errorMessage = error.message || JSON.stringify(serializeError(error))
let errorMessage = error.message || JSON.stringify(serializeError(error), null, 2)
const errorString = `Error executing command:\n${errorMessage}`
this.say("error", errorMessage)
this.say("error", `Error executing command:\n${errorMessage}`) // TODO: in webview show code block for command errors
return errorString
}
}
@@ -456,7 +497,14 @@ ${activeEditorContents}`
let assistantResponses: Anthropic.Messages.ContentBlock[] = []
let inputTokens = response.usage.input_tokens
let outputTokens = response.usage.output_tokens
await this.say("api_req_finished", JSON.stringify({ tokensIn: inputTokens, tokensOut: outputTokens, cost: this.calculateApiCost(inputTokens, outputTokens) }))
await this.say(
"api_req_finished",
JSON.stringify({
tokensIn: inputTokens,
tokensOut: outputTokens,
cost: this.calculateApiCost(inputTokens, outputTokens),
})
)
// A response always returns text content blocks (it's just that before we were iterating over the completion_attempt response before we could append text response, resulting in bug)
for (const contentBlock of response.content) {
@@ -547,7 +595,7 @@ ${activeEditorContents}`
return { didCompleteTask, inputTokens, outputTokens }
} catch (error) {
// only called if the API request fails (executeTool errors are returned back to claude)
this.say("error", JSON.stringify(serializeError(error)))
this.say("error", `API request failed:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`)
return { didCompleteTask: true, inputTokens: 0, outputTokens: 0 }
}
}

View File

@@ -19,6 +19,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-text-truncate": "^0.19.0",
"react-textarea-autosize": "^8.5.3",
"rewire": "^7.0.0",
@@ -26,6 +27,7 @@
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-text-truncate": "^0.14.4",
"@types/vscode-webview": "^1.57.5"
}
@@ -4421,6 +4423,15 @@
"@types/node": "*"
}
},
"node_modules/@types/hast": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
"integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^2"
}
},
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -4564,6 +4575,16 @@
"@types/react": "*"
}
},
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-text-truncate": {
"version": "0.14.4",
"resolved": "https://registry.npmjs.org/@types/react-text-truncate/-/react-text-truncate-0.14.4.tgz",
@@ -4655,6 +4676,12 @@
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@types/unist": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
"integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==",
"license": "MIT"
},
"node_modules/@types/vscode-webview": {
"version": "1.57.5",
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.5.tgz",
@@ -6328,6 +6355,36 @@
"node": ">=10"
}
},
"node_modules/character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-reference-invalid": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/check-types": {
"version": "11.2.3",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz",
@@ -6501,6 +6558,16 @@
"node": ">= 0.8"
}
},
"node_modules/comma-separated-tokens": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
@@ -8825,6 +8892,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/fault": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/faye-websocket": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
@@ -9268,6 +9348,14 @@
"node": ">= 6"
}
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -9710,6 +9798,33 @@
"node": ">= 0.4"
}
},
"node_modules/hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"license": "MIT",
"dependencies": {
"@types/hast": "^2.0.0",
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -9719,6 +9834,15 @@
"he": "bin/he"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -10153,6 +10277,30 @@
"node": ">= 10"
}
},
"node_modules/is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -10303,6 +10451,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-decimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
@@ -10384,6 +10542,16 @@
"node": ">=0.10.0"
}
},
"node_modules/is-hexadecimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
@@ -13307,6 +13475,20 @@
"tslib": "^2.0.3"
}
},
"node_modules/lowlight": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"license": "MIT",
"dependencies": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -14058,6 +14240,24 @@
"node": ">=6"
}
},
"node_modules/parse-entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
"license": "MIT",
"dependencies": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -15629,6 +15829,15 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -15674,6 +15883,19 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/property-information": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -16155,6 +16377,22 @@
}
}
},
"node_modules/react-syntax-highlighter": {
"version": "15.5.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz",
"integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"lowlight": "^1.17.0",
"prismjs": "^1.27.0",
"refractor": "^3.6.0"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/react-text-truncate": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/react-text-truncate/-/react-text-truncate-0.19.0.tgz",
@@ -16266,6 +16504,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/refractor": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
"license": "MIT",
"dependencies": {
"hastscript": "^6.0.0",
"parse-entities": "^2.0.0",
"prismjs": "~1.27.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -17213,6 +17475,16 @@
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
"license": "MIT"
},
"node_modules/space-separated-tokens": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/spdy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
@@ -19659,6 +19931,15 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT"
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@@ -14,6 +14,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-text-truncate": "^0.19.0",
"react-textarea-autosize": "^8.5.3",
"rewire": "^7.0.0",
@@ -45,6 +46,7 @@
]
},
"devDependencies": {
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-text-truncate": "^0.14.4",
"@types/vscode-webview": "^1.57.5"
}

View File

@@ -2,6 +2,9 @@ import React, { useState } from "react"
import { ClaudeMessage, ClaudeAsk, ClaudeSay, ClaudeSayTool } from "@shared/ExtensionMessage"
import { VSCodeButton, VSCodeProgressRing, VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
import { COMMAND_OUTPUT_STRING } from "../utilities/combineCommandSequences"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism"
import CodeBlock from "./CodeBlock"
interface ChatRowProps {
message: ClaudeMessage
@@ -45,13 +48,6 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
style={{ color: successColor, marginBottom: "-1.5px" }}></span>,
<span style={{ color: successColor, fontWeight: "bold" }}>Task Completed</span>,
]
case "tool":
return [
<span
className="codicon codicon-tools"
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
<span style={{ color: normalColor, fontWeight: "bold" }}>Tool</span>,
]
case "api_req_started":
return [
cost ? (
@@ -123,47 +119,51 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
tool: "editedExistingFile",
path: "/path/to/file",
}
const toolIcon = (name: string) => (
<span
className={`codicon codicon-${name}`}
style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}></span>
)
switch (tool.tool) {
case "editedExistingFile":
return (
<>
<div style={headerStyle}>
{icon}
Edited File
{toolIcon("edit")}
Edited file...
</div>
<p>Path: {tool.path!}</p>
<p>{tool.diff!}</p>
<CodeBlock diff={tool.diff!} path={tool.path!} />
</>
)
case "newFileCreated":
return (
<>
<div style={headerStyle}>
{icon}
Created New File
{toolIcon("new-file")}
Created new file...
</div>
<p>Path: {tool.path!}</p>
<p>{tool.content!}</p>
<CodeBlock code={tool.content!} path={tool.path!} />
</>
)
case "readFile":
return (
<>
<div style={headerStyle}>
{icon}
Read File
{toolIcon("file-code")}
Read file...
</div>
<p>Path: {tool.path!}</p>
<CodeBlock code={tool.content!} path={tool.path!} />
</>
)
case "listFiles":
return (
<>
<div style={headerStyle}>
{icon}
Viewed Directory
{toolIcon("folder-opened")}
Viewed contents of directory...
</div>
<p>Path: {tool.path!}</p>
<CodeBlock code={tool.content!} path={tool.path!} language="shell-session" />
</>
)
}
@@ -244,14 +244,24 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
{title}
</div>
<div style={contentStyle}>
<p style={contentStyle}>Claude Dev wants to execute the following terminal command. Would you like to proceed?</p>
<p style={contentStyle}>{command}</p>
<p style={contentStyle}>
Claude Dev wants to execute the following terminal command. Would you like to
proceed?
</p>
<div style={{ marginTop: "10px" }}>
<CodeBlock code={command} language="shell-session" />
</div>
{output && (
<>
<p style={{ ...contentStyle, fontWeight: "bold" }}>
<p style={{ ...contentStyle, margin: "10px 0 10px 0" }}>
{COMMAND_OUTPUT_STRING}
</p>
<p style={contentStyle}>{output}</p>
<CodeBlock
code={output}
language="shell-session"
path="src/components/WelcomeView.tsx/src/components/WelcomeView.tsx"
/>
</>
)}
</div>
@@ -300,7 +310,9 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
}}>
{renderContent()}
{isExpanded && message.say === "api_req_started" && (
<p style={{ marginTop: "10px" }}>{JSON.stringify(JSON.parse(message.text || "{}").request)}</p>
<div style={{ marginTop: "10px" }}>
<CodeBlock code={JSON.stringify(JSON.parse(message.text || "{}").request)} language="json" />
</div>
)}
</div>
)

View File

@@ -0,0 +1,143 @@
import React, { useState } from "react"
import SyntaxHighlighter from "react-syntax-highlighter"
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
/*
const vscodeSyntaxStyle: React.CSSProperties = {
backgroundColor: "var(--vscode-editor-background)",
color: "var(--vscode-editor-foreground)",
fontFamily: "var(--vscode-editor-font-family)",
fontSize: "var(--vscode-editor-font-size)",
lineHeight: "var(--vscode-editor-line-height)",
textAlign: "left",
whiteSpace: "pre",
wordSpacing: "normal",
wordBreak: "normal",
wordWrap: "normal",
tabSize: 4,
hyphens: "none",
padding: "1em",
margin: "0.5em 0",
overflow: "auto",
borderRadius: "6px",
}
const tokenStyles = {
comment: { color: "var(--vscode-editor-foreground)" },
prolog: { color: "var(--vscode-editor-foreground)" },
doctype: { color: "var(--vscode-editor-foreground)" },
cdata: { color: "var(--vscode-editor-foreground)" },
punctuation: { color: "var(--vscode-editor-foreground)" },
property: { color: "var(--vscode-symbolIcon-propertyForeground)" },
tag: { color: "var(--vscode-symbolIcon-colorForeground)" },
boolean: { color: "var(--vscode-symbolIcon-booleanForeground)" },
number: { color: "var(--vscode-symbolIcon-numberForeground)" },
constant: { color: "var(--vscode-symbolIcon-constantForeground)" },
symbol: { color: "var(--vscode-symbolIcon-colorForeground)" },
selector: { color: "var(--vscode-symbolIcon-colorForeground)" },
"attr-name": { color: "var(--vscode-symbolIcon-propertyForeground)" },
string: { color: "var(--vscode-symbolIcon-stringForeground)" },
char: { color: "var(--vscode-symbolIcon-stringForeground)" },
builtin: { color: "var(--vscode-symbolIcon-keywordForeground)" },
inserted: { color: "var(--vscode-gitDecoration-addedResourceForeground)" },
operator: { color: "var(--vscode-symbolIcon-operatorForeground)" },
entity: { color: "var(--vscode-symbolIcon-snippetForeground)", cursor: "help" },
url: { color: "var(--vscode-textLink-foreground)" },
variable: { color: "var(--vscode-symbolIcon-variableForeground)" },
atrule: { color: "var(--vscode-symbolIcon-keywordForeground)" },
"attr-value": { color: "var(--vscode-symbolIcon-stringForeground)" },
keyword: { color: "var(--vscode-symbolIcon-keywordForeground)" },
function: { color: "var(--vscode-symbolIcon-functionForeground)" },
regex: { color: "var(--vscode-symbolIcon-regexForeground)" },
important: { color: "var(--vscode-editorWarning-foreground)", fontWeight: "bold" },
bold: { fontWeight: "bold" },
italic: { fontStyle: "italic" },
deleted: { color: "var(--vscode-gitDecoration-deletedResourceForeground)" },
}
*/
interface CodeBlockProps {
code?: string
diff?: string
language?: string | undefined
path?: string
}
const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const backgroundColor = oneDark['pre[class*="language-"]'].background as string
return (
<div
style={{
borderRadius: "3px",
backgroundColor: backgroundColor,
overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners
}}>
{path && (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "6px 10px",
}}>
<span
style={{
color: "var(--vscode-descriptionForeground)",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
marginRight: "8px",
// trick to get ellipsis at beginning of string
direction: "rtl",
textAlign: "left",
}}>
{path}
</span>
<VSCodeButton appearance="icon" aria-label="Toggle Code" onClick={() => setIsExpanded(!isExpanded)}>
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
</VSCodeButton>
</div>
)}
{(!path || isExpanded) && (
<div
className="code-block-scrollable"
style={{
overflowX: "auto",
overflowY: "hidden",
maxWidth: "100%",
}}>
<SyntaxHighlighter
wrapLines={false}
language={language}
style={oneDark}
customStyle={{
margin: 0,
padding: "6px 10px",
borderRadius: 0,
}}
lineProps={
diff != null
? (lineNumber) => {
const line = diff?.split("\n")?.[lineNumber - 1]
let style: React.CSSProperties = { display: "block", width: "100%" }
if (line && line[0] === "+") {
style.backgroundColor = "var(--vscode-diffEditor-insertedTextBackground)"
} else if (line && line[0] === "-") {
style.backgroundColor = "var(--vscode-diffEditor-removedTextBackground)"
}
return { style }
}
: undefined
}>
{code ?? diff ?? ""}
</SyntaxHighlighter>
</div>
)}
</div>
)
}
export default CodeBlock

View File

@@ -34,7 +34,7 @@ body {
}
body.scrollable,
.scrollable {
.scrollable, body.code-block-scrollable, .code-block-scrollable {
border-color: transparent;
transition: border-color 0.7s linear;
}
@@ -42,16 +42,21 @@ body.scrollable,
body:hover.scrollable,
body:hover .scrollable,
body:focus-within.scrollable,
body:focus-within .scrollable {
body:focus-within .scrollable,
body:hover.code-block-scrollable,
body:hover .code-block-scrollable,
body:focus-within.code-block-scrollable,
body:focus-within .code-block-scrollable
{
border-color: var(--vscode-scrollbarSlider-background);
transition: none;
}
::-webkit-scrollbar-corner {
.scrollable::-webkit-scrollbar-corner {
background-color: transparent !important;
}
::-webkit-scrollbar-thumb {
.scrollable::-webkit-scrollbar-thumb {
background-color: transparent;
border-color: inherit;
border-right-style: inset;
@@ -59,11 +64,11 @@ body:focus-within .scrollable {
border-radius: unset !important;
}
::-webkit-scrollbar-thumb:hover {
.scrollable::-webkit-scrollbar-thumb:hover {
border-color: var(--vscode-scrollbarSlider-hoverBackground);
}
::-webkit-scrollbar-thumb:active {
.scrollable::-webkit-scrollbar-thumb:active {
border-color: var(--vscode-scrollbarSlider-activeBackground);
}
@@ -75,4 +80,31 @@ https://github.com/microsoft/vscode/issues/213045
html {
scrollbar-color: unset;
}
}
/*
The above scrollbar styling uses some transparent background color magic to accomplish its animation. However this doesn't play nicely with SyntaxHighlighter, so we need to set a background color for the code blocks' horizontal scrollbar. This actually has the unintended consequence of always showing the scrollbar which I prefer since it makes it more obvious that there is more content to scroll to.
*/
.code-block-scrollable::-webkit-scrollbar-track {
background: transparent;
}
.code-block-scrollable::-webkit-scrollbar-thumb {
background-color: var(--vscode-scrollbarSlider-background);
border-radius: 5px;
border: 2px solid transparent;
background-clip: content-box;
}
.code-block-scrollable::-webkit-scrollbar-thumb:hover {
background-color: var(--vscode-scrollbarSlider-hoverBackground);
}
.code-block-scrollable::-webkit-scrollbar-thumb:active {
background-color: var(--vscode-scrollbarSlider-activeBackground);
}
.code-block-scrollable::-webkit-scrollbar-corner {
background-color: transparent;
}