diff --git a/src/components/Settings/Sections/Models/DeleteProviderDialog.tsx b/src/components/Settings/Sections/Models/DeleteProviderDialog.tsx index a33d383..e05a449 100644 --- a/src/components/Settings/Sections/Models/DeleteProviderDialog.tsx +++ b/src/components/Settings/Sections/Models/DeleteProviderDialog.tsx @@ -2,118 +2,117 @@ import { Dialog, DialogPanel } from '@headlessui/react'; import { Loader2, Trash2 } from 'lucide-react'; import { useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; -import { - ConfigModelProvider, -} from '@/lib/config/types'; +import { ConfigModelProvider } from '@/lib/config/types'; import { toast } from 'sonner'; const DeleteProvider = ({ - modelProvider, - setProviders, + modelProvider, + setProviders, }: { - modelProvider: ConfigModelProvider; - setProviders: React.Dispatch>; + modelProvider: ConfigModelProvider; + setProviders: React.Dispatch>; }) => { - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); - const handleDelete = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - try { - const res = await fetch(`/api/providers/${modelProvider.id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - }); + const handleDelete = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + const res = await fetch(`/api/providers/${modelProvider.id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); - if (!res.ok) { - throw new Error('Failed to delete provider'); - } + if (!res.ok) { + throw new Error('Failed to delete provider'); + } - setProviders((prev) => { - return prev.filter((p) => p.id !== modelProvider.id); - }); + setProviders((prev) => { + return prev.filter((p) => p.id !== modelProvider.id); + }); - toast.success('Provider deleted successfully.'); - } catch (error) { - console.error('Error deleting provider:', error); - toast.error('Failed to delete provider.'); - } finally { - setLoading(false); - } - }; + toast.success('Provider deleted successfully.'); + } catch (error) { + console.error('Error deleting provider:', error); + toast.error('Failed to delete provider.'); + } finally { + setLoading(false); + } + }; - return ( - <> - + + {open && ( + setOpen(false)} + className="relative z-[60]" + > + - - - - {open && ( - setOpen(false)} - className="relative z-[60]" - > - - -
-

- Delete provider -

-
-
-
-

- Are you sure you want to delete the provider "{modelProvider.name}"? This action cannot be undone. -

-
-
- - -
- - -
- )} -
- - ); + +
+

+ Delete provider +

+
+
+
+

+ Are you sure you want to delete the provider " + {modelProvider.name}"? This action cannot be undone. +

+
+
+ + +
+ + +
+ )} +
+ + ); }; export default DeleteProvider; diff --git a/src/components/Settings/Sections/Models/ModelProvider.tsx b/src/components/Settings/Sections/Models/ModelProvider.tsx index fecb5f3..0bc3b88 100644 --- a/src/components/Settings/Sections/Models/ModelProvider.tsx +++ b/src/components/Settings/Sections/Models/ModelProvider.tsx @@ -9,199 +9,209 @@ import UpdateProvider from './UpdateProviderDialog'; import DeleteProvider from './DeleteProviderDialog'; const ModelProvider = ({ - modelProvider, - setProviders, - fields, + modelProvider, + setProviders, + fields, }: { - modelProvider: ConfigModelProvider; - fields: UIConfigField[]; - setProviders: React.Dispatch>; + modelProvider: ConfigModelProvider; + fields: UIConfigField[]; + setProviders: React.Dispatch>; }) => { - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(false); - const handleModelDelete = async ( - type: 'chat' | 'embedding', - modelKey: string, - ) => { - try { - const res = await fetch(`/api/providers/${modelProvider.id}/models`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ key: modelKey, type: type }), - }); + const handleModelDelete = async ( + type: 'chat' | 'embedding', + modelKey: string, + ) => { + try { + const res = await fetch(`/api/providers/${modelProvider.id}/models`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ key: modelKey, type: type }), + }); - if (!res.ok) { - throw new Error('Failed to delete model: ' + (await res.text())); + if (!res.ok) { + throw new Error('Failed to delete model: ' + (await res.text())); + } + + setProviders( + (prev) => + prev.map((provider) => { + if (provider.id === modelProvider.id) { + return { + ...provider, + ...(type === 'chat' + ? { + chatModels: provider.chatModels.filter( + (m) => m.key !== modelKey, + ), + } + : { + embeddingModels: provider.embeddingModels.filter( + (m) => m.key !== modelKey, + ), + }), + }; } + return provider; + }) as ConfigModelProvider[], + ); - setProviders( - (prev) => - prev.map((provider) => { - if (provider.id === modelProvider.id) { - return { - ...provider, - ...(type === 'chat' - ? { - chatModels: provider.chatModels.filter( - (m) => m.key !== modelKey, - ), - } - : { - embeddingModels: provider.embeddingModels.filter( - (m) => m.key !== modelKey, - ), - }), - }; - } - return provider; - }) as ConfigModelProvider[], - ); + toast.success('Model deleted successfully.'); + } catch (err) { + console.error('Failed to delete model', err); + toast.error('Failed to delete model.'); + } + }; - toast.success('Model deleted successfully.'); - } catch (err) { - console.error('Failed to delete model', err); - toast.error('Failed to delete model.'); - } - }; - - return ( -
-
setOpen(!open)} - > -

- {modelProvider.name} -

-
-
- - -
- -
-
- - {open && ( - -
-
- {modelProvider.chatModels.length > 0 && ( -
-
-

- Chat models -

- -
-
- {modelProvider.chatModels.some((m) => m.key === 'error') ? ( -
- - - {modelProvider.chatModels.find((m) => m.key === 'error')?.name} - -
- ) : ( -
- {modelProvider.chatModels.map((model, index) => ( -
- {model.name} - -
- ))} -
- )} -
-
- )} - {modelProvider.embeddingModels.length > 0 && ( -
-
-

- Embedding models -

- -
-
- {modelProvider.embeddingModels.some((m) => m.key === 'error') ? ( -
- - - {modelProvider.embeddingModels.find((m) => m.key === 'error')?.name} - -
- ) : ( -
- {modelProvider.embeddingModels.map((model, index) => ( -
- {model.name} - -
- ))} -
- )} -
-
- )} -
- - )} - + return ( +
+
setOpen(!open)} + > +

+ {modelProvider.name} +

+
+
+ + +
+
- ); +
+ + {open && ( + +
+
+ {modelProvider.chatModels.length > 0 && ( +
+
+

+ Chat models +

+ +
+
+ {modelProvider.chatModels.some((m) => m.key === 'error') ? ( +
+ + + { + modelProvider.chatModels.find( + (m) => m.key === 'error', + )?.name + } + +
+ ) : ( +
+ {modelProvider.chatModels.map((model, index) => ( +
+ {model.name} + +
+ ))} +
+ )} +
+
+ )} + {modelProvider.embeddingModels.length > 0 && ( +
+
+

+ Embedding models +

+ +
+
+ {modelProvider.embeddingModels.some( + (m) => m.key === 'error', + ) ? ( +
+ + + { + modelProvider.embeddingModels.find( + (m) => m.key === 'error', + )?.name + } + +
+ ) : ( +
+ {modelProvider.embeddingModels.map((model, index) => ( +
+ {model.name} + +
+ ))} +
+ )} +
+
+ )} +
+ + )} + +
+ ); }; export default ModelProvider; diff --git a/src/lib/config/serverRegistry.ts b/src/lib/config/serverRegistry.ts index 9214e29..11420d3 100644 --- a/src/lib/config/serverRegistry.ts +++ b/src/lib/config/serverRegistry.ts @@ -11,4 +11,5 @@ export const getConfiguredModelProviderById = ( return getConfiguredModelProviders().find((p) => p.id === id) ?? undefined; }; -export const getSearxngURL = () => configManager.getConfig('search.searxngURL', '') +export const getSearxngURL = () => + configManager.getConfig('search.searxngURL', ''); diff --git a/src/lib/config/types.ts b/src/lib/config/types.ts index f4da00b..3d69bbc 100644 --- a/src/lib/config/types.ts +++ b/src/lib/config/types.ts @@ -17,7 +17,6 @@ type StringUIConfigField = BaseUIConfigField & { type SelectUIConfigFieldOptions = { name: string; - key: string; value: string; }; @@ -56,8 +55,8 @@ type Config = { }; modelProviders: ConfigModelProvider[]; search: { - [key: string]: any - } + [key: string]: any; + }; }; type EnvMap = { @@ -76,7 +75,7 @@ type ModelProviderUISection = { type UIConfigSections = { general: UIConfigField[]; modelProviders: ModelProviderUISection[]; - search: UIConfigField[]; + search: UIConfigField[]; }; export type { @@ -84,6 +83,8 @@ export type { Config, EnvMap, UIConfigSections, + SelectUIConfigField, + StringUIConfigField, ModelProviderUISection, ConfigModelProvider, }; diff --git a/src/lib/models/providers/index.ts b/src/lib/models/providers/index.ts index e98b9bd..8132ea4 100644 --- a/src/lib/models/providers/index.ts +++ b/src/lib/models/providers/index.ts @@ -5,7 +5,7 @@ import OllamaProvider from './ollama'; export const providers: Record> = { openai: OpenAIProvider, - ollama: OllamaProvider + ollama: OllamaProvider, }; export const getModelProvidersUIConfigSection = diff --git a/src/lib/models/providers/ollama.ts b/src/lib/models/providers/ollama.ts index b8b819f..8b03780 100644 --- a/src/lib/models/providers/ollama.ts +++ b/src/lib/models/providers/ollama.ts @@ -7,129 +7,131 @@ import { UIConfigField } from '@/lib/config/types'; import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry'; interface OllamaConfig { - baseURL: string; + baseURL: string; } const providerConfigFields: UIConfigField[] = [ - { - type: 'string', - name: 'Base URL', - key: 'baseURL', - description: 'The base URL for the Ollama', - required: true, - placeholder: 'Ollama Base URL', - default: process.env.DOCKER ? 'http://host.docker.internal:11434' : 'http://localhost:11434', - env: 'OLLAMA_BASE_URL', - scope: 'server', - }, + { + type: 'string', + name: 'Base URL', + key: 'baseURL', + description: 'The base URL for the Ollama', + required: true, + placeholder: 'Ollama Base URL', + default: process.env.DOCKER + ? 'http://host.docker.internal:11434' + : 'http://localhost:11434', + env: 'OLLAMA_BASE_URL', + scope: 'server', + }, ]; class OllamaProvider extends BaseModelProvider { - constructor(id: string, name: string, config: OllamaConfig) { - super(id, name, config); - } + constructor(id: string, name: string, config: OllamaConfig) { + super(id, name, config); + } - async getDefaultModels(): Promise { - try { - const res = await fetch(`${this.config.baseURL}/api/tags`, { - method: "GET", - headers: { - 'Content-type': 'application/json' - } - }) + async getDefaultModels(): Promise { + try { + const res = await fetch(`${this.config.baseURL}/api/tags`, { + method: 'GET', + headers: { + 'Content-type': 'application/json', + }, + }); - const data = await res.json() - - const models: Model[] = data.models.map((m: any) => { - return { - name: m.name, - key: m.model - } - }) - - return { - embedding: models, - chat: models, - }; - } catch (err) { - if (err instanceof TypeError) { - throw new Error('Error connecting to Ollama API. Please ensure the base URL is correct and the Ollama server is running.'); - } - - throw err; - } - } - - async getModelList(): Promise { - const defaultModels = await this.getDefaultModels(); - const configProvider = getConfiguredModelProviderById(this.id)!; + const data = await res.json(); + const models: Model[] = data.models.map((m: any) => { return { - embedding: [ - ...defaultModels.embedding, - ...configProvider.embeddingModels, - ], - chat: [...defaultModels.chat, ...configProvider.chatModels], + name: m.name, + key: m.model, }; + }); + + return { + embedding: models, + chat: models, + }; + } catch (err) { + if (err instanceof TypeError) { + throw new Error( + 'Error connecting to Ollama API. Please ensure the base URL is correct and the Ollama server is running.', + ); + } + + throw err; + } + } + + async getModelList(): Promise { + const defaultModels = await this.getDefaultModels(); + const configProvider = getConfiguredModelProviderById(this.id)!; + + return { + embedding: [ + ...defaultModels.embedding, + ...configProvider.embeddingModels, + ], + chat: [...defaultModels.chat, ...configProvider.chatModels], + }; + } + + async loadChatModel(key: string): Promise { + const modelList = await this.getModelList(); + + const exists = modelList.chat.find((m) => m.key === key); + + if (!exists) { + throw new Error( + 'Error Loading Ollama Chat Model. Invalid Model Selected', + ); } - async loadChatModel(key: string): Promise { - const modelList = await this.getModelList(); + return new ChatOllama({ + temperature: 0.7, + model: key, + baseUrl: this.config.baseURL, + }); + } - const exists = modelList.chat.find((m) => m.key === key); + async loadEmbeddingModel(key: string): Promise { + const modelList = await this.getModelList(); + const exists = modelList.embedding.find((m) => m.key === key); - if (!exists) { - throw new Error( - 'Error Loading Ollama Chat Model. Invalid Model Selected', - ); - } - - return new ChatOllama({ - temperature: 0.7, - model: key, - baseUrl: this.config.baseURL, - }); + if (!exists) { + throw new Error( + 'Error Loading Ollama Embedding Model. Invalid Model Selected.', + ); } - async loadEmbeddingModel(key: string): Promise { - const modelList = await this.getModelList(); - const exists = modelList.embedding.find((m) => m.key === key); + return new OllamaEmbeddings({ + model: key, + baseUrl: this.config.baseURL, + }); + } - if (!exists) { - throw new Error( - 'Error Loading Ollama Embedding Model. Invalid Model Selected.', - ); - } + static parseAndValidate(raw: any): OllamaConfig { + if (!raw || typeof raw !== 'object') + throw new Error('Invalid config provided. Expected object'); + if (!raw.baseURL) + throw new Error('Invalid config provided. Base URL must be provided'); - return new OllamaEmbeddings({ - model: key, - baseUrl: this.config.baseURL, - }); - } + return { + baseURL: String(raw.baseURL), + }; + } - static parseAndValidate(raw: any): OllamaConfig { - if (!raw || typeof raw !== 'object') - throw new Error('Invalid config provided. Expected object'); - if (!raw.baseURL) - throw new Error( - 'Invalid config provided. Base URL must be provided', - ); + static getProviderConfigFields(): UIConfigField[] { + return providerConfigFields; + } - return { - baseURL: String(raw.baseURL), - }; - } - - static getProviderConfigFields(): UIConfigField[] { - return providerConfigFields; - } - - static getProviderMetadata(): ProviderMetadata { - return { - key: 'ollama', - name: 'Ollama', - }; - } + static getProviderMetadata(): ProviderMetadata { + return { + key: 'ollama', + name: 'Ollama', + }; + } } export default OllamaProvider; diff --git a/src/lib/models/registry.ts b/src/lib/models/registry.ts index b44781a..5067b6d 100644 --- a/src/lib/models/registry.ts +++ b/src/lib/models/registry.ts @@ -67,7 +67,6 @@ class ModelRegistry { chatModels: m.chat, embeddingModels: m.embedding, }); - }), );