diff --git a/src/modules/llms/models-modal/ModelsModal.tsx b/src/modules/llms/models-modal/ModelsModal.tsx
index 2c44f1f99..8a5c8dda0 100644
--- a/src/modules/llms/models-modal/ModelsModal.tsx
+++ b/src/modules/llms/models-modal/ModelsModal.tsx
@@ -91,7 +91,7 @@ function ModelsConfiguratorModal(props: {
// start button
const startButton = React.useMemo(() => {
if (showWizard)
- return ;
+ return ;
// return ;
if (!isMultiServices)
return ;
diff --git a/src/modules/llms/models-modal/ModelsServiceSelector.tsx b/src/modules/llms/models-modal/ModelsServiceSelector.tsx
index 4f2496017..53360dbf9 100644
--- a/src/modules/llms/models-modal/ModelsServiceSelector.tsx
+++ b/src/modules/llms/models-modal/ModelsServiceSelector.tsx
@@ -225,7 +225,7 @@ export function ModelsServiceSelector(props: {
) : (
-
+
} sx={{ borderColor: 'neutral.outlinedBorder' }}>
Add
diff --git a/src/modules/llms/models-modal/ModelsWizard.tsx b/src/modules/llms/models-modal/ModelsWizard.tsx
index d60377b37..c1d97bed5 100644
--- a/src/modules/llms/models-modal/ModelsWizard.tsx
+++ b/src/modules/llms/models-modal/ModelsWizard.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { useShallow } from 'zustand/react/shallow';
-import { Avatar, Badge, Box, Button, CircularProgress, Input, Sheet, Typography } from '@mui/joy';
+import { Avatar, Badge, Box, Button, Chip, CircularProgress, Input, Sheet, Typography } from '@mui/joy';
import { TooltipOutlined } from '~/common/components/TooltipOutlined';
import { llmsStoreState, useModelsStore } from '~/common/stores/llms/store-llms';
@@ -10,18 +10,33 @@ import { useShallowStabilizer } from '~/common/util/hooks/useShallowObject';
import type { IModelVendor } from '../vendors/IModelVendor';
import { ModelVendorAnthropic } from '../vendors/anthropic/anthropic.vendor';
import { ModelVendorGemini } from '../vendors/gemini/gemini.vendor';
+import { ModelVendorLMStudio } from '../vendors/lmstudio/lmstudio.vendor';
+import { ModelVendorLocalAI } from '../vendors/localai/localai.vendor';
+import { ModelVendorOllama } from '../vendors/ollama/ollama.vendor';
import { ModelVendorOpenAI } from '../vendors/openai/openai.vendor';
import { llmsUpdateModelsForServiceOrThrow } from '../llm.client';
// configuration
-const WizardVendors = [
- { vendor: ModelVendorOpenAI, apiKeyField: 'oaiKey' },
- { vendor: ModelVendorAnthropic, apiKeyField: 'anthropicKey' },
- { vendor: ModelVendorGemini, apiKeyField: 'geminiKey' },
- // { vendor: ModelVendorOpenRouter, apiKeyField: 'oaiKey' },
+const WizardProviders: ReadonlyArray = [
+ { cat: 'popular', vendor: ModelVendorOpenAI, settingsKey: 'oaiKey' } as const,
+ { cat: 'popular', vendor: ModelVendorAnthropic, settingsKey: 'anthropicKey' } as const,
+ { cat: 'popular', vendor: ModelVendorGemini, settingsKey: 'geminiKey' } as const,
+ { cat: 'local', vendor: ModelVendorLocalAI, settingsKey: 'localAIHost' } as const,
+ { cat: 'local', vendor: ModelVendorOllama, settingsKey: 'ollamaHost' } as const,
+ { cat: 'local', vendor: ModelVendorLMStudio, settingsKey: 'oaiHost', omit: true } as const,
+ // { vendor: ModelVendorOpenRouter, settingsKey: 'oaiKey' } as const,
] as const;
+type VendorCategory = 'popular' | 'local';
+
+interface WizardProvider {
+ cat: VendorCategory,
+ vendor: IModelVendor, Record>,
+ settingsKey: string,
+ omit?: boolean,
+}
+
const _styles = {
@@ -43,6 +58,13 @@ const _styles = {
gap: 0.25,
} as const,
+ text1Mobile: {
+ mb: 2,
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 0.25,
+ } as const,
+
text2: {
my: 1,
ml: 7.25,
@@ -50,63 +72,76 @@ const _styles = {
fontSize: 'sm',
} as const,
+ text2Mobile: {
+ mt: 2,
+ color: 'text.tertiary',
+ fontSize: 'sm',
+ } as const,
+
} as const;
function WizardProviderSetup(props: {
- apiKeyField: string,
+ provider: WizardProvider,
isFirst: boolean,
- vendor: IModelVendor, Record>,
+ isHidden: boolean,
}) {
- const { id: vendorId, name: vendorName, Icon: VendorIcon } = props.vendor;
+ const { cat: providerCat, vendor: providerVendor, settingsKey: providerSettingsKey, omit: providerOmit } = props.provider;
// state
- const [localKey, setLocalKey] = React.useState(null);
+ const [localValue, setLocalValue] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
const [updateError, setUpdateError] = React.useState(null);
// external state
const stabilizeTransportAccess = useShallowStabilizer>();
- const { serviceAPIKey, serviceLLMsCount } = useModelsStore(useShallow(({ llms, sources }) => {
+ const { serviceKeyValue, serviceLLMsCount } = useModelsStore(useShallow(({ llms, sources }) => {
// find the service | null
- const vendorService = sources.find(s => s.vId === vendorId) ?? null;
+ const vendorService = sources.find(s => s.vId === providerVendor.id) ?? null;
// (safe) service-derived properties
const serviceLLMsCount = !vendorService ? null : llms.filter(llm => llm.sId === vendorService.id).length;
- const serviceAccess = stabilizeTransportAccess(props.vendor.getTransportAccess(vendorService?.setup));
- const serviceAPIKey = !serviceAccess ? null : serviceAccess[props.apiKeyField] ?? null;
+ const serviceAccess = stabilizeTransportAccess(providerVendor.getTransportAccess(vendorService?.setup));
+ const serviceKeyValue = !serviceAccess ? null : vendorService?.setup[providerSettingsKey] ?? null;
return {
- serviceAPIKey,
+ serviceKeyValue,
serviceLLMsCount,
};
}));
// [effect] initialize the local key
+ const triggerValueLoad = localValue === null;
React.useEffect(() => {
- if (localKey === null)
- setLocalKey(serviceAPIKey || '');
- }, [localKey, serviceAPIKey]);
+ if (triggerValueLoad)
+ setLocalValue(serviceKeyValue || '');
+ }, [serviceKeyValue, triggerValueLoad]);
+
+
+ // derived
+ const isLocal = providerCat === 'local';
+ const valueName = isLocal ? 'server address' : 'API Key';
+ const { name: vendorName, Icon: VendorIcon } = providerVendor;
// handlers
const handleTextChanged = React.useCallback((e: React.ChangeEvent) => {
- setLocalKey((e.target as HTMLInputElement).value);
+ setLocalValue((e.target as HTMLInputElement).value);
}, []);
- const handleSetServiceKey = React.useCallback(async () => {
+ const handleSetServiceKeyValue = React.useCallback(async () => {
// create the service if missing
const { sources: llmsServices, createModelsService, updateServiceSettings, setLLMs } = llmsStoreState();
- const vendorService = llmsServices.find(s => s.vId === vendorId) || createModelsService(props.vendor);
+ const vendorService = llmsServices.find(s => s.vId === providerVendor.id) || createModelsService(providerVendor);
const vendorServiceId = vendorService.id;
// set the key
- const newKey = localKey?.trim() ?? '';
- updateServiceSettings(vendorServiceId, { [props.apiKeyField]: newKey });
+ const newKey = localValue?.trim() ?? '';
+ updateServiceSettings(vendorServiceId, { [providerSettingsKey]: newKey });
// if the key is empty, remove the models
if (!newKey) {
@@ -121,7 +156,7 @@ function WizardProviderSetup(props: {
try {
await llmsUpdateModelsForServiceOrThrow(vendorService.id, true);
} catch (error: any) {
- let errorText = error.message || 'An error occurred';
+ let errorText = error.message || `An error occurred. Please check your ${valueName}.`;
if (errorText.includes('Incorrect API key'))
errorText = '[OpenAI issue] Unauthorized: Incorrect API key.';
setUpdateError(errorText);
@@ -129,12 +164,12 @@ function WizardProviderSetup(props: {
}
setIsLoading(false);
- }, [localKey, props.apiKeyField, props.vendor, vendorId]);
+ }, [localValue, providerSettingsKey, providerVendor, valueName]);
// memoed components
- const endButtons = React.useMemo(() => ((localKey || '') === (serviceAPIKey || '')) ? null : (
+ const endButtons = React.useMemo(() => ((localValue || '') === (serviceKeyValue || '')) ? null : (
{/**/}
{/* */}
@@ -144,17 +179,26 @@ function WizardProviderSetup(props: {
{/**/}
}
>
- {!serviceAPIKey ? 'Confirm' : !localKey?.trim() ? 'Clear' : 'Update'}
+ {!serviceKeyValue ? 'Confirm' : !localValue?.trim() ? 'Clear' : 'Update'}
{/**/}
- ), [handleSetServiceKey, localKey, serviceAPIKey]);
+ ), [handleSetServiceKeyValue, localValue, serviceKeyValue]);
- return (
+ // heuristics for warnings
+ const isOnLocalhost = typeof window !== 'undefined' && window.location.hostname === 'localhost';
+
+ return props.isHidden ? null : providerOmit ? (
+
+ {!isOnLocalhost &&
+ Please make sure the addresses can be reached from "{typeof window !== 'undefined' ? window.location.hostname : 'this server'}". If you are using a local service, you may need to use a public URL.
+ }
+
+ ) : (
@@ -186,13 +230,13 @@ function WizardProviderSetup(props: {
{/* Line 2 */}
}
endDecorator={endButtons}
@@ -206,7 +250,7 @@ function WizardProviderSetup(props: {
{/*{!isLoading && !updateError && !!llmsCount && (*/}
{/* {llmsCount} models added.*/}
{/*)}*/}
- {!isLoading && !updateError && !serviceLLMsCount && !!serviceAPIKey && (
+ {!isLoading && !updateError && !serviceLLMsCount && !!serviceKeyValue && (
No models found.
)}
{!!updateError && {updateError}}
@@ -223,21 +267,24 @@ export function ModelsWizard(props: {
}) {
// state
- // const [category, setCategory] = React.useState<'popular' | 'local'>('popular');
+ const [activeCategory, setActiveCategory] = React.useState('popular');
+
+ // derived
+ const isLocal = activeCategory === 'local';
return (
-
-
- Enter API keys to connect your AI services.
- {/* setCategory('popular')}>*/}
- {/* popular*/}
- {/**/}
- {/* setCategory('popular')}>*/}
- {/* local*/}
- {/**/}
- {/*AI services.*/}
+
+
+ Enter {isLocal ? 'the addresses of ' : 'your API keys for '}
+ setActiveCategory('popular')}>
+ Popular
+
+ setActiveCategory('local')}>
+ Local
+
+ {' '}AI services below.
{/**/}
{/* Enter API keys to connect your AI services.{' '}*/}
@@ -245,13 +292,25 @@ export function ModelsWizard(props: {
{/**/}
- {WizardVendors.map(({ vendor, apiKeyField }, index) => (
-
+ {WizardProviders.map((provider, index) => (
+
))}
-
+
{/*{!props.isMobile && <>Switch to Advanced to choose between {getModelVendorsCount()} services.>}{' '}*/}
- {!props.isMobile && <>Switch to Advanced for more services,>}{' '}
+ {!props.isMobile && <>
+ Switch to{' '}
+ advanced configuration
+ {/**/}
+ {/* more services*/}
+ {/**/}
+ {' '}for more services,
+ >}{' '}
or skip for now and do it later.