mirror of
https://github.com/voideditor/void
synced 2026-05-24 01:48:25 +00:00
Merge remote-tracking branch 'origin/model-selection' into edit-chats
This commit is contained in:
commit
e4694256eb
21 changed files with 467 additions and 262 deletions
|
|
@ -14,8 +14,6 @@ There are a few ways to contribute:
|
|||
|
||||
We highly recommend reading [this](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) article on VSCode's sourcecode organization.
|
||||
|
||||
We are currently putting together our own articles on VSCode and Void's sourcecode organization. The best way to get this information right now is by attending a weekly meeting.
|
||||
|
||||
<!-- ADD BLOG HERE
|
||||
We wrote a [guide to working in VSCode].
|
||||
-->
|
||||
|
|
|
|||
112
create-appimage.sh
Normal file
112
create-appimage.sh
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#!/bin/bash
|
||||
set -e # Exit on error
|
||||
set -x # Print commands as they are executed
|
||||
|
||||
# Configuration
|
||||
APP_NAME="void"
|
||||
APP_VERSION="1.0.0"
|
||||
ARCH="x86_64"
|
||||
|
||||
export ARCH
|
||||
|
||||
# Check if void binary exists in current directory
|
||||
if [ ! -f "./void" ]; then
|
||||
echo "Error: void binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if icon exists
|
||||
if [ ! -f "./void.png" ]; then
|
||||
echo "Error: void.png icon not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temporary directory
|
||||
TEMP_DIR="$(mktemp -d)"
|
||||
echo "Created temporary directory: $TEMP_DIR"
|
||||
APP_DIR="$TEMP_DIR/$APP_NAME.AppDir"
|
||||
|
||||
# Create basic AppDir structure
|
||||
mkdir -pv "$APP_DIR/usr/bin"
|
||||
mkdir -pv "$APP_DIR/usr/lib"
|
||||
mkdir -pv "$APP_DIR/usr/share/applications"
|
||||
mkdir -pv "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
|
||||
|
||||
# Exclude create-appimage.sh and appimagetool-x86_64.AppImage from being copied
|
||||
echo "Copying files excluding create-appimage.sh and appimagetool-x86_64.AppImage..."
|
||||
for file in ./*; do
|
||||
if [[ "$file" != "./create-appimage.sh" && "$file" != "./appimagetool-x86_64.AppImage" ]]; then
|
||||
cp -rv "$file" "$APP_DIR/usr/bin/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy the icon to required locations
|
||||
cp -v ./void.png "$APP_DIR/void.png"
|
||||
cp -v ./void.png "$APP_DIR/usr/share/icons/hicolor/256x256/apps/void.png"
|
||||
|
||||
# Copy dependencies with error checking
|
||||
echo "Copying dependencies..."
|
||||
for lib in $(ldd ./void | grep "=> /" | awk '{print $3}'); do
|
||||
if [ -f "$lib" ]; then
|
||||
cp -v "$lib" "$APP_DIR/usr/lib/" || echo "Failed to copy $lib"
|
||||
else
|
||||
echo "Warning: Library $lib not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# Create desktop file with error checking
|
||||
echo "Creating desktop file..."
|
||||
if ! cat > "$APP_DIR/$APP_NAME.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Name=$APP_NAME
|
||||
Exec=void
|
||||
Icon=void
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
Comment=Void Linux Application
|
||||
EOF
|
||||
then
|
||||
echo "Error creating desktop file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make desktop file executable
|
||||
chmod +x "$APP_DIR/$APP_NAME.desktop"
|
||||
|
||||
# Copy the desktop file to the applications directory
|
||||
cp -v "$APP_DIR/$APP_NAME.desktop" "$APP_DIR/usr/share/applications/"
|
||||
|
||||
# Create AppRun with error checking
|
||||
echo "Creating AppRun..."
|
||||
if ! cat > "$APP_DIR/AppRun" <<EOF
|
||||
#!/bin/bash
|
||||
cd "\$(dirname "\$0")/usr/bin"
|
||||
export LD_LIBRARY_PATH="\$APPDIR/usr/lib:\$LD_LIBRARY_PATH"
|
||||
exec ./void "\$@"
|
||||
EOF
|
||||
then
|
||||
echo "Error creating AppRun"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make AppRun executable
|
||||
chmod +x "$APP_DIR/AppRun"
|
||||
|
||||
# Download appimagetool if not present in the current directory
|
||||
if [ ! -f "./appimagetool-x86_64.AppImage" ]; then
|
||||
echo "Downloading appimagetool-x86_64.AppImage..."
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
else
|
||||
echo "appimagetool-x86_64.AppImage is already present."
|
||||
fi
|
||||
|
||||
# Create the AppImage
|
||||
echo "Creating AppImage..."
|
||||
ARCH=x86_64 ./appimagetool-x86_64.AppImage "$APP_DIR" "${APP_NAME}-${APP_VERSION}-${ARCH}.AppImage"
|
||||
|
||||
# Cleanup
|
||||
echo "Cleaning up..."
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "AppImage creation complete!"
|
||||
|
|
@ -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 = {
|
||||
|
|
@ -87,10 +87,11 @@ export const defaultDeepseekModels = modelInfoOfDefaultModelNames([
|
|||
|
||||
// https://console.groq.com/docs/models
|
||||
export const defaultGroqModels = modelInfoOfDefaultModelNames([
|
||||
"distil-whisper-large-v3-en",
|
||||
"llama3-70b-8192",
|
||||
"llama-3.3-70b-versatile",
|
||||
"llama-3.1-8b-instant",
|
||||
"gemma2-9b-it"
|
||||
"gemma2-9b-it",
|
||||
"mixtral-8x7b-32768"
|
||||
])
|
||||
|
||||
|
||||
|
|
@ -186,28 +187,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>
|
||||
|
||||
|
||||
|
||||
|
|
@ -231,7 +227,7 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
|
|||
}
|
||||
else if (providerName === 'deepseek') {
|
||||
return {
|
||||
title: 'DeepSeek',
|
||||
title: 'DeepSeek.com API',
|
||||
}
|
||||
}
|
||||
else if (providerName === 'openRouter') {
|
||||
|
|
@ -252,17 +248,17 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
|
|||
}
|
||||
else if (providerName === 'gemini') {
|
||||
return {
|
||||
title: 'Gemini',
|
||||
title: 'Gemini API',
|
||||
}
|
||||
}
|
||||
else if (providerName === 'groq') {
|
||||
return {
|
||||
title: 'Groq',
|
||||
title: 'Groq API',
|
||||
}
|
||||
}
|
||||
else if (providerName === 'mistral') {
|
||||
return {
|
||||
title: 'Mistral',
|
||||
title: 'Mistral API',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -316,7 +312,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
|||
undefined,
|
||||
}
|
||||
}
|
||||
else if (settingName === '_enabled') {
|
||||
else if (settingName === '_didFillInProviderSettings') {
|
||||
return {
|
||||
title: '(never)',
|
||||
placeholder: '(never)',
|
||||
|
|
@ -380,55 +376,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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -448,20 +444,16 @@ export const displayInfoOfFeatureName = (featureName: FeatureName) => {
|
|||
if (featureName === 'Autocomplete')
|
||||
return 'Autocomplete'
|
||||
else if (featureName === 'Ctrl+K')
|
||||
return 'Quick Edit'
|
||||
return 'Quick-Edit'
|
||||
else if (featureName === 'Ctrl+L')
|
||||
return 'Sidebar Chat'
|
||||
return 'Chat'
|
||||
else if (featureName === 'FastApply')
|
||||
return 'Fast Apply'
|
||||
return 'Apply'
|
||||
else
|
||||
throw new Error(`Feature Name ${featureName} not allowed`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 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 +463,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,
|
||||
|
|
|
|||
|
|
@ -80,7 +80,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;
|
||||
}
|
||||
|
|
@ -311,12 +311,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
|
||||
|
|
@ -186,18 +186,18 @@ const RenderToken = ({ token, nested = false, noSpace = false }: { token: Token
|
|||
<RenderToken key={index} token={token} />
|
||||
))}
|
||||
</>
|
||||
if (nested)
|
||||
return contents
|
||||
return <p className={`${noSpace ? '' : 'my-4'} leading`}>{contents}</p>
|
||||
if (nested) return contents
|
||||
|
||||
return <p className={`${noSpace ? '' : 'my-4'}`}>
|
||||
{contents}
|
||||
</p>
|
||||
}
|
||||
|
||||
if (t.type === "html") {
|
||||
return (
|
||||
<pre className={`bg-4oid-bg-2 p-4 rounded-lg ${noSpace ? '' : 'my-4'} font-mono text-sm`}>
|
||||
{`<html>`}
|
||||
<p className={`${noSpace ? '' : 'my-4'}`}>
|
||||
{t.raw}
|
||||
{`</html>`}
|
||||
</pre>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -78,8 +81,10 @@ export const QuickEditChat = ({
|
|||
|
||||
const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_K_ACTION_ID)?.getLabel()
|
||||
|
||||
const chatAreaRef = useRef<HTMLDivElement | null>(null)
|
||||
return <div ref={sizerRef} style={{ maxWidth: 450 }} className={`py-2 w-full`}>
|
||||
<VoidInputForm
|
||||
<VoidChatArea
|
||||
divRef={chatAreaRef}
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onInterrupt}
|
||||
onClose={onX}
|
||||
|
|
@ -87,6 +92,7 @@ export const QuickEditChat = ({
|
|||
isDisabled={isDisabled}
|
||||
featureName="Ctrl+K"
|
||||
className="py-2 w-full"
|
||||
onClickAnywhere={() => { textAreaRef.current?.focus() }}
|
||||
>
|
||||
<VoidInputBox2
|
||||
className='px-1'
|
||||
|
|
@ -113,7 +119,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)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,14 +15,15 @@ import { URI } from '../../../../../../../base/common/uri.js';
|
|||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
|
||||
import { ErrorDisplay } from './ErrorDisplay.js';
|
||||
import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js';
|
||||
import { ModelDropdown, WarningBox } from '../void-settings-tsx/ModelDropdown.js';
|
||||
import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js';
|
||||
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
||||
import { useScrollbarStyles } from '../util/useScrollbarStyles.js';
|
||||
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, X } from 'lucide-react';
|
||||
import { FeatureName } from '../../../../../../../platform/void/common/voidSettingsTypes.js';
|
||||
import { FeatureName, isFeatureNameDisabled } from '../../../../../../../platform/void/common/voidSettingsTypes.js';
|
||||
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
|
||||
|
||||
|
||||
|
||||
|
|
@ -136,7 +137,7 @@ export const IconLoading = ({ className = '' }: { className?: string }) => {
|
|||
}
|
||||
|
||||
|
||||
interface VoidInputFormProps {
|
||||
interface VoidChatAreaProps {
|
||||
// Required
|
||||
children: React.ReactNode; // This will be the input component
|
||||
|
||||
|
|
@ -145,7 +146,7 @@ interface VoidInputFormProps {
|
|||
onAbort: () => void;
|
||||
isStreaming: boolean;
|
||||
isDisabled?: boolean;
|
||||
formRef?: React.RefObject<HTMLFormElement>;
|
||||
divRef?: React.RefObject<HTMLDivElement>;
|
||||
|
||||
// UI customization
|
||||
featureName: FeatureName;
|
||||
|
|
@ -159,16 +160,18 @@ interface VoidInputFormProps {
|
|||
// selections?: any[];
|
||||
// onSelectionsChange?: (selections: any[]) => void;
|
||||
|
||||
onClickAnywhere?: () => void;
|
||||
// Optional close button
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const VoidInputForm: React.FC<VoidInputFormProps> = ({
|
||||
export const VoidChatArea: React.FC<VoidChatAreaProps> = ({
|
||||
children,
|
||||
onSubmit,
|
||||
onAbort,
|
||||
onClose,
|
||||
formRef,
|
||||
onClickAnywhere,
|
||||
divRef,
|
||||
isStreaming = false,
|
||||
isDisabled = false,
|
||||
className = '',
|
||||
|
|
@ -180,8 +183,8 @@ export const VoidInputForm: React.FC<VoidInputFormProps> = ({
|
|||
setStaging,
|
||||
}) => {
|
||||
return (
|
||||
<form
|
||||
ref={formRef}
|
||||
<div
|
||||
ref={divRef}
|
||||
className={`
|
||||
flex flex-col gap-1 p-2 relative input text-left shrink-0
|
||||
transition-all duration-200
|
||||
|
|
@ -190,6 +193,9 @@ export const VoidInputForm: React.FC<VoidInputFormProps> = ({
|
|||
border border-void-border-3 focus-within:border-void-border-1 hover:border-void-border-1
|
||||
${className}
|
||||
`}
|
||||
onClick={(e) => {
|
||||
onClickAnywhere?.()
|
||||
}}
|
||||
>
|
||||
{/* Selections section */}
|
||||
{showSelections && staging && setStaging && (
|
||||
|
|
@ -220,7 +226,8 @@ export const VoidInputForm: React.FC<VoidInputFormProps> = ({
|
|||
{/* Bottom row */}
|
||||
<div className='flex flex-row justify-between items-end gap-1'>
|
||||
{showModelDropdown && (
|
||||
<div className='max-w-[150px] @@[&_select]:!void-border-none @@[&_select]:!void-outline-none flex-grow'>
|
||||
<div className='max-w-[150px] @@[&_select]:!void-border-none @@[&_select]:!void-outline-none flex-grow'
|
||||
onClick={(e) => { e.preventDefault(); e.stopPropagation() }}>
|
||||
<ModelDropdown featureName={featureName} />
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -234,7 +241,7 @@ export const VoidInputForm: React.FC<VoidInputFormProps> = ({
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -635,7 +642,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM
|
|||
}
|
||||
|
||||
chatbubbleContents = <>
|
||||
<VoidInputForm
|
||||
<VoidChatArea
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onAbort}
|
||||
isStreaming={false}
|
||||
|
|
@ -662,7 +669,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM
|
|||
fnsRef={textAreaFnsRef}
|
||||
multiline={true}
|
||||
/>
|
||||
</VoidInputForm>
|
||||
</VoidChatArea>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
|
@ -678,6 +685,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM
|
|||
: 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)}
|
||||
|
|
@ -731,6 +739,7 @@ export const SidebarChat = () => {
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const chatThreadsService = accessor.get('IChatThreadService')
|
||||
|
||||
const settingsState = useSettingsState()
|
||||
// ----- HIGHER STATE -----
|
||||
// sidebar state
|
||||
const sidebarStateService = accessor.get('ISidebarStateService')
|
||||
|
|
@ -763,10 +772,11 @@ 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()
|
||||
const [chatAreaRef, chatAreaDimensions] = useResizeObserver()
|
||||
const [historyRef, historyDimensions] = useResizeObserver()
|
||||
|
||||
useScrollbarStyles(sidebarRef)
|
||||
|
|
@ -829,7 +839,7 @@ export const SidebarChat = () => {
|
|||
py-4
|
||||
${prevMessagesHTML.length === 0 && !messageSoFar ? 'hidden' : ''}
|
||||
`}
|
||||
style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - formDimensions.height - 36 }} // the height of the previousMessages is determined by all other heights
|
||||
style={{ maxHeight: sidebarDimensions.height - historyDimensions.height - chatAreaDimensions.height - 36 }} // the height of the previousMessages is determined by all other heights
|
||||
>
|
||||
{/* previous messages */}
|
||||
{prevMessagesHTML}
|
||||
|
|
@ -848,7 +858,7 @@ export const SidebarChat = () => {
|
|||
showDismiss={true}
|
||||
/>
|
||||
|
||||
<WarningBox className='text-sm my-2 pl-4' onClick={() => { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }} text='Open settings' />
|
||||
<WarningBox className='text-sm my-2 mx-4' onClick={() => { commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }} text='Open settings' />
|
||||
</div>
|
||||
}
|
||||
</ScrollToBottomContainer>
|
||||
|
|
@ -863,8 +873,8 @@ 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
|
||||
formRef={formRef}
|
||||
<VoidChatArea
|
||||
divRef={chatAreaRef}
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onAbort}
|
||||
isStreaming={isStreaming}
|
||||
|
|
@ -873,7 +883,7 @@ export const SidebarChat = () => {
|
|||
showProspectiveSelections={prevMessagesHTML.length === 0}
|
||||
staging={staging}
|
||||
setStaging={setStaging}
|
||||
// onSelectionsChange={chatThreadsService.setStagingSelections.bind(chatThreadsService)}
|
||||
onClickAnywhere={() => { textAreaRef.current?.focus() }}
|
||||
featureName="Ctrl+L"
|
||||
>
|
||||
<VoidInputBox2
|
||||
|
|
@ -886,7 +896,7 @@ export const SidebarChat = () => {
|
|||
fnsRef={textAreaFnsRef}
|
||||
multiline={true}
|
||||
/>
|
||||
</VoidInputForm>
|
||||
</VoidChatArea>
|
||||
</div>
|
||||
|
||||
return <div ref={sidebarRef} className={`w-full h-full`}>
|
||||
|
|
|
|||
|
|
@ -303,9 +303,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,
|
||||
|
|
@ -316,7 +316,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;
|
||||
|
|
@ -375,14 +375,12 @@ export const VoidCustomSelectBox = <T extends any>({
|
|||
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(() => {
|
||||
|
|
@ -409,6 +407,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,13 +4,14 @@
|
|||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
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'
|
||||
import { ModelOption } from '../../../../../../../platform/void/common/voidSettingsService.js'
|
||||
import { WarningBox } from './WarningBox.js'
|
||||
|
||||
const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
|
||||
if (m1.length !== m2.length) return false
|
||||
|
|
@ -25,13 +26,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,10 +74,13 @@ const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], feat
|
|||
// />
|
||||
// }
|
||||
|
||||
const MemoizedModelSelectBox = ({ featureName }: { featureName: FeatureName }) => {
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -90,46 +94,22 @@ const MemoizedModelSelectBox = ({ featureName }: { featureName: FeatureName }) =
|
|||
|
||||
}
|
||||
|
||||
export const WarningBox = ({ text, onClick, className }: { text: string; onClick?: () => void; className?: string }) => {
|
||||
|
||||
return <div
|
||||
className={`
|
||||
text-void-warning brightness-90 opacity-90
|
||||
text-xs text-ellipsis
|
||||
${onClick ? `hover:brightness-75 transition-all duration-200 cursor-pointer` : ''}
|
||||
flex items-center flex-nowrap
|
||||
${className}
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<IconWarning
|
||||
size={14}
|
||||
className='mr-1'
|
||||
/>
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
// return <VoidSelectBox
|
||||
// options={[{ text: 'Please add a model!', value: null }]}
|
||||
// onChangeSelection={() => { }}
|
||||
// />
|
||||
}
|
||||
|
||||
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 a model'
|
||||
: isDisabled === 'addModel' ? 'Add a model'
|
||||
: (isDisabled === 'addProvider' || isDisabled === 'notFilledIn' || isDisabled === 'providerNotAutoDetected') ? 'Provider required'
|
||||
: 'Provider required'
|
||||
} />
|
||||
|
||||
return <MemoizedModelDropdown featureName={featureName} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,18 @@
|
|||
|
||||
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'
|
||||
import { isWindows, isLinux, isMacintosh } from '../../../../../../../base/common/platform.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { env } from '../../../../../../../base/common/process.js'
|
||||
import { WarningBox, ModelDropdown } from './ModelDropdown.js'
|
||||
import { ModelDropdown } from './ModelDropdown.js'
|
||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
||||
import { WarningBox } from './WarningBox.js'
|
||||
|
||||
const SubtleButton = ({ onClick, text, icon, disabled }: { onClick: () => void, text: string, icon: React.ReactNode, disabled: boolean }) => {
|
||||
|
||||
|
|
@ -79,7 +80,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 +113,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 +200,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 +224,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 +260,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 +283,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 +300,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 +334,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 >
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { IconWarning } from '../sidebar-tsx/SidebarChat.js';
|
||||
|
||||
|
||||
export const WarningBox = ({ text, onClick, className }: { text: string; onClick?: () => void; className?: string }) => {
|
||||
|
||||
return <div
|
||||
className={`
|
||||
text-void-warning brightness-90 opacity-90 w-fit
|
||||
text-xs text-ellipsis
|
||||
${onClick ? `hover:brightness-75 transition-all duration-200 cursor-pointer` : ''}
|
||||
flex items-center flex-nowrap
|
||||
${className}
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<IconWarning
|
||||
size={14}
|
||||
className='mr-1'
|
||||
/>
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
// return <VoidSelectBox
|
||||
// options={[{ text: 'Please add a model!', value: null }]}
|
||||
// onChangeSelection={() => { }}
|
||||
// />
|
||||
}
|
||||
|
|
@ -140,13 +140,12 @@ registerAction2(class extends Action2 {
|
|||
const setSelections = (s: StagingSelectionItem[]) => setStaging({ ...staging, selections: s })
|
||||
|
||||
// if matches with existing selection, overwrite (since text may change)
|
||||
const currentStagingEltIdx = findMatchingStagingIndex(selections, selection)
|
||||
|
||||
if (currentStagingEltIdx !== undefined && currentStagingEltIdx !== -1) {
|
||||
const matchingStagingEltIdx = findMatchingStagingIndex(selections, selection)
|
||||
if (matchingStagingEltIdx !== undefined && matchingStagingEltIdx !== -1) {
|
||||
setSelections([
|
||||
...selections!.slice(0, currentStagingEltIdx),
|
||||
...selections!.slice(0, matchingStagingEltIdx),
|
||||
selection,
|
||||
...selections!.slice(currentStagingEltIdx + 1, Infinity)
|
||||
...selections!.slice(matchingStagingEltIdx + 1, Infinity)
|
||||
])
|
||||
}
|
||||
// if no match, add it
|
||||
|
|
|
|||
Loading…
Reference in a new issue