mirror of
https://github.com/voideditor/void
synced 2026-05-22 17:08:25 +00:00
Enhance local provider integration and setup instructions
- Introduced separate handling for local providers: Ollama, MLX, and Apple Foundation Models. - Updated settings to include distinct sections for each local provider with relevant instructions and model filters. - Improved user interface for selecting local providers based on the operating system. - Refactored provider names to enhance clarity and maintainability in the codebase.
This commit is contained in:
parent
8c9b8e43ce
commit
81bd697dcb
3 changed files with 142 additions and 28 deletions
|
|
@ -6,9 +6,9 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useAccessor, useIsDark, useSettingsState } from '../util/services.js';
|
||||
import { Brain, Check, ChevronRight, DollarSign, ExternalLink, Lock, X } from 'lucide-react';
|
||||
import { displayInfoOfProviderName, ProviderName, providerNames, localProviderNames, featureNames, FeatureName, isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js';
|
||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
|
||||
import { OllamaSetupInstructions, OneClickSwitchButton, SettingsForProvider, ModelDump } from '../void-settings-tsx/Settings.js';
|
||||
import { displayInfoOfProviderName, ProviderName, providerNames, localProviderNames, ollamaProviderNames, mlxProviderNames, appleProviderNames, otherLocalProviderNames, featureNames, FeatureName, isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js';
|
||||
import { os } from '../../../../common/helpers/systemInfo.js';
|
||||
import { OllamaSetupInstructions, MlxSetupInstructions, AppleFoundationModelsSetupInstructions, OneClickSwitchButton, SettingsForProvider, ModelDump } from '../void-settings-tsx/Settings.js';
|
||||
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js';
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js';
|
||||
import { isLinux } from '../../../../../../../base/common/platform.js';
|
||||
|
|
@ -99,6 +99,22 @@ const tabNames = ['Free', 'Paid', 'Local'] as const;
|
|||
|
||||
type TabName = typeof tabNames[number] | 'Cloud/Other';
|
||||
|
||||
type LocalSubTab = 'ollama' | 'mlx' | 'apple' | 'other';
|
||||
|
||||
const localSubTabLabels: Record<LocalSubTab, string> = {
|
||||
ollama: 'Ollama',
|
||||
mlx: 'MLX',
|
||||
apple: 'apple',
|
||||
other: 'Other',
|
||||
};
|
||||
|
||||
const providerNamesOfLocalSubTab: Record<LocalSubTab, ProviderName[]> = {
|
||||
ollama: [...ollamaProviderNames],
|
||||
mlx: [...mlxProviderNames],
|
||||
apple: [...appleProviderNames],
|
||||
other: [...otherLocalProviderNames],
|
||||
};
|
||||
|
||||
// Data for cloud providers tab
|
||||
const cloudProviders: ProviderName[] = ['googleVertex', 'liteLLM', 'microsoftAzure', 'awsBedrock', 'openAICompatible'];
|
||||
|
||||
|
|
@ -128,6 +144,7 @@ const featureNameMap: { display: string, featureName: FeatureName }[] = [
|
|||
|
||||
const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setPageIndex: (index: number) => void }) => {
|
||||
const [currentTab, setCurrentTab] = useState<TabName>('Free');
|
||||
const [localSubTab, setLocalSubTab] = useState<LocalSubTab>('ollama');
|
||||
const settingsState = useSettingsState();
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -200,7 +217,24 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
|
|||
<div className="text-sm opacity-80 text-void-fg-3 my-4 w-full">{descriptionOfTab[currentTab]}</div>
|
||||
</div>
|
||||
|
||||
{providerNamesOfTab[currentTab].map((providerName) => (
|
||||
{currentTab === 'Local' && (
|
||||
<div className="flex gap-2 mb-6 w-full max-w-xl flex-wrap">
|
||||
{(os === 'mac' ? (['ollama', 'mlx', 'apple', 'other'] as const) : (['ollama', 'other'] as const)).map(sub => (
|
||||
<button
|
||||
key={sub}
|
||||
className={`py-1.5 px-3 rounded-md text-sm ${localSubTab === sub
|
||||
? 'bg-[#0e70c0]/80 text-white'
|
||||
: 'bg-void-bg-2 hover:bg-void-bg-2/80 text-void-fg-1'
|
||||
}`}
|
||||
onClick={() => setLocalSubTab(sub)}
|
||||
>
|
||||
{localSubTabLabels[sub]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(currentTab === 'Local' ? providerNamesOfLocalSubTab[localSubTab] : providerNamesOfTab[currentTab]).map((providerName) => (
|
||||
<div key={providerName} className="w-full max-w-xl mb-10">
|
||||
<div className="text-xl mb-2">
|
||||
Add {displayInfoOfProviderName(providerName).title}
|
||||
|
|
@ -226,6 +260,8 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
|
|||
|
||||
</div>
|
||||
{providerName === 'ollama' && <OllamaSetupInstructions />}
|
||||
{providerName === 'mlx' && os === 'mac' && <MlxSetupInstructions />}
|
||||
{providerName === 'appleFoundationModels' && os === 'mac' && <AppleFoundationModelsSetupInstructions />}
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
|
@ -239,7 +275,7 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
|
|||
<div className="text-sm opacity-80 text-void-fg-3 my-4 w-full">Local models should be detected automatically. You can add custom models below.</div>
|
||||
)}
|
||||
|
||||
{currentTab === 'Local' && <ModelDump filteredProviders={localProviderNames} />}
|
||||
{currentTab === 'Local' && <ModelDump filteredProviders={[...providerNamesOfLocalSubTab[localSubTab]]} />}
|
||||
{currentTab === 'Cloud/Other' && <ModelDump filteredProviders={cloudProviders} />}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; // Added useRef import just in case it was missed, though likely already present
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames, subTextMdOfProviderName } from '../../../../common/voidSettingsTypes.js'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, ollamaProviderNames, mlxProviderNames, appleProviderNames, otherLocalProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames, subTextMdOfProviderName } from '../../../../common/voidSettingsTypes.js'
|
||||
import { os } from '../../../../common/helpers/systemInfo.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js'
|
||||
|
|
@ -26,7 +26,10 @@ import { StorageScope, StorageTarget } from '../../../../../../../platform/stora
|
|||
|
||||
type Tab =
|
||||
| 'models'
|
||||
| 'localProviders'
|
||||
| 'ollama'
|
||||
| 'mlx'
|
||||
| 'apple'
|
||||
| 'localOther'
|
||||
| 'providers'
|
||||
| 'featureOptions'
|
||||
| 'mcp'
|
||||
|
|
@ -95,11 +98,11 @@ const RefreshModelButton = ({ providerName }: { providerName: RefreshableProvide
|
|||
/>
|
||||
}
|
||||
|
||||
const RefreshableModels = () => {
|
||||
const RefreshableModels = ({ providerNamesFilter }: { providerNamesFilter?: readonly ProviderName[] }) => {
|
||||
const settingsState = useSettingsState()
|
||||
const names = providerNamesFilter ?? refreshableProviderNames
|
||||
|
||||
|
||||
const buttons = refreshableProviderNames.map(providerName => {
|
||||
const buttons = names.map(providerName => {
|
||||
if (!settingsState.settingsOfProvider[providerName]._didFillInProviderSettings) return null
|
||||
return <RefreshModelButton key={providerName} providerName={providerName} />
|
||||
})
|
||||
|
|
@ -746,7 +749,7 @@ export const SettingsForProvider = ({ providerName, showProviderTitle, showProvi
|
|||
}
|
||||
|
||||
|
||||
export const VoidProviderSettings = ({ providerNames }: { providerNames: ProviderName[] }) => {
|
||||
export const VoidProviderSettings = ({ providerNames }: { providerNames: readonly ProviderName[] }) => {
|
||||
return <>
|
||||
{providerNames.map(providerName =>
|
||||
<SettingsForProvider key={providerName} providerName={providerName} showProviderTitle={true} showProviderSuggestions={true} />
|
||||
|
|
@ -754,6 +757,32 @@ export const VoidProviderSettings = ({ providerNames }: { providerNames: Provide
|
|||
</>
|
||||
}
|
||||
|
||||
const LocalProviderSection = ({ title, description, instructions, providerNames, refreshable, autoSetup, modelFilter }: {
|
||||
title: string
|
||||
description?: string
|
||||
instructions?: React.ReactNode
|
||||
providerNames: readonly ProviderName[]
|
||||
refreshable?: boolean
|
||||
autoSetup?: React.ReactNode
|
||||
modelFilter?: readonly ProviderName[]
|
||||
}) => (
|
||||
<div className='flex flex-col gap-4 mb-12'>
|
||||
<h2 className='text-3xl mb-1'>{title}</h2>
|
||||
{description && <h3 className='text-void-fg-3 mb-2'>{description}</h3>}
|
||||
{instructions && <div className='opacity-80 mb-2'>{instructions}</div>}
|
||||
{autoSetup}
|
||||
{refreshable && <RefreshableModels providerNamesFilter={providerNames} />}
|
||||
<VoidProviderSettings providerNames={providerNames} />
|
||||
{modelFilter && (
|
||||
<>
|
||||
<div className='w-full h-[1px] my-2' />
|
||||
<h3 className='text-lg text-void-fg-3 mb-2'>Models for {title}</h3>
|
||||
<ModelDump filteredProviders={[...modelFilter]} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
type TabName = 'models' | 'general'
|
||||
export const AutoDetectLocalModelsToggle = () => {
|
||||
|
|
@ -1110,7 +1139,12 @@ export const Settings = () => {
|
|||
|
||||
const navItems: { tab: Tab; label: string }[] = [
|
||||
{ tab: 'models', label: 'Models' },
|
||||
{ tab: 'localProviders', label: 'Local Providers' },
|
||||
{ tab: 'ollama', label: 'Ollama' },
|
||||
...(os === 'mac' ? ([
|
||||
{ tab: 'mlx' as const, label: 'MLX' },
|
||||
{ tab: 'apple' as const, label: 'apple' },
|
||||
]) : []),
|
||||
{ tab: 'localOther', label: 'Local (other)' },
|
||||
{ tab: 'providers', label: 'Main Providers' },
|
||||
{ tab: 'featureOptions', label: 'Feature Options' },
|
||||
{ tab: 'general', label: 'General' },
|
||||
|
|
@ -1254,27 +1288,65 @@ export const Settings = () => {
|
|||
<ModelDump />
|
||||
<div className='w-full h-[1px] my-4' />
|
||||
<AutoDetectLocalModelsToggle />
|
||||
<AutoSetupMlxToggle />
|
||||
<AutoSetupAppleFoundationModelsToggle />
|
||||
<RefreshableModels />
|
||||
<div className='w-full h-[1px] my-4' />
|
||||
<p className='text-void-fg-3 text-sm mb-4'>Per-provider setup and refresh are under <strong>Ollama</strong>, <strong>MLX</strong>, and <strong>apple</strong> in the sidebar.</p>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
{/* Local Providers section */}
|
||||
<div className={shouldShowTab('localProviders') ? `` : 'hidden'}>
|
||||
<div className={shouldShowTab('ollama') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<h2 className={`text-3xl mb-2`}>Local Providers</h2>
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
|
||||
<LocalProviderSection
|
||||
title='Ollama'
|
||||
description='Pull models with `ollama pull` — Void autodetects them at your endpoint.'
|
||||
instructions={<OllamaSetupInstructions sayWeAutoDetect={true} />}
|
||||
providerNames={ollamaProviderNames}
|
||||
refreshable
|
||||
modelFilter={ollamaProviderNames}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<div className='opacity-80 mb-4'>
|
||||
<OllamaSetupInstructions sayWeAutoDetect={true} />
|
||||
</div>
|
||||
<div className='opacity-80'>
|
||||
<MlxSetupInstructions />
|
||||
<AppleFoundationModelsSetupInstructions />
|
||||
</div>
|
||||
{os === 'mac' && (
|
||||
<div className={shouldShowTab('mlx') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<LocalProviderSection
|
||||
title='MLX'
|
||||
description='One loaded model at a time via mlx_lm.server (port 8080 by default).'
|
||||
instructions={<MlxSetupInstructions />}
|
||||
providerNames={mlxProviderNames}
|
||||
refreshable
|
||||
autoSetup={<AutoSetupMlxToggle />}
|
||||
modelFilter={mlxProviderNames}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<VoidProviderSettings providerNames={localProviderNames} />
|
||||
{os === 'mac' && (
|
||||
<div className={shouldShowTab('apple') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<LocalProviderSection
|
||||
title='apple'
|
||||
description='On-device Foundation model via maclocal-api (`afm`, port 9999).'
|
||||
instructions={<AppleFoundationModelsSetupInstructions />}
|
||||
providerNames={appleProviderNames}
|
||||
refreshable
|
||||
autoSetup={<AutoSetupAppleFoundationModelsToggle />}
|
||||
modelFilter={appleProviderNames}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={shouldShowTab('localOther') ? `` : 'hidden'}>
|
||||
<ErrorBoundary>
|
||||
<LocalProviderSection
|
||||
title='Local (other)'
|
||||
description='vLLM and LM Studio — OpenAI-compatible local servers.'
|
||||
providerNames={otherLocalProviderNames}
|
||||
refreshable
|
||||
modelFilter={otherLocalProviderNames}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ type UnionOfKeys<T> = T extends T ? keyof T : never;
|
|||
export type ProviderName = keyof typeof defaultProviderSettings
|
||||
export const providerNames = Object.keys(defaultProviderSettings) as ProviderName[]
|
||||
|
||||
export const localProviderNames = ['ollama', 'vLLM', 'lmStudio', 'mlx', 'appleFoundationModels'] satisfies ProviderName[] // all local names
|
||||
export const ollamaProviderNames = ['ollama'] as const satisfies ProviderName[]
|
||||
export const mlxProviderNames = ['mlx'] as const satisfies ProviderName[]
|
||||
export const appleProviderNames = ['appleFoundationModels'] as const satisfies ProviderName[]
|
||||
/** vLLM, LM Studio — separate from Ollama / MLX / apple */
|
||||
export const otherLocalProviderNames = ['vLLM', 'lmStudio'] as const satisfies ProviderName[]
|
||||
|
||||
export const localProviderNames = [...ollamaProviderNames, ...mlxProviderNames, ...appleProviderNames, ...otherLocalProviderNames] satisfies ProviderName[] // all local names
|
||||
export const nonlocalProviderNames = providerNames.filter((name) => !(localProviderNames as string[]).includes(name)) // all non-local names
|
||||
|
||||
type CustomSettingName = UnionOfKeys<typeof defaultProviderSettings[ProviderName]>
|
||||
|
|
|
|||
Loading…
Reference in a new issue