diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json
index 6ebc6476..a23620fe 100644
--- a/extensions/void/package-lock.json
+++ b/extensions/void/package-lock.json
@@ -9,8 +9,8 @@
"version": "0.0.1",
"dependencies": {
"@anthropic-ai/sdk": "^0.27.1",
- "diff-match-patch": "^1.0.5",
"diff": "^7.0.0",
+ "diff-match-patch": "^1.0.5",
"ollama": "^0.5.9",
"openai": "^4.57.0"
},
@@ -23,6 +23,7 @@
"@types/node": "^22.5.1",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
+ "@types/uuid": "^10.0.0",
"@types/vscode": "1.89.0",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
@@ -42,7 +43,8 @@
"rimraf": "^6.0.1",
"tailwindcss": "^3.4.10",
"typescript": "5.5.4",
- "typescript-eslint": "^8.3.0"
+ "typescript-eslint": "^8.3.0",
+ "uuid": "^10.0.0"
},
"engines": {
"vscode": "^1.89.0"
@@ -768,6 +770,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/vscode": {
"version": "1.89.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.89.0.tgz",
@@ -7720,6 +7729,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
diff --git a/extensions/void/package.json b/extensions/void/package.json
index 17c44e1f..72bfb32a 100644
--- a/extensions/void/package.json
+++ b/extensions/void/package.json
@@ -363,6 +363,7 @@
"@types/node": "^22.5.1",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
+ "@types/uuid": "^10.0.0",
"@types/vscode": "1.89.0",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
@@ -382,13 +383,14 @@
"rimraf": "^6.0.1",
"tailwindcss": "^3.4.10",
"typescript": "5.5.4",
- "typescript-eslint": "^8.3.0"
+ "typescript-eslint": "^8.3.0",
+ "uuid": "^10.0.0"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.27.1",
+ "diff": "^7.0.0",
"diff-match-patch": "^1.0.5",
"ollama": "^0.5.9",
- "openai": "^4.57.0",
- "diff": "^7.0.0"
+ "openai": "^4.57.0"
}
-}
\ No newline at end of file
+}
diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts
index 0c0541be..55c53350 100644
--- a/extensions/void/src/extension.ts
+++ b/extensions/void/src/extension.ts
@@ -1,6 +1,6 @@
import * as vscode from 'vscode';
import { DisplayChangesProvider } from './DisplayChangesProvider';
-import { BaseDiffArea, ChatThreads, WebviewMessage } from './shared_types';
+import { BaseDiffArea, ChatThreads, MessageFromSidebar, MessageToSidebar } from './shared_types';
import { SidebarWebviewProvider } from './SidebarWebviewProvider';
import { ApiConfig } from './common/sendLLMMessage';
@@ -79,7 +79,7 @@ export function activate(context: vscode.ExtensionContext) {
const filePath = editor.document.uri;
// send message to the webview (Sidebar.tsx)
- webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies WebviewMessage));
+ webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
})
);
@@ -105,23 +105,23 @@ export function activate(context: vscode.ExtensionContext) {
// top navigation bar commands
context.subscriptions.push(vscode.commands.registerCommand('void.startNewThread', async () => {
- webview.postMessage({ type: 'startNewThread' } satisfies WebviewMessage)
+ webview.postMessage({ type: 'startNewThread' } satisfies MessageToSidebar)
}))
context.subscriptions.push(vscode.commands.registerCommand('void.toggleThreadSelector', async () => {
- webview.postMessage({ type: 'toggleThreadSelector' } satisfies WebviewMessage)
+ webview.postMessage({ type: 'toggleThreadSelector' } satisfies MessageToSidebar)
}))
// when config changes, send it to the sidebar
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('void')) {
const apiConfig = getApiConfig()
- webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage)
+ webview.postMessage({ type: 'apiConfig', apiConfig } satisfies MessageToSidebar)
}
})
// Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`)
- webview.onDidReceiveMessage(async (m: WebviewMessage) => {
+ webview.onDidReceiveMessage(async (m: MessageFromSidebar) => {
if (m.type === 'requestFiles') {
@@ -131,7 +131,7 @@ export function activate(context: vscode.ExtensionContext) {
)
// send contents to webview
- webview.postMessage({ type: 'files', files, } satisfies WebviewMessage)
+ webview.postMessage({ type: 'files', files, } satisfies MessageToSidebar)
} else if (m.type === 'applyChanges') {
@@ -168,11 +168,11 @@ export function activate(context: vscode.ExtensionContext) {
}
else if (m.type === 'getApiConfig') {
const apiConfig = getApiConfig()
- webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage)
+ webview.postMessage({ type: 'apiConfig', apiConfig } satisfies MessageToSidebar)
}
else if (m.type === 'getAllThreads') {
const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {}
- webview.postMessage({ type: 'allThreads', threads } satisfies WebviewMessage)
+ webview.postMessage({ type: 'allThreads', threads } satisfies MessageToSidebar)
}
else if (m.type === 'persistThread') {
const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {}
@@ -180,7 +180,7 @@ export function activate(context: vscode.ExtensionContext) {
context.workspaceState.update('allThreads', updatedThreads)
}
else {
- console.error('unrecognized command', m.type, m)
+ console.error('unrecognized command', m)
}
})
}
diff --git a/extensions/void/src/shared_types.ts b/extensions/void/src/shared_types.ts
index ad2025f1..0fcbef42 100644
--- a/extensions/void/src/shared_types.ts
+++ b/extensions/void/src/shared_types.ts
@@ -38,45 +38,25 @@ type Diff = {
lenses: vscode.CodeLens[],
} & BaseDiff
-type WebviewMessage = (
-
- // editor -> sidebar
+// editor -> sidebar
+type MessageToSidebar = (
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor
-
- // sidebar -> editor
- | { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar
-
- // sidebar -> editor
- | { type: 'requestFiles', filepaths: vscode.Uri[] }
-
- // editor -> sidebar
| { type: 'files', files: { filepath: vscode.Uri, content: string }[] }
-
- // sidebar -> editor
- | { type: 'getApiConfig' }
-
- // editor -> sidebar
| { type: 'apiConfig', apiConfig: ApiConfig }
-
- // sidebar -> editor
- | { type: 'getAllThreads' }
-
- // editor -> sidebar
| { type: 'allThreads', threads: ChatThreads }
-
- // sidebar -> editor
- | { type: 'persistThread', thread: ChatThreads[string] }
-
- // editor -> sidebar
| { type: 'startNewThread' }
-
- // editor -> sidebar
| { type: 'toggleThreadSelector' }
-
)
+// sidebar -> editor
+type MessageFromSidebar = (
+ | { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar
+ | { type: 'requestFiles', filepaths: vscode.Uri[] }
+ | { type: 'getApiConfig' }
+ | { type: 'getAllThreads' }
+ | { type: 'persistThread', thread: ChatThreads[string] }
+)
-type Command = WebviewMessage['type']
type ChatThreads = {
[id: string]: {
@@ -105,8 +85,8 @@ export {
Diff, DiffArea,
CodeSelection,
File,
- WebviewMessage,
- Command,
+ MessageFromSidebar,
+ MessageToSidebar,
ChatThreads,
ChatMessage,
}
diff --git a/extensions/void/src/sidebar/Sidebar.tsx b/extensions/void/src/sidebar/Sidebar.tsx
index 8521d83d..e41e87e4 100644
--- a/extensions/void/src/sidebar/Sidebar.tsx
+++ b/extensions/void/src/sidebar/Sidebar.tsx
@@ -1,317 +1,42 @@
import React, { useState, useEffect, useRef, useCallback, FormEvent } from "react"
import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage"
-import { File, CodeSelection, WebviewMessage, ChatMessage } from "../shared_types"
-import { awaitVSCodeResponse, getVSCodeAPI, resolveAwaitingVSCodeResponse } from "./getVscodeApi"
+import { CodeSelection, ChatMessage, MessageToSidebar } from "../shared_types"
+import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode } from "./getVscodeApi"
-import { marked } from 'marked';
-import MarkdownRender from "./markdown/MarkdownRender";
-import BlockCode from "./markdown/BlockCode";
-
-import * as vscode from 'vscode'
-import { SelectedFiles } from "./components/SelectedFiles";
+import { SidebarThreadSelector } from "./SidebarThreadSelector";
import { useThreads } from "./threadsContext";
-
-
-const filesStr = (fullFiles: File[]) => {
- return fullFiles.map(({ filepath, content }) =>
- `
-${filepath.fsPath}
-\`\`\`
-${content}
-\`\`\``).join('\n')
-}
-
-const userInstructionsStr = (instructions: string, files: File[], selection: CodeSelection | null) => {
- return `
-${filesStr(files)}
-
-${!selection ? '' : `
-I am currently selecting this code:
-\`\`\`${selection.selectionStr}\`\`\`
-`}
-
-Please edit the code following these instructions (or, if appropriate, answer my question instead):
-${instructions}
-
-If you make a change, rewrite the entire file.
-`; // TODO don't rewrite the whole file on prompt, instead rewrite it when click Apply
-}
-
-
-const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
-
- const role = chatMessage.role
- const children = chatMessage.displayContent
-
- if (!children)
- return null
-
- let chatbubbleContents: React.ReactNode
-
- if (role === 'user') {
- chatbubbleContents = <>
-
- {chatMessage.selection?.selectionStr && }
- {children}
- >
- }
- else if (role === 'assistant') {
-
- chatbubbleContents = // sectionsHTML
- }
-
-
- return
-
- {chatbubbleContents}
-
-
-}
-
-const ThreadSelector = ({ onClose }: { onClose: () => void }) => {
- const { allThreads, currentThread, switchToThread } = useThreads()
- return (
-
-
- {/* iterate through all past threads */}
- {Object.keys(allThreads ?? {}).map((threadId) => {
- const pastThread = (allThreads ?? {})[threadId];
- return (
-
- )
- })}
-
- )
-}
+import { SidebarChat } from "./SidebarChat";
const Sidebar = () => {
- const { allThreads, currentThread, addMessageToHistory, startNewThread, } = useThreads()
-
- // state of current message
- const [selection, setSelection] = useState(null) // the code the user is selecting
- const [files, setFiles] = useState([]) // the names of the files in the chat
- const [instructions, setInstructions] = useState('') // the user's instructions
-
- // state of chat
- const [messageStream, setMessageStream] = useState('')
- const [isLoading, setIsLoading] = useState(false)
const [isThreadSelectorOpen, setIsThreadSelectorOpen] = useState(false)
- const abortFnRef = useRef<(() => void) | null>(null)
-
- const [apiConfig, setApiConfig] = useState(null)
-
// get Api Config on mount
useEffect(() => {
getVSCodeAPI().postMessage({ type: 'getApiConfig' })
}, [])
- // Receive messages from the extension
+ // Receive messages from the VSCode extension
useEffect(() => {
const listener = (event: MessageEvent) => {
-
- const m = event.data as WebviewMessage;
- // resolve any awaiting promises
- // eg. it will resolve the promise below for `await VSCodeResponse('files')`
- resolveAwaitingVSCodeResponse(m)
-
- // if user pressed ctrl+l, add their selection to the sidebar
- if (m.type === 'ctrl+l') {
- setSelection(m.selection)
- const filepath = m.selection.filePath
-
- // add current file to the context if it's not already in the files array
- if (!files.find(f => f.fsPath === filepath.fsPath))
- setFiles(files => [...files, filepath])
-
- }
- // when get apiConfig, set
- else if (m.type === 'apiConfig') {
- setApiConfig(m.apiConfig)
- }
-
- // if they pressed the + to add a new chat
- else if (m.type === 'startNewThread') {
- setIsThreadSelectorOpen(false)
- if (currentThread?.messages.length !== 0)
- startNewThread()
- }
-
- // if they opened thread selector
- else if (m.type === 'toggleThreadSelector') {
- setIsThreadSelectorOpen(v => !v)
- }
-
+ const m = event.data as MessageToSidebar;
+ onMessageFromVSCode(m)
}
window.addEventListener('message', listener);
return () => { window.removeEventListener('message', listener) }
- }, [files, selection, startNewThread, currentThread])
+ }, [])
- const formRef = useRef(null)
- const onSubmit = async (e: FormEvent) => {
-
- e.preventDefault()
- if (isLoading) return
-
- setIsLoading(true)
- setInstructions('');
- formRef.current?.reset(); // reset the form's text
- setSelection(null)
- setFiles([])
-
- // request file content from vscode and await response
- getVSCodeAPI().postMessage({ type: 'requestFiles', filepaths: files })
- const relevantFiles = await awaitVSCodeResponse('files')
-
- // add message to chat history
- const content = userInstructionsStr(instructions, relevantFiles.files, selection)
- // console.log('prompt:\n', content)
- const newHistoryElt: ChatMessage = { role: 'user', content, displayContent: instructions, selection, files }
- addMessageToHistory(newHistoryElt)
-
- // send message to LLM
- let { abort } = sendLLMMessage({
- messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })), { role: 'user', content }],
- onText: (newText, fullText) => setMessageStream(fullText),
- onFinalMessage: (content) => {
- // add assistant's message to chat history, and clear selection
- const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, }
- addMessageToHistory(newHistoryElt)
-
- // clear selection
- setMessageStream('')
- setIsLoading(false)
- },
- apiConfig: apiConfig
- })
- abortFnRef.current = abort
-
- }
-
- const onStop = useCallback(() => {
- // abort claude
- abortFnRef.current?.()
-
- // if messageStream was not empty, add it to the history
- const llmContent = messageStream || '(canceled)'
- const newHistoryElt: ChatMessage = { role: 'assistant', displayContent: messageStream, content: llmContent }
- addMessageToHistory(newHistoryElt)
-
- setMessageStream('')
- setIsLoading(false)
-
- }, [addMessageToHistory, messageStream])
-
- //Clear code selection
- const clearSelection = () => {
- setSelection(null);
- };
-
return <>
{isThreadSelectorOpen && (
- setIsThreadSelectorOpen(false)} />
+ setIsThreadSelectorOpen(false)} />
)}
-
- {/* previous messages */}
- {currentThread !== null && currentThread.messages.map((message, i) =>
-
- )}
- {/* message stream */}
-
-
- {/* chatbar */}
-
- {/* selection */}
-
-
-
- {/* selection */}
- {(files.length || selection?.selectionStr) &&
- {/* selected files */}
-
- {/* selected code */}
- {!!selection?.selectionStr && (
-
- Remove
-
- )} />
- )}
-
}
-
-
-
-
-
+
>
diff --git a/extensions/void/src/sidebar/SidebarChat.tsx b/extensions/void/src/sidebar/SidebarChat.tsx
new file mode 100644
index 00000000..4b029a66
--- /dev/null
+++ b/extensions/void/src/sidebar/SidebarChat.tsx
@@ -0,0 +1,258 @@
+import React, { FormEvent, useCallback, useEffect, useRef, useState } from "react";
+
+
+import { marked } from 'marked';
+import MarkdownRender from "./markdown/MarkdownRender";
+import BlockCode from "./markdown/BlockCode";
+import { SelectedFiles } from "./components/SelectedFiles";
+import { File, ChatMessage, CodeSelection } from "../shared_types";
+import * as vscode from 'vscode'
+import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi";
+import { useThreads } from "./threadsContext";
+import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage";
+
+
+
+const filesStr = (fullFiles: File[]) => {
+ return fullFiles.map(({ filepath, content }) =>
+ `
+${filepath.fsPath}
+\`\`\`
+${content}
+\`\`\``).join('\n')
+}
+
+const userInstructionsStr = (instructions: string, files: File[], selection: CodeSelection | null) => {
+ return `
+${filesStr(files)}
+
+${!selection ? '' : `
+I am currently selecting this code:
+\`\`\`${selection.selectionStr}\`\`\`
+`}
+
+Please edit the code following these instructions (or, if appropriate, answer my question instead):
+${instructions}
+
+If you make a change, rewrite the entire file.
+`; // TODO don't rewrite the whole file on prompt, instead rewrite it when click Apply
+}
+
+
+const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
+
+ const role = chatMessage.role
+ const children = chatMessage.displayContent
+
+ if (!children)
+ return null
+
+ let chatbubbleContents: React.ReactNode
+
+ if (role === 'user') {
+ chatbubbleContents = <>
+
+ {chatMessage.selection?.selectionStr && }
+ {children}
+ >
+ }
+ else if (role === 'assistant') {
+
+ chatbubbleContents = // sectionsHTML
+ }
+
+
+ return
+
+ {chatbubbleContents}
+
+
+}
+
+
+export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOpen: (v: boolean | ((v: boolean) => boolean)) => void }) => {
+
+
+ // state of current message
+ const [selection, setSelection] = useState(null) // the code the user is selecting
+ const [files, setFiles] = useState([]) // the names of the files in the chat
+ const [instructions, setInstructions] = useState('') // the user's instructions
+
+ // state of chat
+ const [messageStream, setMessageStream] = useState('')
+ const [isLoading, setIsLoading] = useState(false)
+ const abortFnRef = useRef<(() => void) | null>(null)
+
+ // higher level state
+ const { allThreads, currentThread, addMessageToHistory, startNewThread, } = useThreads()
+ const [apiConfig, setApiConfig] = useState(null)
+
+
+ // if user pressed ctrl+l, add their selection to the sidebar
+ useOnVSCodeMessage('ctrl+l', (m) => {
+ setSelection(m.selection)
+ const filepath = m.selection.filePath
+
+ // add current file to the context if it's not already in the files array
+ if (!files.find(f => f.fsPath === filepath.fsPath))
+ setFiles(files => [...files, filepath])
+ })
+
+ // when get apiConfig, set
+ useOnVSCodeMessage('apiConfig', (m) => {
+ setApiConfig(m.apiConfig)
+ })
+
+ // if they pressed the + to add a new chat
+ useOnVSCodeMessage('startNewThread', (m) => {
+ setIsThreadSelectorOpen(false)
+ if (currentThread?.messages.length !== 0)
+ startNewThread()
+
+ })
+
+ // if they opened thread selector
+ useOnVSCodeMessage('toggleThreadSelector', (m) => {
+ setIsThreadSelectorOpen(v => !v)
+ })
+
+
+ const formRef = useRef(null)
+ const onSubmit = async (e: FormEvent) => {
+
+ e.preventDefault()
+ if (isLoading) return
+
+ setIsLoading(true)
+ setInstructions('');
+ formRef.current?.reset(); // reset the form's text
+ setSelection(null)
+ setFiles([])
+
+ // request file content from vscode and await response
+ getVSCodeAPI().postMessage({ type: 'requestFiles', filepaths: files })
+ const relevantFiles = await awaitVSCodeResponse('files')
+
+ // add message to chat history
+ const content = userInstructionsStr(instructions, relevantFiles.files, selection)
+ // console.log('prompt:\n', content)
+ const newHistoryElt: ChatMessage = { role: 'user', content, displayContent: instructions, selection, files }
+ addMessageToHistory(newHistoryElt)
+
+ // send message to LLM
+ let { abort } = sendLLMMessage({
+ messages: [...(currentThread?.messages ?? []).map(m => ({ role: m.role, content: m.content })), { role: 'user', content }],
+ onText: (newText, fullText) => setMessageStream(fullText),
+ onFinalMessage: (content) => {
+ // add assistant's message to chat history, and clear selection
+ const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, }
+ addMessageToHistory(newHistoryElt)
+
+ // clear selection
+ setMessageStream('')
+ setIsLoading(false)
+ },
+ apiConfig: apiConfig
+ })
+ abortFnRef.current = abort
+
+ }
+
+ const onStop = useCallback(() => {
+ // abort claude
+ abortFnRef.current?.()
+
+ // if messageStream was not empty, add it to the history
+ const llmContent = messageStream || '(canceled)'
+ const newHistoryElt: ChatMessage = { role: 'assistant', displayContent: messageStream, content: llmContent }
+ addMessageToHistory(newHistoryElt)
+
+ setMessageStream('')
+ setIsLoading(false)
+
+ }, [addMessageToHistory, messageStream])
+
+ //Clear code selection
+ const clearSelection = () => {
+ setSelection(null);
+ };
+
+
+ return <>
+
+ {/* previous messages */}
+ {currentThread !== null && currentThread.messages.map((message, i) =>
+
+ )}
+ {/* message stream */}
+
+
+ {/* chatbar */}
+
+ {/* selection */}
+
+
+
+
+ {/* selection */}
+ {(files.length || selection?.selectionStr) &&
+ {/* selected files */}
+
+ {/* selected code */}
+ {!!selection?.selectionStr && (
+
+ Remove
+
+ )} />
+ )}
+
}
+
+
+
+
+
+ >
+}
+
+
diff --git a/extensions/void/src/sidebar/SidebarThreadSelector.tsx b/extensions/void/src/sidebar/SidebarThreadSelector.tsx
new file mode 100644
index 00000000..287f0420
--- /dev/null
+++ b/extensions/void/src/sidebar/SidebarThreadSelector.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+import { ThreadsProvider, useThreads } from "./threadsContext";
+
+export const SidebarThreadSelector = ({ onClose }: { onClose: () => void }) => {
+ const { allThreads, currentThread, switchToThread } = useThreads()
+ return (
+
+
+ {/* iterate through all past threads */}
+ {Object.keys(allThreads ?? {}).map((threadId) => {
+ const pastThread = (allThreads ?? {})[threadId];
+ return (
+
+ )
+ })}
+
+ )
+}
\ No newline at end of file
diff --git a/extensions/void/src/sidebar/getVscodeApi.ts b/extensions/void/src/sidebar/getVscodeApi.ts
index 9e5172d7..bdc5b2ed 100644
--- a/extensions/void/src/sidebar/getVscodeApi.ts
+++ b/extensions/void/src/sidebar/getVscodeApi.ts
@@ -1,48 +1,77 @@
-import { Command, WebviewMessage } from "../shared_types";
+import { useEffect } from "react";
+import { MessageFromSidebar, MessageToSidebar, } from "../shared_types";
+import { v4 as uuidv4 } from 'uuid';
+type Command = MessageToSidebar['type']
-// message -> res[]
-const awaiting: { [c in Command]: ((res: any) => void)[] } = {
+// messageType -> res[]
+const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
"ctrl+l": [],
- "applyChanges": [],
- "requestFiles": [],
"files": [],
"apiConfig": [],
- "getApiConfig": [],
"startNewThread": [],
- "getAllThreads": [],
"allThreads": [],
- "persistThread": [],
"toggleThreadSelector": []
}
+// messageType -> id -> res
+const callbacks: { [C in Command]: { [id: string]: ((res: any) => void) } } = {
+ "ctrl+l": {},
+ "files": {},
+ "apiConfig": {},
+ "startNewThread": {},
+ "allThreads": {},
+ "toggleThreadSelector": {}
+}
+
+
// use this function to await responses
export const awaitVSCodeResponse = (c: C) => {
- let result: Promise = new Promise((res, rej) => {
- awaiting[c].push(res)
+ let result: Promise = new Promise((res, rej) => {
+ onetimeCallbacks[c].push(res)
})
return result
}
-export const resolveAwaitingVSCodeResponse = (m: WebviewMessage) => {
- // resolve all promises for this message
- for (let res of awaiting[m.type]) {
+
+// use this function to add a listener to a certain type of message
+export const useOnVSCodeMessage = (messageType: C, fn: (e: MessageToSidebar & { type: C }) => void) => {
+ useEffect(() => {
+ const mType = messageType
+ const callbackId: string = uuidv4();
+ // @ts-ignore
+ callbacks[mType][callbackId] = fn;
+ return () => { delete callbacks[mType][callbackId] }
+ }, [messageType, fn])
+}
+
+
+
+// this function gets called whenever sidebar receives a message - it should only mount once
+export const onMessageFromVSCode = (m: MessageToSidebar) => {
+ // resolve all promises for this message type
+ for (let res of onetimeCallbacks[m.type]) {
+ res(m)
+ onetimeCallbacks[m.type].splice(0) // clear the array
+ }
+ // call the listener for this message type
+ for (let res of Object.values(callbacks[m.type])) {
res(m)
- awaiting[m.type].splice(0) // clear the array
}
}
-// VS Code exposes the function acquireVsCodeApi() to us, it should only get called once
-let vsCodeApi: ReturnType | undefined;
type AcquireVsCodeApiType = () => {
- postMessage(message: WebviewMessage): void;
+ postMessage(message: MessageFromSidebar): void;
// setState(state: any): void; // getState and setState are made obsolete by us using { retainContextWhenHidden: true }
// getState(): any;
};
+// VS Code exposes the function acquireVsCodeApi() to us, this variable makes sure it only gets called once
+let vsCodeApi: ReturnType | undefined;
+
export function getVSCodeAPI(): ReturnType {
if (vsCodeApi)
return vsCodeApi;