diff --git a/extensions/void/package.json b/extensions/void/package.json
index 72bfb32a..e1d147c1 100644
--- a/extensions/void/package.json
+++ b/extensions/void/package.json
@@ -16,252 +16,7 @@
"configuration": {
"title": "Void",
"properties": {
- "void.whichApi": {
- "type": "string",
- "default": "anthropic",
- "description": "Choose an API provider.",
- "enum": [
- "openAI",
- "openRouter",
- "openAICompatible",
- "anthropic",
- "azure",
- "ollama",
- "greptile"
- ]
- },
- "void.anthropic.apiKey": {
- "type": "string",
- "default": "",
- "description": "Anthropic API key."
- },
- "void.anthropic.model": {
- "type": "string",
- "default": "claude-3-5-sonnet-20240620",
- "description": "Anthropic model to use.",
- "enum": [
- "claude-3-5-sonnet-20240620",
- "claude-3-opus-20240229",
- "claude-3-sonnet-20240229",
- "claude-3-haiku-20240307"
- ]
- },
- "void.anthropic.maxTokens": {
- "type": "string",
- "default": "8192",
- "description": "Anthropic max number of tokens to output.",
- "enum": [
- "1024",
- "2048",
- "4096",
- "8192"
- ]
- },
- "void.openAI.apiKey": {
- "type": "string",
- "default": "",
- "description": "OpenAI API key."
- },
- "void.openAI.model": {
- "type": "string",
- "default": "gpt-4o",
- "description": "OpenAI model to use.",
- "enum": [
- "o1-preview",
- "o1-mini",
- "gpt-4o",
- "gpt-4o-2024-05-13",
- "gpt-4o-2024-08-06",
- "gpt-4o-mini",
- "gpt-4o-mini-2024-07-18",
- "gpt-4-turbo",
- "gpt-4-turbo-2024-04-09",
- "gpt-4-turbo-preview",
- "gpt-4-0125-preview",
- "gpt-4-1106-preview",
- "gpt-4",
- "gpt-4-0613",
- "gpt-3.5-turbo-0125",
- "gpt-3.5-turbo",
- "gpt-3.5-turbo-1106"
- ]
- },
- "void.greptile.apiKey": {
- "type": "string",
- "default": "",
- "description": "Greptile API key."
- },
- "void.greptile.githubPAT": {
- "type": "string",
- "default": "",
- "description": "Github PAT given to Greptile to access your repository."
- },
- "void.greptile.remote": {
- "type": "string",
- "description": "remote provider",
- "enum": [
- "github",
- "gitlab"
- ]
- },
- "void.greptile.repository": {
- "type": "string",
- "description": "Repository identifier in \"owner/repository\" format."
- },
- "void.greptile.branch": {
- "type": "string",
- "default": "main",
- "description": "Name of the git branch."
- },
- "void.azure.apiKey": {
- "type": "string",
- "description": "Azure API key."
- },
- "void.azure.deploymentId": {
- "type": "string",
- "description": "Azure API deployment ID."
- },
- "void.azure.resourceName": {
- "type": "string",
- "description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`"
- },
- "void.azure.providerSettings": {
- "type": "object",
- "properties": {
- "baseURL": {
- "type": "string",
- "default": "https://${resourceName}.openai.azure.com/openai/deployments",
- "description": "Azure API base URL."
- },
- "headers": {
- "type": "object",
- "description": "Custom headers to include in the requests."
- }
- }
- },
- "void.ollama.endpoint": {
- "type": "string",
- "default": "http://127.0.0.1:11434",
- "description": "The Ollama endpoint. Start Ollama by running `OLLAMA_ORIGINS=\"vscode-webview://*\" ollama serve`"
- },
- "void.ollama.model": {
- "type": "string",
- "default": "llama3.1",
- "description": "Ollama model to use.",
- "enum": [
- "codegemma",
- "codegemma:2b",
- "codegemma:7b",
- "codellama",
- "codellama:7b",
- "codellama:13b",
- "codellama:34b",
- "codellama:70b",
- "codellama:code",
- "codellama:python",
- "command-r",
- "command-r:35b",
- "command-r-plus",
- "command-r-plus:104b",
- "deepseek-coder-v2",
- "deepseek-coder-v2:16b",
- "deepseek-coder-v2:236b",
- "falcon2",
- "falcon2:11b",
- "firefunction-v2",
- "firefunction-v2:70b",
- "gemma",
- "gemma:2b",
- "gemma:7b",
- "gemma2",
- "gemma2:2b",
- "gemma2:9b",
- "gemma2:27b",
- "llama2",
- "llama2:7b",
- "llama2:13b",
- "llama2:70b",
- "llama3",
- "llama3:8b",
- "llama3:70b",
- "llama3-chatqa",
- "llama3-chatqa:8b",
- "llama3-chatqa:70b",
- "llama3-gradient",
- "llama3-gradient:8b",
- "llama3-gradient:70b",
- "llama3.1",
- "llama3.1:8b",
- "llama3.1:70b",
- "llama3.1:405b",
- "llava",
- "llava:7b",
- "llava:13b",
- "llava:34b",
- "llava-llama3",
- "llava-llama3:8b",
- "llava-phi3",
- "llava-phi3:3.8b",
- "mistral",
- "mistral:7b",
- "mistral-large",
- "mistral-large:123b",
- "mistral-nemo",
- "mistral-nemo:12b",
- "mixtral",
- "mixtral:8x7b",
- "mixtral:8x22b",
- "moondream",
- "moondream:1.8b",
- "openhermes",
- "openhermes:v2.5",
- "phi3",
- "phi3:3.8b",
- "phi3:14b",
- "phi3.5",
- "phi3.5:3.8b",
- "qwen",
- "qwen:7b",
- "qwen:14b",
- "qwen:32b",
- "qwen:72b",
- "qwen:110b",
- "qwen2",
- "qwen2:0.5b",
- "qwen2:1.5b",
- "qwen2:7b",
- "qwen2:72b",
- "smollm",
- "smollm:135m",
- "smollm:360m",
- "smollm:1.7b"
- ]
- },
- "void.openRouter.model": {
- "type": "string",
- "default": "openai/gpt-4o",
- "description": "OpenRouter model to use."
- },
- "void.openRouter.apiKey": {
- "type": "string",
- "default": "",
- "description": "OpenRouter API key."
- },
- "void.openAICompatible.endpoint": {
- "type": "string",
- "default": "http://127.0.0.1:11434/v1",
- "description": "The endpoint."
- },
- "void.openAICompatible.model": {
- "type": "string",
- "default": "gpt-4o",
- "description": "The name of the model to use."
- },
- "void.openAICompatible.apiKey": {
- "type": "string",
- "default": "",
- "description": "Your API key."
- }
+
}
},
"commands": [
diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts
index f8ea1d97..764647b9 100644
--- a/extensions/void/src/common/sendLLMMessage.ts
+++ b/extensions/void/src/common/sendLLMMessage.ts
@@ -1,44 +1,9 @@
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';
import { Ollama } from 'ollama/browser'
+import { VoidConfig } from '../sidebar/contextForConfig';
-// always compare these against package.json to make sure every setting in this type can actually be provided by the user
-export type ApiConfig = {
- anthropic: {
- apikey: string,
- model: string,
- maxTokens: string
- },
- openAI: {
- apikey: string,
- model: string,
- },
- greptile: {
- apikey: string,
- githubPAT: string,
- repoinfo: {
- remote: string, // e.g. 'github'
- repository: string, // e.g. 'voideditor/void'
- branch: string // e.g. 'main'
- }
- },
- ollama: {
- endpoint: string,
- model: string
- },
- openAICompatible: {
- endpoint: string,
- model: string,
- apikey: string
- },
- openRouter: {
- model: string,
- apikey: string
- }
- whichApi: string
-}
-
type OnText = (newText: string, fullText: string) => void
@@ -52,7 +17,7 @@ type SendLLMMessageFnTypeInternal = (params: {
messages: LLMMessage[],
onText: OnText,
onFinalMessage: (input: string) => void,
- apiConfig: ApiConfig,
+ voidConfig: VoidConfig,
})
=> {
abort: () => void
@@ -62,7 +27,7 @@ type SendLLMMessageFnTypeExternal = (params: {
messages: LLMMessage[],
onText: OnText,
onFinalMessage: (input: string) => void,
- apiConfig: ApiConfig | null,
+ voidConfig: VoidConfig | null,
})
=> {
abort: () => void
@@ -72,13 +37,13 @@ type SendLLMMessageFnTypeExternal = (params: {
// Claude
-const sendClaudeMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => {
+const sendClaudeMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, voidConfig }) => {
- const anthropic = new Anthropic({ apiKey: apiConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
+ const anthropic = new Anthropic({ apiKey: voidConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
const stream = anthropic.messages.stream({
- model: apiConfig.anthropic.model,
- max_tokens: parseInt(apiConfig.anthropic.maxTokens),
+ model: voidConfig.anthropic.model,
+ max_tokens: parseInt(voidConfig.anthropic.maxTokens),
messages: messages,
});
@@ -113,7 +78,7 @@ const sendClaudeMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
// OpenAI, OpenRouter, OpenAICompatible
-const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => {
+const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, voidConfig }) => {
let didAbort = false
let fullText = ''
@@ -126,27 +91,27 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
let openai: OpenAI
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
- if (apiConfig.whichApi === 'openAI') {
- openai = new OpenAI({ apiKey: apiConfig.openAI.apikey, dangerouslyAllowBrowser: true });
- options = { model: apiConfig.openAI.model, messages: messages, stream: true, }
+ if (voidConfig.default.whichApi === 'openAI') {
+ openai = new OpenAI({ apiKey: voidConfig.openAI.apikey, dangerouslyAllowBrowser: true });
+ options = { model: voidConfig.openAI.model, messages: messages, stream: true, }
}
- else if (apiConfig.whichApi === 'openRouter') {
+ else if (voidConfig.default.whichApi === 'openRouter') {
openai = new OpenAI({
- baseURL: "https://openrouter.ai/api/v1", apiKey: apiConfig.openRouter.apikey, dangerouslyAllowBrowser: true,
+ baseURL: "https://openrouter.ai/api/v1", apiKey: voidConfig.openRouter.apikey, dangerouslyAllowBrowser: true,
defaultHeaders: {
"HTTP-Referer": 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings.
"X-Title": 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
},
});
- options = { model: apiConfig.openRouter.model, messages: messages, stream: true, }
+ options = { model: voidConfig.openRouter.model, messages: messages, stream: true, }
}
- else if (apiConfig.whichApi === 'openAICompatible') {
- openai = new OpenAI({ baseURL: apiConfig.openAICompatible.endpoint, apiKey: apiConfig.openAICompatible.apikey, dangerouslyAllowBrowser: true })
- options = { model: apiConfig.openAICompatible.model, messages: messages, stream: true, }
+ else if (voidConfig.default.whichApi === 'openAICompatible') {
+ openai = new OpenAI({ baseURL: voidConfig.openAICompatible.endpoint, apiKey: voidConfig.openAICompatible.apikey, dangerouslyAllowBrowser: true })
+ options = { model: voidConfig.openAICompatible.model, messages: messages, stream: true, }
}
else {
- console.error(`sendOpenAIMsg: invalid whichApi: ${apiConfig.whichApi}`)
- throw new Error(`apiConfig.whichAPI was invalid: ${apiConfig.whichApi}`)
+ console.error(`sendOpenAIMsg: invalid whichApi: ${voidConfig.default.whichApi}`)
+ throw new Error(`voidConfig.whichAPI was invalid: ${voidConfig.default.whichApi}`)
}
openai.chat.completions
@@ -177,7 +142,7 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
// Ollama
-export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => {
+export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, voidConfig }) => {
let didAbort = false
let fullText = ""
@@ -187,10 +152,10 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
didAbort = true;
};
- const ollama = new Ollama({ host: apiConfig.ollama.endpoint })
+ const ollama = new Ollama({ host: voidConfig.ollama.endpoint })
ollama.chat({
- model: apiConfig.ollama.model,
+ model: voidConfig.ollama.model,
messages: messages,
stream: true,
})
@@ -224,7 +189,7 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
// https://docs.greptile.com/api-reference/query
// https://docs.greptile.com/quickstart#sample-response-streamed
-const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => {
+const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, voidConfig }) => {
let didAbort = false
let fullText = ''
@@ -236,14 +201,14 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
fetch('https://api.greptile.com/v2/query', {
method: 'POST',
headers: {
- "Authorization": `Bearer ${apiConfig.greptile.apikey}`,
- "X-Github-Token": `${apiConfig.greptile.githubPAT}`,
+ "Authorization": `Bearer ${voidConfig.greptile.apikey}`,
+ "X-Github-Token": `${voidConfig.greptile.githubPAT}`,
"Content-Type": `application/json`,
},
body: JSON.stringify({
messages,
stream: true,
- repositories: [apiConfig.greptile.repoinfo]
+ repositories: [voidConfig.greptile.repoinfo]
}),
})
// this is {message}\n{message}\n{message}...\n
@@ -293,23 +258,23 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
-export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, apiConfig }) => {
- if (!apiConfig) return { abort: () => { } }
+export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, voidConfig }) => {
+ if (!voidConfig) return { abort: () => { } }
- switch (apiConfig.whichApi) {
+ switch (voidConfig.default.whichApi) {
case 'anthropic':
- return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig });
+ return sendClaudeMsg({ messages, onText, onFinalMessage, voidConfig });
case 'openAI':
case 'openRouter':
case 'openAICompatible':
- return sendOpenAIMsg({ messages, onText, onFinalMessage, apiConfig });
+ return sendOpenAIMsg({ messages, onText, onFinalMessage, voidConfig });
case 'greptile':
- return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig });
+ return sendGreptileMsg({ messages, onText, onFinalMessage, voidConfig });
case 'ollama':
- return sendOllamaMsg({ messages, onText, onFinalMessage, apiConfig });
+ return sendOllamaMsg({ messages, onText, onFinalMessage, voidConfig });
default:
- console.error(`Error: whichApi was ${apiConfig.whichApi}, which is not recognized!`);
+ console.error(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`);
return { abort: () => { } }
- //return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }); // TODO
+ //return sendClaudeMsg({ messages, onText, onFinalMessage, voidConfig }); // TODO
}
}
diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts
index 55c53350..f42074b8 100644
--- a/extensions/void/src/extension.ts
+++ b/extensions/void/src/extension.ts
@@ -2,53 +2,12 @@ import * as vscode from 'vscode';
import { DisplayChangesProvider } from './DisplayChangesProvider';
import { BaseDiffArea, ChatThreads, MessageFromSidebar, MessageToSidebar } from './shared_types';
import { SidebarWebviewProvider } from './SidebarWebviewProvider';
-import { ApiConfig } from './common/sendLLMMessage';
const readFileContentOfUri = async (uri: vscode.Uri) => {
return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')
.replace(/\r\n/g, '\n') // replace windows \r\n with \n
}
-
-const getApiConfig = () => {
- const apiConfig: ApiConfig = {
- anthropic: {
- apikey: vscode.workspace.getConfiguration('void.anthropic').get('apiKey') ?? '',
- model: vscode.workspace.getConfiguration('void.anthropic').get('model') ?? '',
- maxTokens: vscode.workspace.getConfiguration('void.anthropic').get('maxTokens') ?? '',
- },
- openAI: {
- apikey: vscode.workspace.getConfiguration('void.openAI').get('apiKey') ?? '',
- model: vscode.workspace.getConfiguration('void.openAI').get('model') ?? '',
- },
- greptile: {
- apikey: vscode.workspace.getConfiguration('void.greptile').get('apiKey') ?? '',
- githubPAT: vscode.workspace.getConfiguration('void.greptile').get('githubPAT') ?? '',
- repoinfo: {
- remote: 'github',
- repository: 'TODO',
- branch: 'main'
- }
- },
- ollama: {
- endpoint: vscode.workspace.getConfiguration('void.ollama').get('endpoint') ?? '',
- model: vscode.workspace.getConfiguration('void.ollama').get('model') ?? '',
- },
- openAICompatible: {
- endpoint: vscode.workspace.getConfiguration('void.openAICompatible').get('endpoint') ?? '',
- model: vscode.workspace.getConfiguration('void.openAICompatible').get('model') ?? '',
- apikey: vscode.workspace.getConfiguration('void.openAICompatible').get('apiKey') ?? '',
- },
- openRouter: {
- model: vscode.workspace.getConfiguration('void.openRouter').get('model') ?? '',
- apikey: vscode.workspace.getConfiguration('void.openRouter').get('apiKey') ?? '',
- },
- whichApi: vscode.workspace.getConfiguration('void').get('whichApi') ?? ''
- }
- return apiConfig
-}
-
-
export function activate(context: vscode.ExtensionContext) {
// 1. Mount the chat sidebar
@@ -111,15 +70,6 @@ export function activate(context: vscode.ExtensionContext) {
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 MessageToSidebar)
- }
- })
-
-
// Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`)
webview.onDidReceiveMessage(async (m: MessageFromSidebar) => {
@@ -166,9 +116,13 @@ export function activate(context: vscode.ExtensionContext) {
displayChangesProvider.refreshDiffAreas(editor.document.uri)
}
- else if (m.type === 'getApiConfig') {
- const apiConfig = getApiConfig()
- webview.postMessage({ type: 'apiConfig', apiConfig } satisfies MessageToSidebar)
+ else if (m.type === 'getPartialVoidConfig') {
+ const partialVoidConfig = context.globalState.get('partialVoidConfig') ?? {}
+ webview.postMessage({ type: 'partialVoidConfig', partialVoidConfig } satisfies MessageToSidebar)
+ }
+ else if (m.type === 'persistPartialVoidConfig') {
+ const partialVoidConfig = m.partialVoidConfig
+ context.workspaceState.update('partialVoidConfig', partialVoidConfig)
}
else if (m.type === 'getAllThreads') {
const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {}
diff --git a/extensions/void/src/shared_types.ts b/extensions/void/src/shared_types.ts
index 0fcbef42..eed9d07b 100644
--- a/extensions/void/src/shared_types.ts
+++ b/extensions/void/src/shared_types.ts
@@ -1,6 +1,6 @@
import * as vscode from 'vscode';
-import { ApiConfig } from './common/sendLLMMessage';
+import { PartialVoidConfig } from './sidebar/contextForConfig';
@@ -42,7 +42,7 @@ type Diff = {
type MessageToSidebar = (
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor
| { type: 'files', files: { filepath: vscode.Uri, content: string }[] }
- | { type: 'apiConfig', apiConfig: ApiConfig }
+ | { type: 'partialVoidConfig', partialVoidConfig: PartialVoidConfig }
| { type: 'allThreads', threads: ChatThreads }
| { type: 'startNewThread' }
| { type: 'toggleThreadSelector' }
@@ -52,7 +52,8 @@ type MessageToSidebar = (
type MessageFromSidebar = (
| { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar
| { type: 'requestFiles', filepaths: vscode.Uri[] }
- | { type: 'getApiConfig' }
+ | { type: 'getPartialVoidConfig' }
+ | { type: 'persistPartialVoidConfig', partialVoidConfig: PartialVoidConfig }
| { type: 'getAllThreads' }
| { type: 'persistThread', thread: ChatThreads[string] }
)
diff --git a/extensions/void/src/sidebar/Sidebar.tsx b/extensions/void/src/sidebar/Sidebar.tsx
index e41e87e4..0ad474fb 100644
--- a/extensions/void/src/sidebar/Sidebar.tsx
+++ b/extensions/void/src/sidebar/Sidebar.tsx
@@ -1,10 +1,9 @@
import React, { useState, useEffect, useRef, useCallback, FormEvent } from "react"
-import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage"
import { CodeSelection, ChatMessage, MessageToSidebar } from "../shared_types"
-import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode } from "./getVscodeApi"
+import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi"
import { SidebarThreadSelector } from "./SidebarThreadSelector";
-import { useThreads } from "./threadsContext";
+import { useThreads } from "./contextForThreads";
import { SidebarChat } from "./SidebarChat";
@@ -12,10 +11,16 @@ import { SidebarChat } from "./SidebarChat";
const Sidebar = () => {
const [isThreadSelectorOpen, setIsThreadSelectorOpen] = useState(false)
- // get Api Config on mount
- useEffect(() => {
- getVSCodeAPI().postMessage({ type: 'getApiConfig' })
- }, [])
+ // if they pressed the + to add a new chat
+ useOnVSCodeMessage('startNewThread', (m) => {
+ setIsThreadSelectorOpen(false)
+ })
+
+ // if they toggled thread selector
+ useOnVSCodeMessage('toggleThreadSelector', (m) => {
+ setIsThreadSelectorOpen(v => !v)
+ })
+
// Receive messages from the VSCode extension
useEffect(() => {
@@ -36,7 +41,7 @@ const Sidebar = () => {
)}
-
+
>
diff --git a/extensions/void/src/sidebar/SidebarChat.tsx b/extensions/void/src/sidebar/SidebarChat.tsx
index 4b029a66..272a369c 100644
--- a/extensions/void/src/sidebar/SidebarChat.tsx
+++ b/extensions/void/src/sidebar/SidebarChat.tsx
@@ -8,8 +8,9 @@ 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";
+import { useThreads } from "./contextForThreads";
+import { sendLLMMessage } from "../common/sendLLMMessage";
+import { useVoidConfig } from "./contextForConfig";
@@ -70,7 +71,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
}
-export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOpen: (v: boolean | ((v: boolean) => boolean)) => void }) => {
+export const SidebarChat = () => {
// state of current message
@@ -85,8 +86,13 @@ export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOp
// higher level state
const { allThreads, currentThread, addMessageToHistory, startNewThread, } = useThreads()
- const [apiConfig, setApiConfig] = useState(null)
+ const { voidConfig } = useVoidConfig()
+ // if they pressed the + to add a new chat
+ useOnVSCodeMessage('startNewThread', (m) => {
+ if (currentThread?.messages.length !== 0)
+ startNewThread()
+ })
// if user pressed ctrl+l, add their selection to the sidebar
useOnVSCodeMessage('ctrl+l', (m) => {
@@ -98,24 +104,6 @@ export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOp
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) => {
@@ -152,7 +140,7 @@ export const SidebarChat = ({ setIsThreadSelectorOpen }: { setIsThreadSelectorOp
setMessageStream('')
setIsLoading(false)
},
- apiConfig: apiConfig
+ voidConfig: voidConfig
})
abortFnRef.current = abort
diff --git a/extensions/void/src/sidebar/SidebarSettings.tsx b/extensions/void/src/sidebar/SidebarSettings.tsx
new file mode 100644
index 00000000..56118f0b
--- /dev/null
+++ b/extensions/void/src/sidebar/SidebarSettings.tsx
@@ -0,0 +1,16 @@
+import React, { useState } from "react";
+import { useVoidConfig } from "./contextForConfig";
+
+export const SidebarSettings = () => {
+
+ const { voidConfig, setConfigParam } = useVoidConfig()
+
+
+
+ // only show the settings relevant to the current field
+
+ voidConfig.default.whichApi
+
+}
+
+
diff --git a/extensions/void/src/sidebar/SidebarThreadSelector.tsx b/extensions/void/src/sidebar/SidebarThreadSelector.tsx
index 287f0420..59faa41f 100644
--- a/extensions/void/src/sidebar/SidebarThreadSelector.tsx
+++ b/extensions/void/src/sidebar/SidebarThreadSelector.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { ThreadsProvider, useThreads } from "./threadsContext";
+import { ThreadsProvider, useThreads } from "./contextForThreads";
export const SidebarThreadSelector = ({ onClose }: { onClose: () => void }) => {
const { allThreads, currentThread, switchToThread } = useThreads()
diff --git a/extensions/void/src/sidebar/contextForConfig.tsx b/extensions/void/src/sidebar/contextForConfig.tsx
new file mode 100644
index 00000000..708ca286
--- /dev/null
+++ b/extensions/void/src/sidebar/contextForConfig.tsx
@@ -0,0 +1,354 @@
+import React, { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"
+import { awaitVSCodeResponse, getVSCodeAPI, useOnVSCodeMessage } from "./getVscodeApi"
+
+const configEnum = (description: string, defaultVal: EnumArr[number], enumArr: EnumArr) => {
+ return {
+ description,
+ defaultVal,
+ enumArr,
+ }
+}
+
+const configString = (description: string, defaultVal: string) => {
+ return {
+ description,
+ defaultVal,
+ enumArr: undefined,
+ }
+}
+
+
+
+
+const configFields = [
+ 'anthropic',
+ 'openAI',
+ 'greptile',
+ 'ollama',
+ 'openRouter',
+ 'openAICompatible',
+ 'azure'
+] as const
+
+
+
+const voidConfigInfo: Record<
+ typeof configFields[number] | 'default', {
+ [prop: string]: {
+ description: string,
+ enumArr?: readonly string[] | undefined,
+ defaultVal: string,
+ },
+ }
+> = {
+ default: {
+ whichApi: configEnum(
+ "Choose an API provider.",
+ 'anthropic',
+ configFields,
+ ),
+ },
+ anthropic: {
+ apikey: configString('Anthropic API key.', ''),
+ model: configEnum(
+ "Anthropic model to use.",
+ 'claude-3-5-sonnet-20240620',
+ [
+ "claude-3-5-sonnet-20240620",
+ "claude-3-opus-20240229",
+ "claude-3-sonnet-20240229",
+ "claude-3-haiku-20240307"
+ ] as const,
+ ),
+
+ maxTokens: configEnum(
+ "Anthropic max number of tokens to output.",
+ '8192',
+ [
+ "1024",
+ "2048",
+ "4096",
+ "8192"
+ ] as const,
+ ),
+ },
+ openAI: {
+ apikey: configString('OpenAI API key.', ''),
+ model: configEnum(
+ 'OpenAI model to use.',
+ 'gpt-4o',
+ [
+ "o1-preview",
+ "o1-mini",
+ "gpt-4o",
+ "gpt-4o-2024-05-13",
+ "gpt-4o-2024-08-06",
+ "gpt-4o-mini",
+ "gpt-4o-mini-2024-07-18",
+ "gpt-4-turbo",
+ "gpt-4-turbo-2024-04-09",
+ "gpt-4-turbo-preview",
+ "gpt-4-0125-preview",
+ "gpt-4-1106-preview",
+ "gpt-4",
+ "gpt-4-0613",
+ "gpt-3.5-turbo-0125",
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-1106"
+ ] as const
+ ),
+ },
+ greptile: {
+ apikey: configString('Greptile API key.', ''),
+ githubPAT: configString('Github PAT that Greptile uses to access your repository', ''),
+ remote: configEnum(
+ 'Repo location',
+ 'github',
+ [
+ 'github',
+ 'gitlab'
+ ] as const
+ ),
+ repository: configString('Repository identifier in "owner/repository" format.', ''),
+ branch: configString('Name of the branch to use.', 'main'),
+ },
+ ollama: {
+ endpoint: configString(
+ 'The Ollama endpoint. Start Ollama by running `OLLAMA_ORIGINS="vscode-webview://*" ollama serve`',
+ 'http://127.0.0.1:11434'
+ ),
+ model: configEnum(
+ 'Ollama model to use.',
+ 'llama3.1',
+ [
+ "codegemma",
+ "codegemma:2b",
+ "codegemma:7b",
+ "codellama",
+ "codellama:7b",
+ "codellama:13b",
+ "codellama:34b",
+ "codellama:70b",
+ "codellama:code",
+ "codellama:python",
+ "command-r",
+ "command-r:35b",
+ "command-r-plus",
+ "command-r-plus:104b",
+ "deepseek-coder-v2",
+ "deepseek-coder-v2:16b",
+ "deepseek-coder-v2:236b",
+ "falcon2",
+ "falcon2:11b",
+ "firefunction-v2",
+ "firefunction-v2:70b",
+ "gemma",
+ "gemma:2b",
+ "gemma:7b",
+ "gemma2",
+ "gemma2:2b",
+ "gemma2:9b",
+ "gemma2:27b",
+ "llama2",
+ "llama2:7b",
+ "llama2:13b",
+ "llama2:70b",
+ "llama3",
+ "llama3:8b",
+ "llama3:70b",
+ "llama3-chatqa",
+ "llama3-chatqa:8b",
+ "llama3-chatqa:70b",
+ "llama3-gradient",
+ "llama3-gradient:8b",
+ "llama3-gradient:70b",
+ "llama3.1",
+ "llama3.1:8b",
+ "llama3.1:70b",
+ "llama3.1:405b",
+ "llava",
+ "llava:7b",
+ "llava:13b",
+ "llava:34b",
+ "llava-llama3",
+ "llava-llama3:8b",
+ "llava-phi3",
+ "llava-phi3:3.8b",
+ "mistral",
+ "mistral:7b",
+ "mistral-large",
+ "mistral-large:123b",
+ "mistral-nemo",
+ "mistral-nemo:12b",
+ "mixtral",
+ "mixtral:8x7b",
+ "mixtral:8x22b",
+ "moondream",
+ "moondream:1.8b",
+ "openhermes",
+ "openhermes:v2.5",
+ "phi3",
+ "phi3:3.8b",
+ "phi3:14b",
+ "phi3.5",
+ "phi3.5:3.8b",
+ "qwen",
+ "qwen:7b",
+ "qwen:14b",
+ "qwen:32b",
+ "qwen:72b",
+ "qwen:110b",
+ "qwen2",
+ "qwen2:0.5b",
+ "qwen2:1.5b",
+ "qwen2:7b",
+ "qwen2:72b",
+ "smollm",
+ "smollm:135m",
+ "smollm:360m",
+ "smollm:1.7b"
+ ] as const
+ ),
+ },
+ openRouter: {
+ model: configString(
+ 'OpenRouter model to use.',
+ 'openai/gpt-4o'
+ ),
+ apikey: configString('OpenRouter API key.', ''),
+ },
+ openAICompatible: {
+ endpoint: configString('The endpoint.', 'http://127.0.0.1:11434/v1'),
+ model: configString('The name of the model to use.', 'gpt-4o'),
+ apikey: configString('Your API key.', ''),
+ },
+ azure: {
+ // "void.azure.apiKey": {
+ // "type": "string",
+ // "description": "Azure API key."
+ // },
+ // "void.azure.deploymentId": {
+ // "type": "string",
+ // "description": "Azure API deployment ID."
+ // },
+ // "void.azure.resourceName": {
+ // "type": "string",
+ // "description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`"
+ // },
+ // "void.azure.providerSettings": {
+ // "type": "object",
+ // "properties": {
+ // "baseURL": {
+ // "type": "string",
+ // "default": "https://${resourceName}.openai.azure.com/openai/deployments",
+ // "description": "Azure API base URL."
+ // },
+ // "headers": {
+ // "type": "object",
+ // "description": "Custom headers to include in the requests."
+ // }
+ // }
+ // },
+ },
+}
+
+
+// this is the type that comes with metadata like desc, default val, etc
+type VoidConfigInfo = typeof voidConfigInfo
+
+// this is the type that specifies the user's actual config
+export type PartialVoidConfig = {
+ [K in keyof typeof voidConfigInfo]?: {
+ [P in keyof typeof voidConfigInfo[K]]?: string
+ }
+}
+
+export type VoidConfig = {
+ [K in keyof typeof voidConfigInfo]: {
+ [P in keyof typeof voidConfigInfo[K]]: string
+ }
+}
+
+
+
+const getVoidConfig = (currentConfig: PartialVoidConfig): VoidConfig => {
+ const config = {} as PartialVoidConfig
+ for (let field of configFields) {
+ config[field] = {}
+ for (let prop in voidConfigInfo[field]) {
+ config[field][prop] = currentConfig[field]?.[prop] || voidConfigInfo[field][prop].defaultVal
+ }
+ }
+ return config as VoidConfig
+}
+
+const defaultVoidConfig: VoidConfig = getVoidConfig({})
+
+// const [stateRef, setState] = useInstantState(initVal)
+// setState instantly changes the value of stateRef instead of having to wait until the next render
+const useInstantState = (initVal: T) => {
+ const stateRef = useRef(initVal)
+ const [_, setS] = useState(initVal)
+ const setState = useCallback((newVal: T) => {
+ setS(newVal);
+ stateRef.current = newVal;
+ }, [])
+ return [stateRef as React.RefObject, setState] as const // make s.current readonly - setState handles all changes
+}
+
+
+
+type ConfigValueType = {
+ voidConfig: VoidConfig,
+ setConfigParam: (field: K, param: keyof VoidConfigInfo[K], newVal: string) => void
+}
+
+
+const ConfigContext = createContext(undefined as unknown as ConfigValueType)
+
+export function ConfigProvider({ children }: { children: ReactNode }) {
+ const [partialVoidConfig, setPartialVoidConfig] = useInstantState({}) // only used internally here, and to communicate with the extension
+ const [voidConfig, setVoidConfig] = useState(defaultVoidConfig)
+
+
+ // get the config on mount
+ useEffect(() => {
+ getVSCodeAPI().postMessage({ type: 'getPartialVoidConfig' })
+ awaitVSCodeResponse('partialVoidConfig').then((m) => {
+ setPartialVoidConfig(m.partialVoidConfig)
+ const newFullConfig = getVoidConfig(m.partialVoidConfig)
+ setVoidConfig(newFullConfig)
+ })
+ }, [setPartialVoidConfig])
+
+ // return the provider
+ return ( {
+ const newPartialConfig: PartialVoidConfig = {
+ ...partialVoidConfig.current,
+ [field]: {
+ ...partialVoidConfig.current?.[field],
+ [param]: newVal
+ }
+ }
+ setPartialVoidConfig(newPartialConfig)
+ const newFullConfig = getVoidConfig(newPartialConfig)
+ setVoidConfig(newFullConfig)
+ }
+ }}
+ >
+ {children}
+
+ )
+}
+
+export function useVoidConfig(): ConfigValueType {
+ const context = useContext(ConfigContext)
+ if (context === undefined) {
+ throw new Error("useVoidConfig missing Provider")
+ }
+ return context
+}
+
diff --git a/extensions/void/src/sidebar/threadsContext.tsx b/extensions/void/src/sidebar/contextForThreads.tsx
similarity index 86%
rename from extensions/void/src/sidebar/threadsContext.tsx
rename to extensions/void/src/sidebar/contextForThreads.tsx
index 5ade37f4..5e9b5fd9 100644
--- a/extensions/void/src/sidebar/threadsContext.tsx
+++ b/extensions/void/src/sidebar/contextForThreads.tsx
@@ -3,7 +3,8 @@ import { ChatMessage, ChatThreads } from "../shared_types"
import { awaitVSCodeResponse, getVSCodeAPI } from "./getVscodeApi"
-type ThreadsContextValue = {
+// a "thread" means a chat message history
+type ConfigForThreadsValueType = {
readonly allThreads: ChatThreads | null,
readonly currentThread: ChatThreads[string] | null;
addMessageToHistory: (message: ChatMessage) => void;
@@ -11,7 +12,7 @@ type ThreadsContextValue = {
startNewThread: () => void;
}
-const ThreadsContext = createContext(undefined as unknown as ThreadsContextValue)
+const ThreadsContext = createContext(undefined as unknown as ConfigForThreadsValueType)
const createNewThread = () => ({
id: new Date().getTime().toString(),
@@ -39,7 +40,7 @@ export function ThreadsProvider({ children }: { children: ReactNode }) {
// this loads allThreads in on mount
useEffect(() => {
- getVSCodeAPI().postMessage({ type: "getAllThreads" })
+ getVSCodeAPI().postMessage({ type: 'getAllThreads' })
awaitVSCodeResponse('allThreads')
.then(response => {
setAllThreads(response.threads)
@@ -90,10 +91,10 @@ export function ThreadsProvider({ children }: { children: ReactNode }) {
)
}
-export function useThreads(): ThreadsContextValue {
- const context = useContext(ThreadsContext)
+export function useThreads(): ConfigForThreadsValueType {
+ const context = useContext(ThreadsContext)
if (context === undefined) {
- throw new Error("useThreads must be used within a ThreadsProvider")
+ throw new Error("useThreads missing Provider")
}
return context
}
diff --git a/extensions/void/src/sidebar/getVscodeApi.ts b/extensions/void/src/sidebar/getVscodeApi.ts
index bdc5b2ed..ffe8a18c 100644
--- a/extensions/void/src/sidebar/getVscodeApi.ts
+++ b/extensions/void/src/sidebar/getVscodeApi.ts
@@ -9,7 +9,7 @@ type Command = MessageToSidebar['type']
const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
"ctrl+l": [],
"files": [],
- "apiConfig": [],
+ "partialVoidConfig": [],
"startNewThread": [],
"allThreads": [],
"toggleThreadSelector": []
@@ -19,7 +19,7 @@ const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
const callbacks: { [C in Command]: { [id: string]: ((res: any) => void) } } = {
"ctrl+l": {},
"files": {},
- "apiConfig": {},
+ "partialVoidConfig": {},
"startNewThread": {},
"allThreads": {},
"toggleThreadSelector": {}
diff --git a/extensions/void/src/sidebar/index.tsx b/extensions/void/src/sidebar/index.tsx
index 7b01c5db..ac226c95 100644
--- a/extensions/void/src/sidebar/index.tsx
+++ b/extensions/void/src/sidebar/index.tsx
@@ -1,7 +1,8 @@
import * as React from "react"
import * as ReactDOM from "react-dom/client"
import Sidebar from "./Sidebar"
-import { ThreadsProvider } from "./threadsContext"
+import { ThreadsProvider } from "./contextForThreads"
+import { ConfigProvider } from "./contextForConfig"
// mount the sidebar on the id="root" element
if (typeof document === "undefined") {
@@ -13,7 +14,9 @@ console.log("Void root Element:", rootElement)
const extension = (
-
+
+
+
)
const root = ReactDOM.createRoot(rootElement)