diff --git a/src/vs/platform/void/common/llmMessageService.ts b/src/vs/platform/void/common/llmMessageService.ts index 2096209a..4718e6c2 100644 --- a/src/vs/platform/void/common/llmMessageService.ts +++ b/src/vs/platform/void/common/llmMessageService.ts @@ -12,7 +12,7 @@ import { createDecorator } from '../../instantiation/common/instantiation.js'; import { Event } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import { IVoidSettingsService } from './voidSettingsService.js'; -import { getProvidersWithoutModels } from './voidSettingsTypes.js'; +import { displayInfoOfProviderName, isFeatureNameDisabled } from './voidSettingsTypes.js'; // import { INotificationService } from '../../notification/common/notification.js'; // calls channel to implement features @@ -91,19 +91,24 @@ export class LLMMessageService extends Disposable implements ILLMMessageService const { onText, onFinalMessage, onError, ...proxyParams } = params; const { useProviderFor: featureName } = proxyParams - // end early if no provider + // throw an error if no model/provider selected (this should usually never be reached, the UI should check this first, but might happen in cases like Apply where we haven't built much UI/checks yet, good practice to have check logic on backend) + const isDisabled = isFeatureNameDisabled(featureName, this.voidSettingsService.state) const modelSelection = this.voidSettingsService.state.modelSelectionOfFeature[featureName] + if (isDisabled || modelSelection === null) { + let message: string - // throw an error for providers without models - const providersWithoutModels = getProvidersWithoutModels(this.voidSettingsService.state.settingsOfProvider) - if (providersWithoutModels.length !== 0) { - onError({ message: `You haven't added any models for ${providersWithoutModels.join(', ')}.`, fullError: null }) - return null - } + if (isDisabled === 'addProvider' || isDisabled === 'providerNotAutoDetected') + message = `Please add a provider in Void Settings.` + else if (isDisabled === 'addModel') + message = `Please add a model.` + else if (isDisabled === 'needToEnableModel') + message = `Please enable a model.` + else if (isDisabled === 'notFilledIn') + message = `Please fill in Void Settings${modelSelection !== null ? ` for ${displayInfoOfProviderName(modelSelection.providerName).title}` : ''}.` + else + message = 'Please add a provider in Void Settings.' - // throw an error if no models - if (modelSelection === null) { - onError({ message: 'Please add a Provider in Settings!', fullError: null }) + onError({ message, fullError: null }) return null } diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/platform/void/common/llmMessageTypes.ts index cbc6856e..fb6a94fc 100644 --- a/src/vs/platform/void/common/llmMessageTypes.ts +++ b/src/vs/platform/void/common/llmMessageTypes.ts @@ -11,6 +11,7 @@ export const errorDetails = (fullError: Error | null): string | null => { return null } else if (typeof fullError === 'object') { + if (Object.keys(fullError).length === 0) return null return JSON.stringify(fullError, null, 2) } else if (typeof fullError === 'string') { @@ -41,10 +42,10 @@ type _InternalSendFIMMessage = { } type SendLLMType = { - type: 'sendChatMessage'; + messagesType: 'chatMessages'; messages: LLMChatMessage[]; } | { - type: 'sendFIMMessage'; + messagesType: 'FIMMessage'; messages: _InternalSendFIMMessage; } diff --git a/src/vs/platform/void/common/refreshModelService.ts b/src/vs/platform/void/common/refreshModelService.ts index 811db0db..7ef6a068 100644 --- a/src/vs/platform/void/common/refreshModelService.ts +++ b/src/vs/platform/void/common/refreshModelService.ts @@ -44,8 +44,8 @@ export type RefreshModelStateOfProvider = Record -type EventProp = T extends 'globalSettings' ? [T, keyof VoidSettingsState[T]] : T | 'all' +// type RealVoidSettings = Exclude +// type EventProp = T extends 'globalSettings' ? [T, keyof VoidSettingsState[T]] : T | 'all' export interface IVoidSettingsService { @@ -51,7 +51,7 @@ export interface IVoidSettingsService { readonly state: VoidSettingsState; // in order to play nicely with react, you should immutably change state readonly waitForInitState: Promise; - onDidChangeState: Event; + onDidChangeState: Event; setSettingOfProvider: SetSettingOfProviderFn; setModelSelectionOfFeature: SetModelSelectionOfFeatureFn; @@ -64,26 +64,76 @@ export interface IVoidSettingsService { } -let _computeModelOptions = (settingsOfProvider: SettingsOfProvider) => { - let modelOptions: ModelOption[] = [] + +const _updatedValidatedState = (state: Omit) => { + + let newSettingsOfProvider = state.settingsOfProvider + + // recompute _didFillInProviderSettings for (const providerName of providerNames) { - const providerConfig = settingsOfProvider[providerName] - if (!providerConfig._enabled) continue // if disabled, don't display model options - for (const { modelName, isHidden } of providerConfig.models) { - if (isHidden) continue - modelOptions.push({ name: `${modelName} (${providerName})`, selection: { providerName, modelName } }) + const settingsAtProvider = newSettingsOfProvider[providerName] + + const didFillInProviderSettings = Object.keys(defaultProviderSettings[providerName]).every(key => !!settingsAtProvider[key as keyof typeof settingsAtProvider]) + + if (didFillInProviderSettings === settingsAtProvider._didFillInProviderSettings) continue + + newSettingsOfProvider = { + ...newSettingsOfProvider, + [providerName]: { + ...settingsAtProvider, + _didFillInProviderSettings: didFillInProviderSettings, + }, } } - return modelOptions + + // update model options + let newModelOptions: ModelOption[] = [] + for (const providerName of providerNames) { + const providerTitle = displayInfoOfProviderName(providerName).title.toLowerCase() // looks better lowercase, best practice to not use raw providerName + if (!newSettingsOfProvider[providerName]._didFillInProviderSettings) continue // if disabled, don't display model options + for (const { modelName, isHidden } of newSettingsOfProvider[providerName].models) { + if (isHidden) continue + newModelOptions.push({ name: `${modelName} (${providerTitle})`, selection: { providerName, modelName } }) + } + } + + // now that model options are updated, make sure the selection is valid + // if the user-selected model is no longer in the list, update the selection for each feature that needs it to something relevant (the 0th model available, or null) + let newModelSelectionOfFeature = state.modelSelectionOfFeature + for (const featureName of featureNames) { + + const modelSelectionAtFeature = newModelSelectionOfFeature[featureName] + const selnIdx = modelSelectionAtFeature === null ? -1 : newModelOptions.findIndex(m => modelSelectionsEqual(m.selection, modelSelectionAtFeature)) + + if (selnIdx !== -1) continue + + newModelSelectionOfFeature = { + ...newModelSelectionOfFeature, + [featureName]: newModelOptions.length === 0 ? null : newModelOptions[0].selection + } + } + + + const newState = { + ...state, + settingsOfProvider: newSettingsOfProvider, + modelSelectionOfFeature: newModelSelectionOfFeature, + _modelOptions: newModelOptions, + } satisfies VoidSettingsState + + return newState } + + + const defaultState = () => { const d: VoidSettingsState = { settingsOfProvider: deepClone(defaultSettingsOfProvider), modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null, 'FastApply': null }, globalSettings: deepClone(defaultGlobalSettings), - _modelOptions: _computeModelOptions(defaultSettingsOfProvider), // computed + _modelOptions: [], // computed later } return d } @@ -93,8 +143,8 @@ export const IVoidSettingsService = createDecorator('VoidS class VoidSettingsService extends Disposable implements IVoidSettingsService { _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 + 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: VoidSettingsState; waitForInitState: Promise // await this if you need a valid state initially @@ -118,39 +168,47 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { this._readState().then(readS => { // the stored data structure might be outdated, so we need to update it here (can do a more general solution later when we need to) - readS = { - ...readS, - settingsOfProvider: { - // A HACK BECAUSE WE ADDED DEEPSEEK (did not exist before, comes before readS) - ...{ deepseek: defaultSettingsOfProvider.deepseek }, + const newSettingsOfProvider = { + // A HACK BECAUSE WE ADDED DEEPSEEK (did not exist before, comes before readS) + ...{ deepseek: defaultSettingsOfProvider.deepseek }, - // A HACK BECAUSE WE ADDED MISTRAL (did not exist before, comes before readS) - ...{ mistral: defaultSettingsOfProvider.mistral }, + // A HACK BECAUSE WE ADDED MISTRAL (did not exist before, comes before readS) + ...{ mistral: defaultSettingsOfProvider.mistral }, - ...readS.settingsOfProvider, + ...readS.settingsOfProvider, - // A HACK BECAUSE WE ADDED NEW GEMINI MODELS (existed before, comes after readS) - gemini: { - ...readS.settingsOfProvider.gemini, - models: [ - ...readS.settingsOfProvider.gemini.models, - ...defaultSettingsOfProvider.gemini.models.filter(m => /* if cant find the model in readS (yes this is O(n^2), very small) */ !readS.settingsOfProvider.gemini.models.find(m2 => m2.modelName === m.modelName)) - ] - } - }, - modelSelectionOfFeature: { - // A HACK BECAUSE WE ADDED FastApply - ...{ 'FastApply': null }, - ...readS.modelSelectionOfFeature, + // A HACK BECAUSE WE ADDED NEW GEMINI MODELS (existed before, comes after readS) + gemini: { + ...readS.settingsOfProvider.gemini, + models: [ + ...readS.settingsOfProvider.gemini.models, + ...defaultSettingsOfProvider.gemini.models.filter(m => /* if cant find the model in readS (yes this is O(n^2), very small) */ !readS.settingsOfProvider.gemini.models.find(m2 => m2.modelName === m.modelName)) + ] } } - this.state = readS + const newModelSelectionOfFeature = { + // A HACK BECAUSE WE ADDED FastApply + ...{ 'FastApply': null }, + ...readS.modelSelectionOfFeature, + } + + readS = { + ...readS, + settingsOfProvider: newSettingsOfProvider, + modelSelectionOfFeature: newModelSelectionOfFeature, + } + + this.state = _updatedValidatedState(readS) + resolver() - this._onDidChangeState.fire('all') + this._onDidChangeState.fire() }) + + } + private async _readState(): Promise { const encryptedState = this._storageService.get(STORAGE_KEY, StorageScope.APPLICATION) @@ -172,7 +230,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { const newModelSelectionOfFeature = this.state.modelSelectionOfFeature - const newSettingsOfProvider = { + const newSettingsOfProvider: SettingsOfProvider = { ...this.state.settingsOfProvider, [providerName]: { ...this.state.settingsOfProvider[providerName], @@ -182,38 +240,17 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { const newGlobalSettings = this.state.globalSettings - // if changed models or enabled a provider, recompute models list - const modelsListChanged = settingName === 'models' || settingName === '_enabled' - const newModelsList = modelsListChanged ? _computeModelOptions(newSettingsOfProvider) : this.state._modelOptions - - const newState: VoidSettingsState = { + const newState = { modelSelectionOfFeature: newModelSelectionOfFeature, settingsOfProvider: newSettingsOfProvider, globalSettings: newGlobalSettings, - _modelOptions: newModelsList, } - // this must go above this.setanythingelse() - this.state = newState - - // if the user-selected model is no longer in the list, update the selection for each feature that needs it to something relevant (the 0th model available, or null) - if (modelsListChanged) { - for (const featureName of featureNames) { - - const currentSelection = newModelSelectionOfFeature[featureName] - const selnIdx = currentSelection === null ? -1 : newModelsList.findIndex(m => modelSelectionsEqual(m.selection, currentSelection)) - - if (selnIdx === -1) { - if (newModelsList.length !== 0) - this.setModelSelectionOfFeature(featureName, newModelsList[0].selection, { doNotApplyEffects: true }) - else - this.setModelSelectionOfFeature(featureName, null, { doNotApplyEffects: true }) - } - } - } + this.state = _updatedValidatedState(newState) await this._storeState() - this._onDidChangeState.fire('settingsOfProvider') + this._onDidChangeState.fire() + } @@ -227,7 +264,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { } this.state = newState await this._storeState() - this._onDidChangeState.fire(['globalSettings', settingName]) + this._onDidChangeState.fire() } @@ -247,7 +284,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { return await this._storeState() - this._onDidChangeState.fire('modelSelectionOfFeature') + this._onDidChangeState.fire() } diff --git a/src/vs/platform/void/common/voidSettingsTypes.ts b/src/vs/platform/void/common/voidSettingsTypes.ts index 26758593..fe79d73d 100644 --- a/src/vs/platform/void/common/voidSettingsTypes.ts +++ b/src/vs/platform/void/common/voidSettingsTypes.ts @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ - +import { VoidSettingsState } from './voidSettingsService.js' export type VoidModelInfo = { @@ -186,28 +186,23 @@ export const customSettingNamesOfProvider = (providerName: ProviderName) => { return Object.keys(defaultProviderSettings[providerName]) as CustomSettingName[] } -export const getProvidersWithoutModels = (settingsOfProvider: SettingsOfProvider) => { - return Object.entries(settingsOfProvider) - .filter(([name, provider]) => provider._enabled && provider.models.length === 0) - .map(([name]) => name) -} type CommonProviderSettings = { - _enabled: boolean | undefined, // undefined initially, computed when user types in all fields + _didFillInProviderSettings: boolean | undefined, // undefined initially, computed when user types in all fields models: VoidModelInfo[], } -export type SettingsForProvider = CustomProviderSettings & CommonProviderSettings +export type SettingsAtProvider = CustomProviderSettings & CommonProviderSettings // part of state export type SettingsOfProvider = { - [providerName in ProviderName]: SettingsForProvider + [providerName in ProviderName]: SettingsAtProvider } -export type SettingName = keyof SettingsForProvider +export type SettingName = keyof SettingsAtProvider @@ -316,7 +311,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName undefined, } } - else if (settingName === '_enabled') { + else if (settingName === '_didFillInProviderSettings') { return { title: '(never)', placeholder: '(never)', @@ -380,55 +375,55 @@ export const defaultSettingsOfProvider: SettingsOfProvider = { ...defaultCustomSettings, ...defaultProviderSettings.anthropic, ...voidInitModelOptions.anthropic, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, openAI: { ...defaultCustomSettings, ...defaultProviderSettings.openAI, ...voidInitModelOptions.openAI, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, deepseek: { ...defaultCustomSettings, ...defaultProviderSettings.deepseek, ...voidInitModelOptions.deepseek, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, gemini: { ...defaultCustomSettings, ...defaultProviderSettings.gemini, ...voidInitModelOptions.gemini, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, mistral: { ...defaultCustomSettings, ...defaultProviderSettings.mistral, ...voidInitModelOptions.mistral, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, groq: { // aggregator ...defaultCustomSettings, ...defaultProviderSettings.groq, ...voidInitModelOptions.groq, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, openRouter: { // aggregator ...defaultCustomSettings, ...defaultProviderSettings.openRouter, ...voidInitModelOptions.openRouter, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, openAICompatible: { // aggregator ...defaultCustomSettings, ...defaultProviderSettings.openAICompatible, ...voidInitModelOptions.openAICompatible, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, ollama: { // aggregator ...defaultCustomSettings, ...defaultProviderSettings.ollama, ...voidInitModelOptions.ollama, - _enabled: undefined, + _didFillInProviderSettings: undefined, }, } @@ -458,10 +453,6 @@ export const displayInfoOfFeatureName = (featureName: FeatureName) => { } - - - - // the models of these can be refreshed (in theory all can, but not all should) export const refreshableProviderNames = localProviderNames export type RefreshableProviderName = typeof refreshableProviderNames[number] @@ -471,6 +462,45 @@ export type RefreshableProviderName = typeof refreshableProviderNames[number] +// use this in isFeatuerNameDissbled +export const isProviderNameDisabled = (providerName: ProviderName, settingsState: VoidSettingsState) => { + + const settingsAtProvider = settingsState.settingsOfProvider[providerName] + const isAutodetected = (refreshableProviderNames as string[]).includes(providerName) + + const isDisabled = settingsAtProvider.models.length === 0 + if (isDisabled) { + return isAutodetected ? 'providerNotAutoDetected' : (!settingsAtProvider._didFillInProviderSettings ? 'notFilledIn' : 'addModel') + } + return false +} + +export const isFeatureNameDisabled = (featureName: FeatureName, settingsState: VoidSettingsState) => { + // if has a selected provider, check if it's enabled + const selectedProvider = settingsState.modelSelectionOfFeature[featureName] + + if (selectedProvider) { + const { providerName } = selectedProvider + return isProviderNameDisabled(providerName, settingsState) + } + + // if there are any models they can turn on, tell them that + const canTurnOnAModel = !!providerNames.find(providerName => settingsState.settingsOfProvider[providerName].models.filter(m => m.isHidden).length !== 0) + if (canTurnOnAModel) return 'needToEnableModel' + + // if there are any providers filled in, then they just need to add a model + const anyFilledIn = !!providerNames.find(providerName => settingsState.settingsOfProvider[providerName]._didFillInProviderSettings) + if (anyFilledIn) return 'addModel' + + return 'addProvider' +} + + + + + + + export type GlobalSettings = { diff --git a/src/vs/platform/void/electron-main/llmMessage/openai.ts b/src/vs/platform/void/electron-main/llmMessage/openai.ts index 8b7afd94..268eadfb 100644 --- a/src/vs/platform/void/electron-main/llmMessage/openai.ts +++ b/src/vs/platform/void/electron-main/llmMessage/openai.ts @@ -99,6 +99,7 @@ export const sendOpenAIFIM: _InternalSendLLMFIMMessageFnType = ({ messages, onTe .create(options) .then(async response => { // TODO!!! + console.log('RESPONSE', response) }) } diff --git a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts index 8b393f1a..7cea9d5a 100644 --- a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts @@ -12,6 +12,7 @@ import { sendOpenAIChat } from './openai.js'; import { sendGeminiChat } from './gemini.js'; import { sendGroqChat } from './groq.js'; import { sendMistralChat } from './mistral.js'; +import { displayInfoOfProviderName } from '../../common/voidSettingsTypes.js'; const cleanChatMessages = (messages: LLMChatMessage[]): _InternalLLMChatMessage[] => { @@ -49,7 +50,7 @@ const cleanChatMessages = (messages: LLMChatMessage[]): _InternalLLMChatMessage[ export const sendLLMMessage = ({ - type, + messagesType, aiInstructions, messages: messages_, onText: onText_, @@ -66,20 +67,20 @@ export const sendLLMMessage = ({ ) => { // messages.unshift({ role: 'system', content: aiInstructions }) - const messagesArr = type === 'sendChatMessage' ? cleanChatMessages(messages_) : [] + const messagesArr = messagesType === 'chatMessages' ? cleanChatMessages(messages_) : [] // only captures number of messages and message "shape", no actual code, instructions, prompts, etc const captureLLMEvent = (eventId: string, extras?: object) => { metricsService.capture(eventId, { providerName, modelName, - ...type === 'sendChatMessage' ? { + ...messagesType === 'chatMessages' ? { numMessages: messagesArr?.length, messagesShape: messagesArr?.map(msg => ({ role: msg.role, length: msg.content.length })), origNumMessages: messages_?.length, origMessagesShape: messages_?.map(msg => ({ role: msg.role, length: msg.content.length })), - } : type === 'sendFIMMessage' ? { + } : messagesType === 'FIMMessage' ? { prefixLength: messages_.prefix.length, suffixLength: messages_.suffix.length, } : {}, @@ -109,6 +110,11 @@ export const sendLLMMessage = ({ const onError: OnError = ({ message: error, fullError }) => { if (_didAbort) return console.error('sendLLMMessage onError:', error) + + // handle failed to fetch errors, which give 0 information by design + if (error === 'TypeError: fetch failed') + error = `Failed to fetch from ${displayInfoOfProviderName(providerName).title}. This likely means you specified the wrong endpoint in Void Settings, or your local model provider like Ollama is powered off.` + captureLLMEvent(`${loggingName} - Error`, { error }) onError_({ message: error, fullError }) } @@ -139,8 +145,8 @@ export const sendLLMMessage = ({ break; case 'ollama': if ( // TODO @andrew in future we want to use our own templates instead of using ollamaFIM - type === 'sendFIMMessage' - && settingsOfProvider['ollama']._enabled + messagesType === 'FIMMessage' + && settingsOfProvider['ollama']._didFillInProviderSettings && settingsOfProvider['ollama'].models.some(m => !m.isHidden) ) sendOllamaFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }) diff --git a/src/vs/workbench/contrib/void/browser/autocompleteService.ts b/src/vs/workbench/contrib/void/browser/autocompleteService.ts index 64d181f0..6b67c6be 100644 --- a/src/vs/workbench/contrib/void/browser/autocompleteService.ts +++ b/src/vs/workbench/contrib/void/browser/autocompleteService.ts @@ -785,13 +785,13 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ } console.log('BB') - console.log(predictionType) + console.log('type', predictionType) // set parameters of `newAutocompletion` appropriately newAutocompletion.llmPromise = new Promise((resolve, reject) => { const requestId = this._llmMessageService.sendLLMMessage({ - type: 'sendFIMMessage', + messagesType: 'FIMMessage', messages: { prefix: llmPrefix, suffix: llmSuffix, diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 34b4dad4..d0ca28b2 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -70,7 +70,7 @@ export type ThreadsState = { export type ThreadStreamState = { [threadId: string]: undefined | { - error?: { message: string, fullError: Error | null }; + error?: { message: string, fullError: Error | null, }; messageSoFar?: string; streamingToken?: string; } @@ -202,12 +202,12 @@ class ChatThreadService extends Disposable implements IChatThreadService { this._setStreamState(threadId, { error: undefined }) const llmCancelToken = this._llmMessageService.sendLLMMessage({ - type: 'sendChatMessage', + messagesType: 'chatMessages', logging: { loggingName: 'Chat' }, useProviderFor: 'Ctrl+L', messages: [ { role: 'system', content: chat_systemMessage }, - ...this.getCurrentThread().messages.map(m => ({ role: m.role, content: m.content || '(null)' })), + ...this.getCurrentThread().messages.map(m => ({ role: m.role, content: m.content || '(empty model output)' })), ], onText: ({ newText, fullText }) => { this._setStreamState(threadId, { messageSoFar: fullText }) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 09da6af3..d4d7d065 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -102,13 +102,13 @@ const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number // similar to ServiceLLM export type StartApplyingOpts = { - featureName: 'Ctrl+K'; + from: 'QuickEdit'; diffareaid: number; // id of the CtrlK area (contains text selection) } | { - featureName: 'Ctrl+L'; + from: 'Chat'; applyStr: string; } | { - featureName: 'Autocomplete'; + from: 'Autocomplete'; range: IRange; userMessage: string; } @@ -1209,13 +1209,13 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { private _initializeStartApplying(opts: StartApplyingOpts): DiffZone | undefined { - const { featureName } = opts + const { from } = opts let startLine: number let endLine: number let uri: URI - if (featureName === 'Ctrl+L') { + if (from === 'Chat') { const uri_ = this._getActiveEditorURI() if (!uri_) return @@ -1231,7 +1231,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { endLine = numLines } - else if (featureName === 'Ctrl+K') { + else if (from === 'QuickEdit') { const { diffareaid } = opts const ctrlKZone = this.diffAreaOfId[diffareaid] if (ctrlKZone.type !== 'CtrlKZone') return @@ -1242,7 +1242,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { endLine = endLine_ } else { - throw new Error(`Void: diff.type not recognized on: ${featureName}`) + throw new Error(`Void: diff.type not recognized on: ${from}`) } const currentFileStr = this._readURI(uri) @@ -1278,7 +1278,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { this._onDidChangeStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) this._onDidAddOrDeleteDiffZones.fire({ uri }) - if (featureName === 'Ctrl+K') { + if (from === 'QuickEdit') { const { diffareaid } = opts const ctrlKZone = this.diffAreaOfId[diffareaid] if (ctrlKZone.type !== 'CtrlKZone') return @@ -1289,14 +1289,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // now handle messages let messages: LLMChatMessage[] - if (featureName === 'Ctrl+L') { + if (from === 'Chat') { const userContent = fastApply_userMessage({ originalCode, applyStr: opts.applyStr, uri }) messages = [ { role: 'system', content: fastApply_systemMessage, }, { role: 'user', content: userContent, } ] } - else if (featureName === 'Ctrl+K') { + else if (from === 'QuickEdit') { const { diffareaid } = opts const ctrlKZone = this.diffAreaOfId[diffareaid] if (ctrlKZone.type !== 'CtrlKZone') return @@ -1323,14 +1323,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { ] // } } - else { throw new Error(`featureName ${featureName} is invalid`) } + else { throw new Error(`featureName ${from} is invalid`) } const onDone = (hadError: boolean) => { diffZone._streamState = { isStreaming: false, } this._onDidChangeStreaming.fire({ uri, diffareaid: diffZone.diffareaid }) - if (featureName === 'Ctrl+K') { + if (from === 'QuickEdit') { const ctrlKZone = this.diffAreaOfId[opts.diffareaid] as CtrlKZone ctrlKZone._linkedStreamingDiffZone = null @@ -1350,11 +1350,11 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { const extractText = (fullText: string, recentlyAddedTextLen: number) => { - if (featureName === 'Ctrl+K') { + if (from === 'QuickEdit') { if (isOllamaFIM) return fullText return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: modelFimTags.midTag }) } - else if (featureName === 'Ctrl+L') { + else if (from === 'Chat') { return extractCodeFromRegular({ text: fullText, recentlyAddedTextLen }) } throw 1 @@ -1367,9 +1367,9 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { let prevIgnoredSuffix = '' streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({ - type: 'sendChatMessage', - useProviderFor: opts.featureName === 'Ctrl+L' ? 'FastApply' : 'Ctrl+K', - logging: { loggingName: `startApplying - ${featureName}` }, + messagesType: 'chatMessages', + useProviderFor: opts.from === 'Chat' ? 'FastApply' : 'Ctrl+K', + logging: { loggingName: `startApplying - ${from}` }, messages, onText: ({ newText: newText_ }) => { diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 6d7fc46b..28cd2539 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -45,7 +45,7 @@ const CodeButtonsOnHover = ({ text }: { text: string }) => { const onApply = useCallback(() => { inlineDiffService.startApplying({ - featureName: 'Ctrl+L', + from: 'Chat', applyStr: text, }) metricsService.capture('Apply Code', { length: text.length }) // capture the length only diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index f1a3456a..71609e14 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -7,11 +7,12 @@ import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'reac import { useSettingsState, useSidebarState, useChatThreadsState, useQuickEditState, useAccessor } from '../util/services.js'; import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; -import { ButtonStop, ButtonSubmit, IconX, VoidInputForm } from '../sidebar-tsx/SidebarChat.js'; +import { ButtonStop, ButtonSubmit, IconX, VoidChatArea } from '../sidebar-tsx/SidebarChat.js'; import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js'; import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js'; import { useRefState } from '../util/helpers.js'; import { useScrollbarStyles } from '../util/useScrollbarStyles.js'; +import { isFeatureNameDisabled } from '../../../../../../../platform/void/common/voidSettingsTypes.js'; export const QuickEditChat = ({ diffareaid, @@ -42,9 +43,11 @@ export const QuickEditChat = ({ }, [onChangeHeight]); + const settingsState = useSettingsState() + // state of current message const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!(initText ?? '')) // the user's instructions - const isDisabled = instructionsAreEmpty + const isDisabled = instructionsAreEmpty || !!isFeatureNameDisabled('Ctrl+K', settingsState) const [currStreamingDiffZoneRef, setCurrentlyStreamingDiffZone] = useRefState(initStreamingDiffZoneId) const isStreaming = currStreamingDiffZoneRef.current !== null @@ -55,7 +58,7 @@ export const QuickEditChat = ({ textAreaFnsRef.current?.disable() const id = inlineDiffsService.startApplying({ - featureName: 'Ctrl+K', + from: 'QuickEdit', diffareaid: diffareaid, }) setCurrentlyStreamingDiffZone(id ?? null) @@ -79,7 +82,7 @@ export const QuickEditChat = ({ const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_K_ACTION_ID)?.getLabel() return
- - +
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx index 2aa7da49..425ce3c9 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx @@ -23,8 +23,9 @@ export const ErrorDisplay = ({ const [isExpanded, setIsExpanded] = useState(false); const details = errorDetails(fullError) + const isExpandable = !!details - const message = message_ === 'TypeError: fetch failed' ? `TypeError: fetch failed. This likely means you specified the wrong endpoint in Void Settings, or a provider like Ollama is powered off.` : message_ + '' + const message = message_ + '' return (
@@ -45,7 +46,7 @@ export const ErrorDisplay = ({
- {details && ( + {isExpandable && (