From e5ac36facda502216b05be70d311ce23faafd627 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 9 Dec 2024 02:41:27 -0800 Subject: [PATCH] add new config type for each feature, UI dump --- src/vs/platform/void/common/configTypes.ts | 449 +++++++++--------- .../platform/void/common/llmMessageTypes.ts | 9 +- .../electron-main/llmMessage/anthropic.ts | 4 +- .../void/electron-main/llmMessage/gemini.ts | 2 +- .../void/electron-main/llmMessage/greptile.ts | 108 ++--- .../void/electron-main/llmMessage/groq.ts | 8 +- .../void/electron-main/llmMessage/ollama.ts | 2 +- .../void/electron-main/llmMessage/openai.ts | 25 +- .../llmMessage/sendLLMMessage.ts | 43 +- .../void/electron-main/llmMessage/util.ts | 5 +- ...kdownRender.tsx => ChatMarkdownRender.tsx} | 6 +- .../browser/react/src/sidebar-tsx/Sidebar.tsx | 9 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 11 +- .../src/sidebar-tsx/SidebarModelSettings.tsx | 37 ++ .../sidebar-tsx/SidebarProviderSettings.tsx | 62 +++ .../react/src/sidebar-tsx/SidebarSettings.tsx | 254 +++++----- .../browser/react/src/sidebar-tsx/inputs.tsx | 66 +-- .../void/browser/react/src/util/services.tsx | 6 +- .../contrib/void/browser/registerConfig.ts | 82 ++-- .../void/browser/registerInlineDiffs.ts | 24 +- .../contrib/void/browser/registerSidebar.ts | 7 +- 21 files changed, 667 insertions(+), 552 deletions(-) rename src/vs/workbench/contrib/void/browser/react/src/markdown/{MarkdownRender.tsx => ChatMarkdownRender.tsx} (94%) create mode 100644 src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarModelSettings.tsx create mode 100644 src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx diff --git a/src/vs/platform/void/common/configTypes.ts b/src/vs/platform/void/common/configTypes.ts index 5b586316..0c121809 100644 --- a/src/vs/platform/void/common/configTypes.ts +++ b/src/vs/platform/void/common/configTypes.ts @@ -4,19 +4,19 @@ * Void Editor additions licensed under the AGPLv3 License. *--------------------------------------------------------------------------------------------*/ -const voidProviderDefaults = { - anthropic: { - apiKey: '', + +export const voidInitModelOptions = { + anthropic: () => ({ + model: 'claude-3-5-sonnet-20240620', models: [ 'claude-3-5-sonnet-20240620', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307' ], - model: 'claude-3-5-sonnet-20240620', - }, - openAI: { - apiKey: '', + }), + openAI: () => ({ + model: 'gpt-4o', models: [ 'o1-preview', 'o1-mini', @@ -34,241 +34,256 @@ const voidProviderDefaults = { 'gpt-4-0613', 'gpt-3.5-turbo-0125', 'gpt-3.5-turbo', - 'gpt-3.5-turbo-1106' + 'gpt-3.5-turbo-1106', ], - model: 'gpt-4o', - }, - ollama: { - endpoint: 'http://127.0.0.1:11434', //'The endpoint of your Ollama instance.', - models: ['codestral', 'qwen2.5-coder', 'qwen2.5-coder:0.5b', 'qwen2.5-coder:1.5b', 'qwen2.5-coder:3b', 'qwen2.5-coder:7b', 'qwen2.5-coder:14b', 'qwen2.5-coder:32b', '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, + }), + ollama: () => ({ // TODO make this do a fetch to get the models model: 'codestral', - - }, - openRouter: { - apiKey: '', - models: ['openai/gpt-4o'], + models: [ + 'codestral', + 'qwen2.5-coder', + 'qwen2.5-coder:0.5b', + 'qwen2.5-coder:1.5b', + 'qwen2.5-coder:3b', + 'qwen2.5-coder:7b', + 'qwen2.5-coder:14b', + 'qwen2.5-coder:32b', + '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', + ], + }), + openRouter: () => ({ model: 'openai/gpt-4o', - }, - openAICompatible: { - apiKey: '', - endpoint: 'http://127.0.0.1:11434/v1', //'The baseUrl (exluding /chat/completions).', - models: ['gpt-4o'], - model: 'gpt-4o', - }, - gemini: { - apiKey: '', + models: null, // any + }), + openAICompatible: () => ({ + model: 'openai/gpt-4o', + models: null, // any + }), + gemini: () => ({ + model: 'gemini-1.5-flash', models: [ 'gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-1.5-flash-8b', 'gemini-1.0-pro' ], - model: 'gemini-1.5-flash', + }), + groq: () => ({ + model: 'mixtral-8x7b-32768', + models: [ + "mixtral-8x7b-32768", + "llama2-70b-4096", + "gemma-7b-it" + ] + }) +} + +export const voidProviderDefaults = { + anthropic: { + apiKey: '', }, + openAI: { + apiKey: '', + }, + ollama: { + endpoint: 'http://127.0.0.1:11434', + }, + openRouter: { + apiKey: '', + }, + openAICompatible: { + apiKey: '', + endpoint: 'http://127.0.0.1:11434/v1', + }, + gemini: { + apiKey: '', + }, + groq: { + apiKey: '' + } } as const -type VoidSettings = typeof voidProviderDefaults + + +export type ProviderName = keyof typeof voidProviderDefaults +export const providerNames = Object.keys(voidProviderDefaults) as ProviderName[] + + +export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete'] +export type FeatureName = typeof featureNames[number] - -// was whichApi: -const providerOptions = _uiConfig( - 'API Provider.', - 'anthropic', - allowedProviders, -) - -const voidFeatureOptions = { - maxTokens: _uiConfig( - 'Max number of tokens to output.', - 'undefined', - [ - 'undefined', - '1024', - '2048', - '4096', - '8192' - ] as const, - ) -} as const - - -type AllVoidProvidersState = { - [providerName in ProviderName]: { - [option in keyof typeof voidProviderOptions[providerName]['providerOptions']]: string // optionName (e.g. apikey) -> string - } +export type VoidConfigState = { + [providerName in ProviderName]: ({ + enabled: string, // 'true' | 'false' + models: string[], + model: string, + maxTokens: string, + } & { + [optionName in keyof typeof voidProviderDefaults[providerName]]: string + }) } -// const features = ['ctrl+L', 'ctrl+K', 'autocomplete'] as const -// type FeatureName = (typeof features)[number] +type UnionOfKeys = T extends T ? keyof T : never; +export const descOfSettingName = (providerName: ProviderName, settingName: UnionOfKeys) => { + if (settingName === 'apiKey') + return 'API Key' + else if (settingName === 'endpoint') { + if (providerName === 'ollama') return 'The endpoint of your Ollama instance.' + if (providerName === 'openAICompatible') return 'The baseUrl (exluding /chat/completions).' + } + else if (settingName === 'maxTokens') + return 'Max Tokens' + else if (settingName === 'model') + return 'Model' + else if (settingName === 'enabled') + return 'Enabled' + else if (settingName === 'models') + return 'Available Models' + + throw new Error(`Unknown setting name: "${settingName}"`) +} -// not very important (remember past user options): -// type AllVoidFeaturesState = { -// [featureName in FeatureName]: { -// [providerName in ProviderName]: { -// [modelName in (typeof voidProviderOptions)[providerName]['modelOptions']['defaultVal']]: { -// options: { [option in keyof typeof voidProviderOptions[providerName]]: string } -// } -// } -// } -// } -type VoidFeatureState< - CtrlLProvider extends ProviderName, - CtrlKProvider extends ProviderName, - AutocompleteProvider extends ProviderName, -> = { - 'ctrl+L': { - provider: CtrlLProvider, - model: (typeof voidProviderOptions)[CtrlLProvider]['modelOptions']['defaultVal'], - // promptTemplate? - // systemTemplate? - // maxTokens? +export const defaultVoidConfigState: VoidConfigState = { + anthropic: { + enabled: 'false', + models: [], + model: '', + apiKey: '', + maxTokens: '1000', }, - 'ctrl+K': { - provider: CtrlKProvider, - model: (typeof voidProviderOptions)[CtrlKProvider]['modelOptions']['defaultVal'], + openAI: { + enabled: 'false', + models: [], + model: '', + apiKey: '', + maxTokens: '1000', }, - 'autocomplete': { - provider: AutocompleteProvider, - model: (typeof voidProviderOptions)[AutocompleteProvider]['modelOptions']['defaultVal'], + ollama: { + enabled: 'false', + models: [], + model: '', + endpoint: '', + maxTokens: '1000', }, -} - - - - -const PartialVoidState = { - -} - -const VoidState = {} - - -// this is the type that comes with metadata like desc, default val, etc -export type VoidConfigInfo = typeof voidProviderOptions -export type VoidConfigField = keyof typeof voidProviderOptions // typeof configFields[number] - -// this is the type that specifies the user's actual config -export type PartialVoidConfig = { - [K in keyof typeof voidProviderOptions]?: { - [P in keyof typeof voidProviderOptions[K]]?: typeof voidProviderOptions[K][P]['defaultVal'] + openRouter: { + enabled: 'false', + models: [], + model: '', + apiKey: '', + maxTokens: '1000', + }, + openAICompatible: { + enabled: 'false', + models: [], + model: '', + apiKey: '', + endpoint: '', + maxTokens: '1000', + }, + gemini: { + enabled: 'false', + models: [], + model: '', + apiKey: '', + maxTokens: '1000', + }, + groq: { + enabled: 'false', + models: [], + model: '', + apiKey: '', + maxTokens: '1000', } } - -export type VoidConfig = { - [K in keyof typeof voidProviderOptions]: { - [P in keyof typeof voidProviderOptions[K]]: typeof voidProviderOptions[K][P]['defaultVal'] - } -} - - -const getVoidConfig = (partialVoidConfig: PartialVoidConfig): VoidConfig => { - const config = {} as PartialVoidConfig - for (const field of [...allowedProviders, 'default'] as const) { - config[field] = {} - for (const prop in voidProviderOptions[field]) { - config[field][prop] = partialVoidConfig[field]?.[prop]?.trim() || voidProviderOptions[field][prop].defaultVal - } - } - return config as VoidConfig -} - - -const VOID_CONFIG_KEY = 'void.partialVoidConfig' - -export type SetFieldFnType = (field: K, param: keyof VoidConfigInfo[K], newVal: string) => Promise; - -export type ConfigState = { - partialVoidConfig: PartialVoidConfig; // free parameter - voidConfig: VoidConfig; // computed from partialVoidConfig -} - -export interface IVoidConfigStateService { - readonly _serviceBrand: undefined; - readonly state: ConfigState; - readonly voidConfigInfo: VoidConfigInfo; - onDidChangeState: Event; - setField: SetFieldFnType; -} - -export const IVoidConfigStateService = createDecorator('VoidConfigStateService'); -class VoidConfigStateService extends Disposable implements IVoidConfigStateService { - _serviceBrand: undefined; - - private readonly _onDidChangeState = new Emitter(); - readonly onDidChangeState: Event = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes - - state: ConfigState; - readonly voidConfigInfo: VoidConfigInfo = voidProviderOptions; // just putting this here for simplicity, it's static though - - constructor( - @IStorageService private readonly _storageService: IStorageService, - @IEncryptionService private readonly _encryptionService: IEncryptionService, - // could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER) - // @ISecretStorageService private readonly _secretStorageService: ISecretStorageService, - ) { - super() - - // at the start, we haven't read the partial config yet, but we need to set state to something, just treat partialVoidConfig like it's empty - this.state = { - partialVoidConfig: {}, - voidConfig: getVoidConfig({}), - } - - // read and update the actual state immediately - this._readPartialVoidConfig().then(partialVoidConfig => { - this._setState(partialVoidConfig) - }) - - } - - private async _readPartialVoidConfig(): Promise { - const encryptedPartialConfig = this._storageService.get(VOID_CONFIG_KEY, StorageScope.APPLICATION) - - if (!encryptedPartialConfig) - return {} - - const partialVoidConfigStr = await this._encryptionService.decrypt(encryptedPartialConfig) - return JSON.parse(partialVoidConfigStr) - } - - - private async _storePartialVoidConfig(partialVoidConfig: PartialVoidConfig) { - const encryptedPartialConfigStr = await this._encryptionService.encrypt(JSON.stringify(partialVoidConfig)) - this._storageService.store(VOID_CONFIG_KEY, encryptedPartialConfigStr, StorageScope.APPLICATION, StorageTarget.USER) - } - - - // Set field on PartialVoidConfig - setField: SetFieldFnType = async (field: K, param: keyof VoidConfigInfo[K], newVal: string) => { - const { partialVoidConfig } = this.state - - const newPartialConfig: PartialVoidConfig = { - ...partialVoidConfig, - [field]: { - ...partialVoidConfig[field], - [param]: newVal - } - } - await this._storePartialVoidConfig(newPartialConfig) - this._setState(newPartialConfig) - } - - // internal function to update state, should be called every time state changes - private async _setState(partialVoidConfig: PartialVoidConfig) { - this.state = { - partialVoidConfig: partialVoidConfig, - voidConfig: getVoidConfig(partialVoidConfig), - } - this._onDidChangeState.fire() - } - -} - -registerSingleton(IVoidConfigStateService, VoidConfigStateService, InstantiationType.Eager); diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/platform/void/common/llmMessageTypes.ts index 651fef0c..d789acc6 100644 --- a/src/vs/platform/void/common/llmMessageTypes.ts +++ b/src/vs/platform/void/common/llmMessageTypes.ts @@ -3,7 +3,7 @@ * Void Editor additions licensed under the AGPLv3 License. *--------------------------------------------------------------------------------------------*/ -import { VoidConfig } from './configTypes.js' +import { ProviderName, VoidConfigState } from './configTypes' // ---------- type definitions ---------- @@ -27,11 +27,13 @@ export type LLMMessageServiceParams = { onError: OnError; messages: LLMMessage[]; - voidConfig: VoidConfig | null; + voidConfig: VoidConfigState | null; logging: { loggingName: string, }; + providerName: ProviderName; + } export type SendLLMMMessageParams = { @@ -40,11 +42,12 @@ export type SendLLMMMessageParams = { onError: OnError; messages: LLMMessage[]; - voidConfig: VoidConfig | null; + voidConfig: VoidConfigState | null; logging: { loggingName: string, }; + providerName: ProviderName; abortRef: AbortRef; } diff --git a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts index 50990537..c79413d6 100644 --- a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts +++ b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts @@ -10,7 +10,7 @@ export const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onTex const thisConfig = voidConfig.anthropic - const anthropic = new Anthropic({ apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"] + const anthropic = new Anthropic({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }); // find system messages and concatenate them const systemMessage = messages @@ -25,7 +25,7 @@ export const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onTex system: systemMessage, messages: anthropicMessages, model: thisConfig.model, - max_tokens: parseMaxTokensStr(voidConfig.default.maxTokens)!, // this might be undefined, but it will just throw an error for the user + max_tokens: parseMaxTokensStr(thisConfig.maxTokens)!, // this might be undefined, but it will just throw an error for the user to see }); diff --git a/src/vs/platform/void/electron-main/llmMessage/gemini.ts b/src/vs/platform/void/electron-main/llmMessage/gemini.ts index f13cf9f8..dfd37e94 100644 --- a/src/vs/platform/void/electron-main/llmMessage/gemini.ts +++ b/src/vs/platform/void/electron-main/llmMessage/gemini.ts @@ -8,7 +8,7 @@ export const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, on const thisConfig = voidConfig.gemini - const genAI = new GoogleGenerativeAI(thisConfig.apikey); + const genAI = new GoogleGenerativeAI(thisConfig.apiKey); const model = genAI.getGenerativeModel({ model: thisConfig.model }); // remove system messages that get sent to Gemini diff --git a/src/vs/platform/void/electron-main/llmMessage/greptile.ts b/src/vs/platform/void/electron-main/llmMessage/greptile.ts index 4ee108d6..e0c993ac 100644 --- a/src/vs/platform/void/electron-main/llmMessage/greptile.ts +++ b/src/vs/platform/void/electron-main/llmMessage/greptile.ts @@ -1,64 +1,64 @@ -// Greptile -// https://docs.greptile.com/api-reference/query -// https://docs.greptile.com/quickstart#sample-response-streamed +// // Greptile +// // https://docs.greptile.com/api-reference/query +// // https://docs.greptile.com/quickstart#sample-response-streamed -import { SendLLMMessageFnTypeInternal } from './util'; +// import { SendLLMMessageFnTypeInternal } from './util'; -export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => { +// export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => { - let fullText = '' +// let fullText = '' - const thisConfig = voidConfig.greptile +// const thisConfig = voidConfig.greptile - fetch('https://api.greptile.com/v2/query', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${thisConfig.apikey}`, - 'X-Github-Token': `${thisConfig.githubPAT}`, - 'Content-Type': `application/json`, - }, - body: JSON.stringify({ - messages, - stream: true, - repositories: [thisConfig.repoinfo], - }), - }) - // this is {message}\n{message}\n{message}...\n - .then(async response => { - const text = await response.text() - console.log('got greptile', text) - return JSON.parse(`[${text.trim().split('\n').join(',')}]`) - }) - // TODO make this actually stream, right now it just sends one message at the end - // TODO add _setAborter() when add streaming - .then(async responseArr => { +// fetch('https://api.greptile.com/v2/query', { +// method: 'POST', +// headers: { +// 'Authorization': `Bearer ${thisConfig.apikey}`, +// 'X-Github-Token': `${thisConfig.githubPAT}`, +// 'Content-Type': `application/json`, +// }, +// body: JSON.stringify({ +// messages, +// stream: true, +// repositories: [thisConfig.repoinfo], +// }), +// }) +// // this is {message}\n{message}\n{message}...\n +// .then(async response => { +// const text = await response.text() +// console.log('got greptile', text) +// return JSON.parse(`[${text.trim().split('\n').join(',')}]`) +// }) +// // TODO make this actually stream, right now it just sends one message at the end +// // TODO add _setAborter() when add streaming +// .then(async responseArr => { - for (const response of responseArr) { - const type: string = response['type'] - const message = response['message'] +// for (const response of responseArr) { +// const type: string = response['type'] +// const message = response['message'] - // when receive text - if (type === 'message') { - fullText += message - onText({ newText: message, fullText }) - } - else if (type === 'sources') { - const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null } - fullText += filepath - onText({ newText: filepath, fullText }) - } - // type: 'status' with an empty 'message' means last message - else if (type === 'status') { - if (!message) { - onFinalMessage({ fullText }) - } - } - } +// // when receive text +// if (type === 'message') { +// fullText += message +// onText({ newText: message, fullText }) +// } +// else if (type === 'sources') { +// const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null } +// fullText += filepath +// onText({ newText: filepath, fullText }) +// } +// // type: 'status' with an empty 'message' means last message +// else if (type === 'status') { +// if (!message) { +// onFinalMessage({ fullText }) +// } +// } +// } - }) - .catch(error => { - onError({ error }) - }); +// }) +// .catch(error => { +// onError({ error }) +// }); -} +// } diff --git a/src/vs/platform/void/electron-main/llmMessage/groq.ts b/src/vs/platform/void/electron-main/llmMessage/groq.ts index 9b2b343a..6506c4ab 100644 --- a/src/vs/platform/void/electron-main/llmMessage/groq.ts +++ b/src/vs/platform/void/electron-main/llmMessage/groq.ts @@ -6,18 +6,20 @@ import { parseMaxTokensStr } from './util.js'; export const sendGroqMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => { let fullText = ''; + const thisConfig = voidConfig.groq + const groq = new Groq({ - apiKey: voidConfig.groq.apikey, + apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }); await groq.chat.completions .create({ messages: messages, - model: voidConfig.groq.model, + model: thisConfig.model, stream: true, temperature: 0.7, - max_tokens: parseMaxTokensStr(voidConfig.default.maxTokens), + max_tokens: parseMaxTokensStr(thisConfig.maxTokens), }) .then(async response => { _setAborter(() => response.controller.abort()) diff --git a/src/vs/platform/void/electron-main/llmMessage/ollama.ts b/src/vs/platform/void/electron-main/llmMessage/ollama.ts index 7b41da01..92754949 100644 --- a/src/vs/platform/void/electron-main/llmMessage/ollama.ts +++ b/src/vs/platform/void/electron-main/llmMessage/ollama.ts @@ -15,7 +15,7 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, model: thisConfig.model, messages: messages, stream: true, - options: { num_predict: parseMaxTokensStr(voidConfig.default.maxTokens) } // this is max_tokens + options: { num_predict: parseMaxTokensStr(thisConfig.maxTokens) } // this is max_tokens }) .then(async stream => { _setAborter(() => stream.abort()) diff --git a/src/vs/platform/void/electron-main/llmMessage/openai.ts b/src/vs/platform/void/electron-main/llmMessage/openai.ts index 7d96792e..7f59e5ac 100644 --- a/src/vs/platform/void/electron-main/llmMessage/openai.ts +++ b/src/vs/platform/void/electron-main/llmMessage/openai.ts @@ -4,39 +4,38 @@ import { parseMaxTokensStr } from './util.js'; // OpenAI, OpenRouter, OpenAICompatible -export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter }) => { +export const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }) => { let fullText = '' let openai: OpenAI let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming - const maxTokens = parseMaxTokensStr(voidConfig.default.maxTokens) - if (voidConfig.default.whichApi === 'openAI') { + if (providerName === 'openAI') { const thisConfig = voidConfig.openAI - openai = new OpenAI({ apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true }); - options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: maxTokens } + openai = new OpenAI({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }); + options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) } } - else if (voidConfig.default.whichApi === 'openRouter') { + else if (providerName === 'openRouter') { const thisConfig = voidConfig.openRouter openai = new OpenAI({ - baseURL: 'https://openrouter.ai/api/v1', apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true, + baseURL: 'https://openrouter.ai/api/v1', apiKey: thisConfig.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: thisConfig.model, messages: messages, stream: true, max_completion_tokens: maxTokens } + options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) } } - else if (voidConfig.default.whichApi === 'openAICompatible') { + else if (providerName === 'openAICompatible') { const thisConfig = voidConfig.openAICompatible - openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apikey, dangerouslyAllowBrowser: true }) - options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: maxTokens } + openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }) + options = { model: thisConfig.model, messages: messages, stream: true, max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens) } } else { - console.error(`sendOpenAIMsg: invalid whichApi: ${voidConfig.default.whichApi}`) - throw new Error(`voidConfig.whichAPI was invalid: ${voidConfig.default.whichApi}`) + console.error(`sendOpenAIMsg: invalid providerName: ${providerName}`) + throw new Error(`providerName was invalid: ${providerName}`) } openai.chat.completions diff --git a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts index 51fedd7e..e6daf1cb 100644 --- a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts @@ -2,18 +2,21 @@ import { SendLLMMMessageParams, OnText, OnFinalMessage, OnError } from '../../co import { IMetricsService } from '../../common/metricsService.js'; import { sendAnthropicMsg } from './anthropic.js'; -import { sendGeminiMsg } from './gemini.js'; -import { sendGreptileMsg } from './greptile.js'; -import { sendGroqMsg } from './groq.js'; import { sendOllamaMsg } from './ollama.js'; import { sendOpenAIMsg } from './openai.js'; +import { sendGeminiMsg } from './gemini.js'; +import { sendGroqMsg } from './groq.js'; - -export const sendLLMMessage = ( - { - messages, onText: onText_, onFinalMessage: onFinalMessage_, onError: onError_, - abortRef: abortRef_, voidConfig, logging: { loggingName }, - }: SendLLMMMessageParams, +export const sendLLMMessage = ({ + messages, + onText: onText_, + onFinalMessage: onFinalMessage_, + onError: onError_, + abortRef: abortRef_, + voidConfig, + logging: { loggingName }, + providerName +}: SendLLMMMessageParams, metricsService: IMetricsService ) => { @@ -25,7 +28,7 @@ export const sendLLMMessage = ( // only captures number of messages and message "shape", no actual code, instructions, prompts, etc const captureChatEvent = (eventId: string, extras?: object) => { metricsService.capture(eventId, { - whichApi: voidConfig.default['whichApi'], + providerName, numMessages: messages?.length, messagesShape: messages?.map(msg => ({ role: msg.role, length: msg.content.length })), version: '2024-11-14', @@ -69,29 +72,29 @@ export const sendLLMMessage = ( captureChatEvent(`${loggingName} - Sending Message`, { messageLength: messages[messages.length - 1]?.content.length }) try { - switch (voidConfig.default.whichApi) { + switch (providerName) { case 'anthropic': - sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }); break; case 'openAI': case 'openRouter': case 'openAICompatible': - sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }); break; case 'gemini': - sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }); break; case 'ollama': - sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); - break; - case 'greptile': - sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }); break; + // case 'greptile': + // sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }); + // break; case 'groq': - sendGroqMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, }); + sendGroqMsg({ messages, onText, onFinalMessage, onError, voidConfig, _setAborter, providerName }); break; default: - onError({ error: `Error: whichApi was "${voidConfig.default.whichApi}", which is not recognized!` }) + onError({ error: `Error: whichApi was "${providerName}", which is not recognized!` }) break; } } diff --git a/src/vs/platform/void/electron-main/llmMessage/util.ts b/src/vs/platform/void/electron-main/llmMessage/util.ts index 92c71156..82f621b2 100644 --- a/src/vs/platform/void/electron-main/llmMessage/util.ts +++ b/src/vs/platform/void/electron-main/llmMessage/util.ts @@ -1,4 +1,4 @@ -import { VoidConfig } from '../../common/configTypes' +import { ProviderName, VoidConfigState } from '../../common/configTypes' import { LLMMessage, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes' export const parseMaxTokensStr = (maxTokensStr: string) => { @@ -15,7 +15,8 @@ export type SendLLMMessageFnTypeInternal = (params: { onText: OnText; onFinalMessage: OnFinalMessage; onError: OnError; - voidConfig: VoidConfig; + voidConfig: VoidConfigState; + providerName: ProviderName; _setAborter: (aborter: () => void) => void; }) => void diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx similarity index 94% rename from src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx rename to src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 428fa5b5..4b0533cf 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/MarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -44,7 +44,7 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => { className="btn btn-secondary btn-sm border border-vscode-input-border rounded" onClick={async () => { - inlineDiffService.startStreaming('ctrl+l', text) + inlineDiffService.startStreaming({ type: 'ctrl+l', providerName: 'anthropic' }, text) }} > Apply @@ -125,7 +125,7 @@ const RenderToken = ({ token, nested = false }: { token: Token | string, nested? {item.task && ( )} - + ))} @@ -209,7 +209,7 @@ const RenderToken = ({ token, nested = false }: { token: Token | string, nested? ) } -export const MarkdownRender = ({ string, nested = false }: { string: string, nested?: boolean }) => { +export const ChatMarkdownRender = ({ string, nested = false }: { string: string, nested?: boolean }) => { const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer return ( <> diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx index 7f2dcb42..04fdb3b6 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/Sidebar.tsx @@ -5,7 +5,9 @@ import React, { useEffect, useState } from 'react' import { mountFnGenerator } from '../util/mountFnGenerator.js' -import { SidebarSettings } from './SidebarSettings.js'; +// import { SidebarSettings } from './SidebarSettings.js'; + + import { useSidebarState } from '../util/services.js'; // import { SidebarThreadSelector } from './SidebarThreadSelector.js'; // import { SidebarChat } from './SidebarChat.js'; @@ -13,6 +15,8 @@ import { useSidebarState } from '../util/services.js'; import '../styles.css' import { SidebarThreadSelector } from './SidebarThreadSelector.js'; import { SidebarChat } from './SidebarChat.js'; +import { SidebarModelSettings } from './SidebarModelSettings.js'; +import { SidebarProviderSettings } from './SidebarProviderSettings.js'; const Sidebar = () => { const sidebarState = useSidebarState() @@ -37,7 +41,8 @@ const Sidebar = () => {
- + {false && } +
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index b9511ac7..9e3e3155 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -11,7 +11,7 @@ import { userInstructionsStr } from '../../../prompt/stringifyFiles.js'; import { ChatMessage, CodeSelection, CodeStagingSelection } from '../../../registerThreads.js'; import { BlockCode } from '../markdown/BlockCode.js'; -import { MarkdownRender } from '../markdown/MarkdownRender.js'; +import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'; import { IModelService } from '../../../../../../../editor/common/services/model.js'; import { URI } from '../../../../../../../base/common/uri.js'; import { EndOfLinePreference } from '../../../../../../../editor/common/model.js'; @@ -110,7 +110,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { } else if (role === 'assistant') { - chatbubbleContents = // sectionsHTML + chatbubbleContents = // sectionsHTML } return
@@ -141,8 +141,8 @@ export const SidebarChat = () => { }, [sidebarStateService, chatInputRef]) // config state - const configState = useConfigState() - const { voidConfig } = configState + const voidConfigState = useConfigState() + // threads state const threadsState = useThreadsState() @@ -220,7 +220,8 @@ export const SidebarChat = () => { setLatestError(error) }, - voidConfig, + voidConfig: voidConfigState, + providerName: 'anthropic', } const latestRequestId = sendLLMMessageService.sendLLMMessage(object) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarModelSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarModelSettings.tsx new file mode 100644 index 00000000..660e6911 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarModelSettings.tsx @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPLv3 License. + *--------------------------------------------------------------------------------------------*/ + +import { FeatureName, featureNames, providerNames } from '../../../../../../../platform/void/common/configTypes.js' +import { useConfigState } from '../util/services.js' + + + + +export const SidebarModelSettingsForFeature = ({ featureName }: { featureName: FeatureName }) => { + + const voidConfigState = useConfigState() + + const models: [string,string][] = [] + for (const providerName of providerNames) { + const providerConfig = voidConfigState[providerName] + if (!providerConfig.enabled) continue + providerConfig.models.forEach(model => { + models.push([providerName,model]) + }) + } + + return <> +

Settings - {featureName}

+ {models.map(([providerName,model], i) => {providerName} - {model})} + +} + +export const SidebarModelSettings = () => { + return <> + {featureNames.map(featureName => )} + + + +} diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx new file mode 100644 index 00000000..6b93809f --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarProviderSettings.tsx @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Void Editor additions licensed under the AGPLv3 License. + *--------------------------------------------------------------------------------------------*/ + +import React, { Fragment } from 'react' +import { descOfSettingName, ProviderName, providerNames, voidProviderDefaults } from '../../../../../../../platform/void/common/configTypes.js' +import { VoidInputBox, VoidSelectBox } from './inputs.js' +import { useConfigState, useService } from '../util/services.js' + +const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => { + const voidConfigState = useConfigState() + const voidConfigService = useService('configStateService') + console.log('CONFIG', voidConfigState) + console.log('provider:', providerName, voidConfigState[providerName]) + const { models, model, ...others } = voidConfigState[providerName] + + return <> +

{providerName}

+ + {/* other settings (e.g. api key) */} + {Object.entries(others).map(([settingName, defaultVal], i) => { + console.log('--- entry:', providerName, settingName, defaultVal) + const sName = settingName as keyof typeof others + + return +

{descOfSettingName(providerName, sName)}

+ { () => { voidConfigService.setState(providerName, sName, newVal) } }} + placeholder={settingName} + multiline={false} + inputBoxRef={{ current: null }} + /> +
+ })} + +

{'Models'}

+ { () => { } }} + selectBoxRef={{ current: null }} + /> + +

{'Enabled'}

+ todo + + +} + + +export const SidebarProviderSettings = () => { + + return <> + {providerNames.map(providerName => + + )} + + + +} diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx index 565a9db5..d5b23aeb 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarSettings.tsx @@ -1,150 +1,150 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Glass Devtools, Inc. All rights reserved. - * Void Editor additions licensed under the AGPLv3 License. - *--------------------------------------------------------------------------------------------*/ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { useConfigState, useService } from '../util/services.js'; +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Glass Devtools, Inc. All rights reserved. +// * Void Editor additions licensed under the AGPLv3 License. +// *--------------------------------------------------------------------------------------------*/ +// import React, { useCallback, useEffect, useRef, useState } from 'react'; +// import { useConfigState, useService } from '../util/services.js'; -import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; -import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'; -import { ConfigState, IVoidConfigStateService } from '../../../registerConfig.js'; -import { nonDefaultConfigFields, VoidConfigField } from '../../../../../../../platform/void/common/configTypes.js'; +// import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; +// import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'; +// import { ConfigState, IVoidConfigStateService } from '../../../registerConfig.js'; +// import { nonDefaultConfigFields, VoidConfigField } from '../../../../../../../platform/void/common/configTypes.js'; -const SettingOfFieldAndParam = ({ field, param, configState, configStateService }: - { field: VoidConfigField; param: string; configState: ConfigState; configStateService: IVoidConfigStateService }) => { +// const SettingOfFieldAndParam = ({ field, param, configState, configStateService }: +// { field: VoidConfigField; param: string; configState: ConfigState; configStateService: IVoidConfigStateService }) => { - const { partialVoidConfig } = configState +// const { partialVoidConfig } = configState - const { enumArr, defaultVal, description } = configStateService.voidConfigInfo[field][param] - const val = partialVoidConfig[field]?.[param] ?? defaultVal // current value of this item - const initValRef = useRef(val) +// const { enumArr, defaultVal, description } = configStateService.voidConfigInfo[field][param] +// const val = partialVoidConfig[field]?.[param] ?? defaultVal // current value of this item +// const initValRef = useRef(val) - const updateState = useCallback((newValue: string) => { - configStateService.setField(field, param, newValue) - }, [configStateService, field, param]) +// const updateState = useCallback((newValue: string) => { +// configStateService.setField(field, param, newValue) +// }, [configStateService, field, param]) - const inputBoxRef = useRef(null); - const selectBoxRef = useRef(null); - const forceState = useCallback((newValue: string) => { - if (inputBoxRef.current) { - inputBoxRef.current.value = newValue; - } - if (selectBoxRef.current) { - selectBoxRef.current.select(enumArr?.indexOf(newValue) ?? 0); - } - // updateState is called automatically when the change happens - }, [enumArr, updateState]) +// const inputBoxRef = useRef(null); +// const selectBoxRef = useRef(null); +// const forceState = useCallback((newValue: string) => { +// if (inputBoxRef.current) { +// inputBoxRef.current.value = newValue; +// } +// if (selectBoxRef.current) { +// selectBoxRef.current.select(enumArr?.indexOf(newValue) ?? 0); +// } +// // updateState is called automatically when the change happens +// }, [enumArr, updateState]) - const resetButton = +// const resetButton = - const inputElement = enumArr === undefined ? - // string - // () - updateState(e.target.value)} - /> - : - // enum - // () - () +// const inputElement = enumArr === undefined ? +// // string +// // () +// updateState(e.target.value)} +// /> +// : +// // enum +// // () +// () - return
- - {description} -
- {inputElement} - {resetButton} -
-
-} +// return
+// +// {description} +//
+// {inputElement} +// {resetButton} +//
+//
+// } -export const SidebarSettings = () => { +// export const SidebarSettings = () => { - const configState = useConfigState() - const configStateService = useService('configStateService') +// const configState = useConfigState() +// const configStateService = useService('configStateService') - const { voidConfig } = configState - const current_field = voidConfig.default['whichApi'] as VoidConfigField +// const { voidConfig } = configState +// const current_field = voidConfig.default['whichApi'] as VoidConfigField - return ( -
+// return ( +//
- {/* choose the field */} -
- - -
+// {/* choose the field */} +//
+// +// +//
-
+//
- {/* render all fields, but hide the ones not visible for fast tab switching */} - {nonDefaultConfigFields.map(field => { - return
- {Object.keys(configStateService.voidConfigInfo[field]).map((param) => ( - - ))} -
- })} -
- ) -} +// {/* render all fields, but hide the ones not visible for fast tab switching */} +// {nonDefaultConfigFields.map(field => { +// return
+// {Object.keys(configStateService.voidConfigInfo[field]).map((param) => ( +// +// ))} +//
+// })} +//
+// ) +// } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx index ec952993..8629d343 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/inputs.tsx @@ -61,44 +61,44 @@ export const VoidInputBox = ({ onChangeText, initVal, placeholder, inputBoxRef, -// export const VoidSelectBox = ({ onChangeSelection, initVal, selectBoxRef, options }: { -// onChangeSelection: (value: string) => void; -// initVal: string; -// selectBoxRef: React.MutableRefObject; -// options: readonly string[]; +export const VoidSelectBox = ({ onChangeSelection, initVal, selectBoxRef, options }: { + onChangeSelection: (value: string) => void; + initVal: string; + selectBoxRef: React.MutableRefObject; + options: readonly string[]; -// }) => { -// const containerRef = useRef(null); -// const contextViewProvider = useService('contextViewService'); +}) => { + const containerRef = useRef(null); + const contextViewProvider = useService('contextViewService'); -// useEffect(() => { -// if (!containerRef.current) return; + useEffect(() => { + if (!containerRef.current) return; -// const defaultIndex = options.indexOf(initVal); + const defaultIndex = options.indexOf(initVal); -// selectBoxRef.current = new SelectBox( -// options.map(opt => ({ text: opt })), -// defaultIndex, -// contextViewProvider, -// unthemedSelectBoxStyles -// ); + selectBoxRef.current = new SelectBox( + options.map(opt => ({ text: opt })), + defaultIndex, + contextViewProvider, + unthemedSelectBoxStyles + ); -// selectBoxRef.current.render(containerRef.current); + selectBoxRef.current.render(containerRef.current); -// selectBoxRef.current.onDidSelect(e => { onChangeSelection(e.selected); }); + selectBoxRef.current.onDidSelect(e => { onChangeSelection(e.selected); }); -// // cleanup -// return () => { -// if (selectBoxRef.current) { -// selectBoxRef.current.dispose(); -// if (containerRef.current) { -// while (containerRef.current.firstChild) { -// containerRef.current.removeChild(containerRef.current.firstChild); -// } -// } -// } -// }; -// }, [options, initVal, onChangeSelection, contextViewProvider, selectBoxRef]); + // cleanup + return () => { + if (selectBoxRef.current) { + selectBoxRef.current.dispose(); + if (containerRef.current) { + while (containerRef.current.firstChild) { + containerRef.current.removeChild(containerRef.current.firstChild); + } + } + } + }; + }, [options, initVal, onChangeSelection, contextViewProvider, selectBoxRef]); -// return
; -// }; + return
; +}; diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index 9cceb8d7..6eba25af 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' -import { ConfigState } from '../../../registerConfig.js' import { VoidSidebarState, ReactServicesType } from '../../../registerSidebar.js' import { ThreadsState } from '../../../registerThreads.js' +import { VoidConfigState } from '../../../../../../../platform/void/common/configTypes.js' // normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes @@ -10,12 +10,12 @@ let services: ReactServicesType // even if React hasn't mounted yet, these variables are always updated to the latest state: let sidebarState: VoidSidebarState -let configState: ConfigState +let configState: VoidConfigState let threadsState: ThreadsState // React listens by adding a setState function to these: const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set() -const configStateListeners: Set<(s: ConfigState) => void> = new Set() +const configStateListeners: Set<(s: VoidConfigState) => void> = new Set() const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set() // must call this before you can use any of the hooks below diff --git a/src/vs/workbench/contrib/void/browser/registerConfig.ts b/src/vs/workbench/contrib/void/browser/registerConfig.ts index 74826458..e73a1514 100644 --- a/src/vs/workbench/contrib/void/browser/registerConfig.ts +++ b/src/vs/workbench/contrib/void/browser/registerConfig.ts @@ -5,40 +5,29 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; +import { deepClone } from '../../../../base/common/objects.js'; import { IEncryptionService } from '../../../../platform/encryption/common/encryptionService.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; -import { PartialVoidConfig, VoidConfig, nonDefaultConfigFields, voidConfigInfo, VoidConfigField, VoidConfigInfo } from '../../../../platform/void/common/configTypes.js'; +import { defaultVoidConfigState, ProviderName, VoidConfigState } from '../../../../platform/void/common/configTypes.js'; -const getVoidConfig = (partialVoidConfig: PartialVoidConfig): VoidConfig => { - const config = {} as PartialVoidConfig - for (const field of [...nonDefaultConfigFields, 'default'] as const) { - config[field] = {} - for (const prop in voidConfigInfo[field]) { - config[field][prop] = partialVoidConfig[field]?.[prop]?.trim() || voidConfigInfo[field][prop].defaultVal - } - } - return config as VoidConfig -} - const VOID_CONFIG_KEY = 'void.partialVoidConfig' -export type SetFieldFnType = (field: K, param: keyof VoidConfigInfo[K], newVal: string) => Promise; +type SetStateFn = ( + providerName: K, + option: keyof VoidConfigState[K], + newVal: string +) => Promise; -export type ConfigState = { - partialVoidConfig: PartialVoidConfig; // free parameter - voidConfig: VoidConfig; // computed from partialVoidConfig -} export interface IVoidConfigStateService { readonly _serviceBrand: undefined; - readonly state: ConfigState; - readonly voidConfigInfo: VoidConfigInfo; + readonly state: VoidConfigState; onDidChangeState: Event; - setField: SetFieldFnType; + setState: SetStateFn; } export const IVoidConfigStateService = createDecorator('VoidConfigStateService'); @@ -48,8 +37,9 @@ class VoidConfigStateService extends Disposable implements IVoidConfigStateServi private readonly _onDidChangeState = new Emitter(); readonly onDidChangeState: Event = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes - state: ConfigState; - readonly voidConfigInfo: VoidConfigInfo = voidConfigInfo; // just putting this here for simplicity, it's static though + state: VoidConfigState; + + // readonly voidConfigInfo: VoidConfigInfo = voidConfigInfo; // just putting this here for simplicity, it's static though constructor( @IStorageService private readonly _storageService: IStorageService, @@ -60,56 +50,48 @@ class VoidConfigStateService extends Disposable implements IVoidConfigStateServi super() // at the start, we haven't read the partial config yet, but we need to set state to something, just treat partialVoidConfig like it's empty - this.state = { - partialVoidConfig: {}, - voidConfig: getVoidConfig({}), - } + this.state = deepClone(defaultVoidConfigState) // read and update the actual state immediately - this._readPartialVoidConfig().then(partialVoidConfig => { - this._setState(partialVoidConfig) + this._readVoidConfigState().then(voidConfigState => { + this._setState(voidConfigState) }) } - private async _readPartialVoidConfig(): Promise { + private async _readVoidConfigState(): Promise { const encryptedPartialConfig = this._storageService.get(VOID_CONFIG_KEY, StorageScope.APPLICATION) if (!encryptedPartialConfig) - return {} + return deepClone(defaultVoidConfigState) - const partialVoidConfigStr = await this._encryptionService.decrypt(encryptedPartialConfig) - return JSON.parse(partialVoidConfigStr) + const voidConfigStateStr = await this._encryptionService.decrypt(encryptedPartialConfig) + return JSON.parse(voidConfigStateStr) } - private async _storePartialVoidConfig(partialVoidConfig: PartialVoidConfig) { - const encryptedPartialConfigStr = await this._encryptionService.encrypt(JSON.stringify(partialVoidConfig)) - this._storageService.store(VOID_CONFIG_KEY, encryptedPartialConfigStr, StorageScope.APPLICATION, StorageTarget.USER) + private async _storeVoidConfigState(voidConfigState: VoidConfigState) { + const encryptedVoidConfigStr = await this._encryptionService.encrypt(JSON.stringify(voidConfigState)) + this._storageService.store(VOID_CONFIG_KEY, encryptedVoidConfigStr, StorageScope.APPLICATION, StorageTarget.USER) } // Set field on PartialVoidConfig - setField: SetFieldFnType = async (field: K, param: keyof VoidConfigInfo[K], newVal: string) => { - const { partialVoidConfig } = this.state - - const newPartialConfig: PartialVoidConfig = { - ...partialVoidConfig, - [field]: { - ...partialVoidConfig[field], - [param]: newVal + setState: SetStateFn = async (providerName, option, newVal) => { + const newState: VoidConfigState = { + ...this.state, + [providerName]: { + ...this.state[providerName], + [option]: newVal, } } - await this._storePartialVoidConfig(newPartialConfig) - this._setState(newPartialConfig) + await this._storeVoidConfigState(newState) + this._setState(newState) } // internal function to update state, should be called every time state changes - private async _setState(partialVoidConfig: PartialVoidConfig) { - this.state = { - partialVoidConfig: partialVoidConfig, - voidConfig: getVoidConfig(partialVoidConfig), - } + private async _setState(voidConfigState: VoidConfigState) { + this.state = voidConfigState this._onDidChangeState.fire() } diff --git a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts index dba595c6..2d7177c8 100644 --- a/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts +++ b/src/vs/workbench/contrib/void/browser/registerInlineDiffs.ts @@ -30,6 +30,7 @@ import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { LLMMessageServiceParams } from '../../../../platform/void/common/llmMessageTypes.js'; import { ISendLLMMessageService } from '../../../../platform/void/browser/llmMessageService.js'; +import { ProviderName } from '../../../../platform/void/common/configTypes.js'; // gets converted to --vscode-void-greenBG, see void.css @@ -110,10 +111,18 @@ type HistorySnapshot = { }) +type StartStreamingOptions = { + type: 'ctrl+k', + providerName: ProviderName, + range: IRange +} | { + type: 'ctrl+l', + providerName: ProviderName +} + export interface IInlineDiffsService { readonly _serviceBrand: undefined; - startStreaming(type: 'ctrl+k' | 'ctrl+l', userMessage: string): void; - + startStreaming(params: StartStreamingOptions, str: string): void; } export const IInlineDiffsService = createDecorator('inlineDiffAreasService'); @@ -637,7 +646,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { - private async _initializeStream(uri: URI, diffRepr: string) { + private async _initializeStream(uri: URI, diffRepr: string, providerName: ProviderName) { // diff area begin and end line const numLines = this._getNumLines(uri) @@ -689,7 +698,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this.diffAreaOfId[diffArea.diffareaid] = diffArea // actually call the LLM - const { voidConfig } = this._voidConfigStateService.state + const voidConfigState = this._voidConfigStateService.state const promptContent = `\ ORIGINAL_CODE \`\`\` @@ -761,7 +770,8 @@ Please finish writing the new file by applying the diff to the original file. Re diffArea._sweepState = { isStreaming: false, line: null } resolve(); }, - voidConfig, + voidConfig: voidConfigState, + providerName, } streamRequestId = this._sendLLMMessageService.sendLLMMessage(object) @@ -776,7 +786,7 @@ Please finish writing the new file by applying the diff to the original file. Re - async startStreaming(type: 'ctrl+k' | 'ctrl+l', userMessage: string) { + async startStreaming(params: StartStreamingOptions, userMessage: string) { const editor = this._editorService.getActiveCodeEditor() if (!editor) return @@ -788,7 +798,7 @@ Please finish writing the new file by applying the diff to the original file. Re // TODO deselect user's cursor - this._initializeStream(uri, userMessage) + this._initializeStream(uri, userMessage, params.providerName) } diff --git a/src/vs/workbench/contrib/void/browser/registerSidebar.ts b/src/vs/workbench/contrib/void/browser/registerSidebar.ts index 5b5c475a..be81879f 100644 --- a/src/vs/workbench/contrib/void/browser/registerSidebar.ts +++ b/src/vs/workbench/contrib/void/browser/registerSidebar.ts @@ -53,8 +53,7 @@ import { IClipboardService } from '../../../../platform/clipboard/common/clipboa // import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; -// compare against search.contribution.ts and https://app.greptile.com/chat/w1nsmt3lauwzculipycpn?repo=github%3Amain%3Amicrosoft%2Fvscode -// and debug.contribution.ts, scm.contribution.ts (source control) +// compare against search.contribution.ts and debug.contribution.ts, scm.contribution.ts (source control) export type VoidSidebarState = { isHistoryOpen: boolean; @@ -91,10 +90,6 @@ class VoidSidebarViewPane extends ViewPane { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, - // Void: - // @IVoidSidebarStateService private readonly _voidSidebarStateService: IVoidSidebarStateService, - // @IThreadHistoryService private readonly _threadHistoryService: IThreadHistoryService, - // TODO chat service ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService)