mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
model and provider error handling
This commit is contained in:
parent
1be9300dc4
commit
2f49650e15
17 changed files with 296 additions and 224 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ export type RefreshModelStateOfProvider = Record<RefreshableProviderName, Refres
|
|||
|
||||
|
||||
const refreshBasedOn: { [k in RefreshableProviderName]: (keyof SettingsOfProvider[k])[] } = {
|
||||
ollama: ['_enabled', 'endpoint'],
|
||||
// openAICompatible: ['_enabled', 'endpoint', 'apiKey'],
|
||||
ollama: ['_didFillInProviderSettings', 'endpoint'],
|
||||
// openAICompatible: ['_didFillInProviderSettings', 'endpoint', 'apiKey'],
|
||||
}
|
||||
const REFRESH_INTERVAL = 5_000
|
||||
// const COOLDOWN_TIMEOUT = 300
|
||||
|
|
@ -95,7 +95,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
|
||||
for (const providerName of refreshableProviderNames) {
|
||||
|
||||
// const { _enabled: enabled } = this.voidSettingsService.state.settingsOfProvider[providerName]
|
||||
// const { '_didFillInProviderSettings': enabled } = this.voidSettingsService.state.settingsOfProvider[providerName]
|
||||
this.startRefreshingModels(providerName, autoOptions)
|
||||
|
||||
// every time providerName.enabled changes, refresh models too, like a useEffect
|
||||
|
|
@ -175,7 +175,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
|
|||
{ enableProviderOnSuccess: options.enableProviderOnSuccess, hideRefresh: options.doNotFire }
|
||||
)
|
||||
|
||||
if (options.enableProviderOnSuccess) this.voidSettingsService.setSettingOfProvider(providerName, '_enabled', true)
|
||||
if (options.enableProviderOnSuccess) this.voidSettingsService.setSettingOfProvider(providerName, '_didFillInProviderSettings', true)
|
||||
|
||||
this._setRefreshState(providerName, 'finished', options)
|
||||
autoPoll()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { registerSingleton, InstantiationType } from '../../instantiation/common
|
|||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
|
||||
import { IMetricsService } from './metricsService.js';
|
||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultModelNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings } from './voidSettingsTypes.js';
|
||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultModelNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, displayInfoOfProviderName, defaultProviderSettings } from './voidSettingsTypes.js';
|
||||
|
||||
|
||||
const STORAGE_KEY = 'void.settingsServiceStorage'
|
||||
|
|
@ -42,8 +42,8 @@ export type VoidSettingsState = {
|
|||
readonly _modelOptions: ModelOption[] // computed based on the two above items
|
||||
}
|
||||
|
||||
type RealVoidSettings = Exclude<keyof VoidSettingsState, '_modelOptions'>
|
||||
type EventProp<T extends RealVoidSettings = RealVoidSettings> = T extends 'globalSettings' ? [T, keyof VoidSettingsState[T]] : T | 'all'
|
||||
// type RealVoidSettings = Exclude<keyof VoidSettingsState, '_modelOptions'>
|
||||
// type EventProp<T extends RealVoidSettings = RealVoidSettings> = 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<void>;
|
||||
|
||||
onDidChangeState: Event<EventProp>;
|
||||
onDidChangeState: Event<void>;
|
||||
|
||||
setSettingOfProvider: SetSettingOfProviderFn;
|
||||
setModelSelectionOfFeature: SetModelSelectionOfFeatureFn;
|
||||
|
|
@ -64,26 +64,76 @@ export interface IVoidSettingsService {
|
|||
}
|
||||
|
||||
|
||||
let _computeModelOptions = (settingsOfProvider: SettingsOfProvider) => {
|
||||
let modelOptions: ModelOption[] = []
|
||||
|
||||
const _updatedValidatedState = (state: Omit<VoidSettingsState, '_modelOptions'>) => {
|
||||
|
||||
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<IVoidSettingsService>('VoidS
|
|||
class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<EventProp>();
|
||||
readonly onDidChangeState: Event<EventProp> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
|
||||
|
||||
state: VoidSettingsState;
|
||||
waitForInitState: Promise<void> // 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<VoidSettingsState> {
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<providerName extends ProviderName> = CustomProviderSettings<providerName> & CommonProviderSettings
|
||||
export type SettingsAtProvider<providerName extends ProviderName> = CustomProviderSettings<providerName> & CommonProviderSettings
|
||||
|
||||
// part of state
|
||||
export type SettingsOfProvider = {
|
||||
[providerName in ProviderName]: SettingsForProvider<providerName>
|
||||
[providerName in ProviderName]: SettingsAtProvider<providerName>
|
||||
}
|
||||
|
||||
|
||||
export type SettingName = keyof SettingsForProvider<ProviderName>
|
||||
export type SettingName = keyof SettingsAtProvider<ProviderName>
|
||||
|
||||
|
||||
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ export const sendOpenAIFIM: _InternalSendLLMFIMMessageFnType = ({ messages, onTe
|
|||
.create(options)
|
||||
.then(async response => {
|
||||
// TODO!!!
|
||||
console.log('RESPONSE', response)
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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_ }) => {
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<number | null>(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 <div ref={sizerRef} style={{ maxWidth: 450 }} className={`py-2 w-full`}>
|
||||
<VoidInputForm
|
||||
<VoidChatArea
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onInterrupt}
|
||||
onClose={onX}
|
||||
|
|
@ -113,7 +116,7 @@ export const QuickEditChat = ({
|
|||
}}
|
||||
multiline={true}
|
||||
/>
|
||||
</VoidInputForm>
|
||||
</VoidChatArea>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className={`rounded-lg border border-red-200 bg-red-50 p-4 overflow-auto`}>
|
||||
|
|
@ -45,7 +46,7 @@ export const ErrorDisplay = ({
|
|||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
{details && (
|
||||
{isExpandable && (
|
||||
<button className='text-red-600 hover:text-red-800 p-1 rounded'
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
|
|||
import { filenameToVscodeLanguage } from '../../../helpers/detectLanguage.js';
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
|
||||
import { Pencil } from 'lucide-react';
|
||||
import { FeatureName } from '../../../../../../../platform/void/common/voidSettingsTypes.js';
|
||||
import { FeatureName, isFeatureNameDisabled } from '../../../../../../../platform/void/common/voidSettingsTypes.js';
|
||||
|
||||
|
||||
export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps<SVGSVGElement>) => {
|
||||
|
|
@ -158,7 +158,7 @@ interface VoidInputFormProps {
|
|||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const VoidInputForm: React.FC<VoidInputFormProps> = ({
|
||||
export const VoidChatArea: React.FC<VoidInputFormProps> = ({
|
||||
children,
|
||||
onSubmit,
|
||||
onAbort,
|
||||
|
|
@ -578,6 +578,7 @@ const ChatBubble = ({ chatMessage, isLoading }: { chatMessage: ChatMessage, isLo
|
|||
: role === 'user' ? `px-2 self-end w-fit max-w-full whitespace-pre-wrap` // user words should be pre
|
||||
: role === 'assistant' ? `px-2 self-start w-full max-w-full` : ''
|
||||
}
|
||||
${role !== 'assistant' ? 'my-2' : ''}
|
||||
`}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
|
|
@ -621,6 +622,7 @@ export const SidebarChat = () => {
|
|||
// const modelService = accessor.get('IModelService')
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
const settingsState = useSettingsState()
|
||||
// ----- HIGHER STATE -----
|
||||
// sidebar state
|
||||
const sidebarStateService = accessor.get('ISidebarStateService')
|
||||
|
|
@ -654,7 +656,8 @@ export const SidebarChat = () => {
|
|||
// state of current message
|
||||
const initVal = ''
|
||||
const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!initVal)
|
||||
const isDisabled = instructionsAreEmpty
|
||||
|
||||
const isDisabled = instructionsAreEmpty || !!isFeatureNameDisabled('Ctrl+L', settingsState)
|
||||
|
||||
const [sidebarRef, sidebarDimensions] = useResizeObserver()
|
||||
const [formRef, formDimensions] = useResizeObserver()
|
||||
|
|
@ -670,22 +673,13 @@ export const SidebarChat = () => {
|
|||
if (isDisabled) return
|
||||
if (isStreaming) return
|
||||
|
||||
console.log('chatThreadsService', chatThreadsService ? chatThreadsService : '!undefined')
|
||||
|
||||
// send message to LLM
|
||||
const userMessage = textAreaRef.current?.value ?? ''
|
||||
console.log('userMessage', userMessage)
|
||||
console.log('streaming...',)
|
||||
await chatThreadsService.addUserMessageAndStreamResponse(userMessage)
|
||||
console.log('done streaming',)
|
||||
|
||||
chatThreadsService.setStaging([]) // clear staging
|
||||
console.log('set staging',)
|
||||
textAreaFnsRef.current?.setValue('')
|
||||
console.log('set value',)
|
||||
textAreaRef.current?.focus() // focus input after submit
|
||||
console.log('textAreaRef', textAreaRef.current)
|
||||
console.log('focus',)
|
||||
|
||||
}, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef])
|
||||
|
||||
|
|
@ -765,7 +759,7 @@ export const SidebarChat = () => {
|
|||
}
|
||||
}, [onSubmit])
|
||||
const inputForm = <div className={`right-0 left-0 m-2 z-[999] overflow-hidden ${previousMessages.length > 0 ? 'absolute bottom-0' : ''}`}>
|
||||
<VoidInputForm
|
||||
<VoidChatArea
|
||||
formRef={formRef}
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onAbort}
|
||||
|
|
@ -785,7 +779,7 @@ export const SidebarChat = () => {
|
|||
fnsRef={textAreaFnsRef}
|
||||
multiline={true}
|
||||
/>
|
||||
</VoidInputForm>
|
||||
</VoidChatArea>
|
||||
</div>
|
||||
|
||||
return <div ref={sidebarRef} className={`w-full h-full`}>
|
||||
|
|
|
|||
|
|
@ -298,9 +298,9 @@ export const VoidCheckBox = ({ label, value, onClick, className }: { label: stri
|
|||
|
||||
|
||||
|
||||
export const VoidCustomSelectBox = <T extends any>({
|
||||
export const VoidCustomDropdownBox = <T extends any>({
|
||||
options,
|
||||
selectedOption: selectedOption_,
|
||||
selectedOption,
|
||||
onChangeOption,
|
||||
getOptionDropdownName,
|
||||
getOptionDisplayName,
|
||||
|
|
@ -311,7 +311,7 @@ export const VoidCustomSelectBox = <T extends any>({
|
|||
gap = 0,
|
||||
}: {
|
||||
options: T[];
|
||||
selectedOption?: T;
|
||||
selectedOption: T | undefined;
|
||||
onChangeOption: (newValue: T) => void;
|
||||
getOptionDropdownName: (option: T) => string;
|
||||
getOptionDisplayName: (option: T) => string;
|
||||
|
|
@ -335,7 +335,7 @@ export const VoidCustomSelectBox = <T extends any>({
|
|||
} = useFloating({
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
placement:'bottom-start',
|
||||
placement: 'bottom-start',
|
||||
|
||||
middleware: [
|
||||
offset(gap),
|
||||
|
|
@ -367,17 +367,15 @@ export const VoidCustomSelectBox = <T extends any>({
|
|||
}),
|
||||
],
|
||||
whileElementsMounted: autoUpdate,
|
||||
strategy:'fixed',
|
||||
strategy: 'fixed',
|
||||
});
|
||||
|
||||
// if the selected option is null, use the 0th option
|
||||
// if the selected option is null, set the selection to the 0th option
|
||||
useEffect(() => {
|
||||
if (!options[0]) return
|
||||
if (!selectedOption_) {
|
||||
onChangeOption(options[0]);
|
||||
}
|
||||
}, [selectedOption_, options])
|
||||
const selectedOption = !selectedOption_ ? options[0] : selectedOption_
|
||||
if (options.length === 0) return
|
||||
if (selectedOption) return
|
||||
onChangeOption(options[0])
|
||||
}, [selectedOption, onChangeOption, options])
|
||||
|
||||
// Handle clicks outside
|
||||
useEffect(() => {
|
||||
|
|
@ -404,6 +402,9 @@ export const VoidCustomSelectBox = <T extends any>({
|
|||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [isOpen, refs.floating, refs.reference]);
|
||||
|
||||
if (!selectedOption)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={`inline-block relative ${className}`}>
|
||||
{/* Hidden measurement div */}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { FeatureName, featureNames, getProvidersWithoutModels, ModelSelection, modelSelectionsEqual, ProviderName, providerNames, SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { FeatureName, featureNames, isFeatureNameDisabled, ModelSelection, modelSelectionsEqual, ProviderName, providerNames, SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { useSettingsState, useRefreshModelState, useAccessor } from '../util/services.js'
|
||||
import { _VoidSelectBox, VoidCustomSelectBox } from '../util/inputs.js'
|
||||
import { _VoidSelectBox, VoidCustomDropdownBox } from '../util/inputs.js'
|
||||
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
|
||||
import { IconWarning } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID, VOID_TOGGLE_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'
|
||||
|
|
@ -25,13 +25,13 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat
|
|||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
const selection = voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
const selectedOption = selection ? voidSettingsService.state._modelOptions.find(v => modelSelectionsEqual(v.selection, selection)) : options[0]
|
||||
const selectedOption = selection ? voidSettingsService.state._modelOptions.find(v => modelSelectionsEqual(v.selection, selection))! : options[0]
|
||||
|
||||
const onChangeOption = useCallback((newOption: ModelOption) => {
|
||||
voidSettingsService.setModelSelectionOfFeature(featureName, newOption.selection)
|
||||
}, [voidSettingsService, featureName])
|
||||
|
||||
return <VoidCustomSelectBox
|
||||
return <VoidCustomDropdownBox
|
||||
options={options}
|
||||
selectedOption={selectedOption}
|
||||
onChangeOption={onChangeOption}
|
||||
|
|
@ -73,22 +73,7 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat
|
|||
// />
|
||||
// }
|
||||
|
||||
const MemoizedModelSelectBox = ({ featureName }: { featureName: FeatureName }) => {
|
||||
const settingsState = useSettingsState()
|
||||
const oldOptionsRef = useRef<ModelOption[]>([])
|
||||
const [memoizedOptions, setMemoizedOptions] = useState(oldOptionsRef.current)
|
||||
useEffect(() => {
|
||||
const oldOptions = oldOptionsRef.current
|
||||
const newOptions = settingsState._modelOptions
|
||||
if (!optionsEqual(oldOptions, newOptions)) {
|
||||
setMemoizedOptions(newOptions)
|
||||
}
|
||||
oldOptionsRef.current = newOptions
|
||||
}, [settingsState._modelOptions])
|
||||
|
||||
return <ModelSelectBox featureName={featureName} options={memoizedOptions} />
|
||||
|
||||
}
|
||||
|
||||
export const WarningBox = ({ text, onClick, className }: { text: string; onClick?: () => void; className?: string }) => {
|
||||
|
||||
|
|
@ -114,22 +99,40 @@ export const WarningBox = ({ text, onClick, className }: { text: string; onClick
|
|||
// />
|
||||
}
|
||||
|
||||
const MemoizedModelDropdown = ({ featureName }: { featureName: FeatureName }) => {
|
||||
const settingsState = useSettingsState()
|
||||
const oldOptionsRef = useRef<ModelOption[]>([])
|
||||
const [memoizedOptions, setMemoizedOptions] = useState(oldOptionsRef.current)
|
||||
|
||||
useEffect(() => {
|
||||
const oldOptions = oldOptionsRef.current
|
||||
const newOptions = settingsState._modelOptions
|
||||
if (!optionsEqual(oldOptions, newOptions)) {
|
||||
setMemoizedOptions(newOptions)
|
||||
}
|
||||
oldOptionsRef.current = newOptions
|
||||
}, [settingsState._modelOptions])
|
||||
|
||||
return <ModelSelectBox featureName={featureName} options={memoizedOptions} />
|
||||
|
||||
}
|
||||
|
||||
export const ModelDropdown = ({ featureName }: { featureName: FeatureName }) => {
|
||||
const settingsState = useSettingsState()
|
||||
|
||||
const providersWithMissingModels = getProvidersWithoutModels(settingsState.settingsOfProvider)
|
||||
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
const openSettings = () => { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID); };
|
||||
|
||||
return <>
|
||||
{providersWithMissingModels.length !== 0 ?
|
||||
<WarningBox onClick={openSettings} text={`Model required for ${providersWithMissingModels[0]}`} />
|
||||
: settingsState._modelOptions.length === 0 ?
|
||||
<WarningBox onClick={openSettings} text='Provider required' />
|
||||
: <MemoizedModelSelectBox featureName={featureName} />
|
||||
}
|
||||
</>
|
||||
const isDisabled = isFeatureNameDisabled(featureName, settingsState)
|
||||
if (isDisabled)
|
||||
return <WarningBox onClick={openSettings} text={
|
||||
isDisabled === 'needToEnableModel' ? 'Enable model'
|
||||
: isDisabled === 'addModel' ? 'Add model'
|
||||
: (isDisabled === 'addProvider' || isDisabled === 'notFilledIn' || isDisabled === 'providerNotAutoDetected') ? 'Provider required'
|
||||
: 'Provider required'
|
||||
} />
|
||||
|
||||
return <MemoizedModelDropdown featureName={featureName} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, globalSettingNames, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, globalSettingNames, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidButton, VoidCheckBox, VoidCustomSelectBox, VoidInputBox, VoidInputBox2, VoidSwitch } from '../util/inputs.js'
|
||||
import { VoidButton, VoidCheckBox, VoidCustomDropdownBox, VoidInputBox, VoidInputBox2, VoidSwitch } from '../util/inputs.js'
|
||||
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
|
||||
import { X, RefreshCw, Loader2, Check, MoveRight } from 'lucide-react'
|
||||
import { useScrollbarStyles } from '../util/useScrollbarStyles.js'
|
||||
|
|
@ -79,7 +79,7 @@ const RefreshableModels = () => {
|
|||
|
||||
|
||||
const buttons = refreshableProviderNames.map(providerName => {
|
||||
if (!settingsState.settingsOfProvider[providerName]._enabled) return null
|
||||
if (!settingsState.settingsOfProvider[providerName]._didFillInProviderSettings) return null
|
||||
return <div key={providerName} className='pb-4'>
|
||||
<RefreshModelButton providerName={providerName} />
|
||||
</div>
|
||||
|
|
@ -112,7 +112,7 @@ const AddModelMenu = ({ onSubmit }: { onSubmit: () => void }) => {
|
|||
<div className='flex items-center gap-4'>
|
||||
|
||||
{/* provider */}
|
||||
<VoidCustomSelectBox
|
||||
<VoidCustomDropdownBox
|
||||
options={providerNames}
|
||||
selectedOption={providerName}
|
||||
onChangeOption={(pn) => setProviderName(pn)}
|
||||
|
|
@ -199,7 +199,7 @@ export const ModelDump = () => {
|
|||
for (let providerName of providerNames) {
|
||||
const providerSettings = settingsState.settingsOfProvider[providerName]
|
||||
// if (!providerSettings.enabled) continue
|
||||
modelDump.push(...providerSettings.models.map(model => ({ ...model, providerName, providerEnabled: !!providerSettings._enabled })))
|
||||
modelDump.push(...providerSettings.models.map(model => ({ ...model, providerName, providerEnabled: !!providerSettings._didFillInProviderSettings })))
|
||||
}
|
||||
|
||||
// sort by hidden
|
||||
|
|
@ -223,7 +223,6 @@ export const ModelDump = () => {
|
|||
<div className={`flex-grow flex items-center gap-4`}>
|
||||
<span className='w-full max-w-32'>{isNewProviderName ? displayInfoOfProviderName(providerName).title : ''}</span>
|
||||
<span className='w-fit truncate'>{modelName}</span>
|
||||
{/* <span>{`${modelName} (${providerName})`}</span> */}
|
||||
</div>
|
||||
{/* right part is anything that fits */}
|
||||
<div className='flex items-center gap-4'>
|
||||
|
|
@ -260,7 +259,6 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
|
||||
const accessor = useAccessor()
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
const voidMetricsService = accessor.get('IMetricsService')
|
||||
|
||||
let weChangedTextRef = false
|
||||
|
||||
|
|
@ -284,25 +282,8 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
weChangedTextRef = true
|
||||
instance.value = stateVal as string
|
||||
weChangedTextRef = false
|
||||
|
||||
const isEverySettingPresent = Object.keys(defaultProviderSettings[providerName]).every(key => {
|
||||
return !!settingsAtProvider[key as keyof typeof settingsAtProvider]
|
||||
})
|
||||
|
||||
const shouldEnable = isEverySettingPresent && !settingsAtProvider._enabled // enable if all settings are present and not already enabled
|
||||
const shouldDisable = !isEverySettingPresent && settingsAtProvider._enabled
|
||||
|
||||
if (shouldEnable) {
|
||||
voidSettingsService.setSettingOfProvider(providerName, '_enabled', true)
|
||||
voidMetricsService.capture('Enable Provider', { providerName })
|
||||
}
|
||||
|
||||
if (shouldDisable) {
|
||||
voidSettingsService.setSettingOfProvider(providerName, '_enabled', false)
|
||||
voidMetricsService.capture('Disable Provider', { providerName })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
syncInstance()
|
||||
const disposable = voidSettingsService.onDidChangeState(syncInstance)
|
||||
return [disposable]
|
||||
|
|
@ -318,7 +299,10 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
}
|
||||
|
||||
const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) => {
|
||||
// const voidSettingsState = useSettingsState()
|
||||
const voidSettingsState = useSettingsState()
|
||||
|
||||
const needsModel = isProviderNameDisabled(providerName, voidSettingsState) === 'addModel'
|
||||
|
||||
// const accessor = useAccessor()
|
||||
// const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
|
|
@ -349,6 +333,12 @@ const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) =
|
|||
{settingNames.map((settingName, i) => {
|
||||
return <ProviderSetting key={settingName} providerName={providerName} settingName={settingName} />
|
||||
})}
|
||||
|
||||
{needsModel ?
|
||||
providerName === 'ollama' ?
|
||||
<WarningBox text={`Please install an Ollama model. We'll auto-detect it.`} />
|
||||
: <WarningBox text={`Please add a model for ${providerTitle} below (Models).`} />
|
||||
: null}
|
||||
</div>
|
||||
</div >
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue