diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml
index 6f633cbb8f0..dcd1ba5696d 100644
--- a/.github/workflows/dev-build.yaml
+++ b/.github/workflows/dev-build.yaml
@@ -6,7 +6,7 @@ concurrency:
on:
push:
- branches: ['model-map-staleness'] # put your current branch to create a build. Core team only.
+ branches: ['2095-model-swap-in-chat'] # put your current branch to create a build. Core team only.
paths-ignore:
- '**.md'
- 'cloud-deployments/*'
diff --git a/frontend/src/components/ModalWrapper/index.jsx b/frontend/src/components/ModalWrapper/index.jsx
index 7c45c22c5ab..ac6b8ea1bb1 100644
--- a/frontend/src/components/ModalWrapper/index.jsx
+++ b/frontend/src/components/ModalWrapper/index.jsx
@@ -9,8 +9,11 @@ import { createPortal } from "react-dom";
*/
/**
+ *
* @param {ModalWrapperProps} props - ModalWrapperProps to pass
* @returns {import("react").ReactNode}
+ *
+ * @todo Add a closeModal prop to the ModalWrapper component so we can escape dismiss anywhere this is used
*/
export default function ModalWrapper({ children, isOpen, noPortal = false }) {
if (!isOpen) return null;
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/ChatModelSelection/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/ChatModelSelection/index.jsx
new file mode 100644
index 00000000000..d05689748fe
--- /dev/null
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/ChatModelSelection/index.jsx
@@ -0,0 +1,120 @@
+import useGetProviderModels, {
+ DISABLED_PROVIDERS,
+} from "@/hooks/useGetProvidersModels";
+import { useTranslation } from "react-i18next";
+
+export default function ChatModelSelection({
+ provider,
+ setHasChanges,
+ selectedLLMModel,
+ setSelectedLLMModel,
+}) {
+ const { defaultModels, customModels, loading } =
+ useGetProviderModels(provider);
+ const { t } = useTranslation();
+ if (DISABLED_PROVIDERS.includes(provider)) return null;
+
+ if (loading) {
+ return (
+
closeSetupProviderModal()}
+ settings={config.settings}
+ llmProvider={config.provider}
+ />
+ >
+ );
+}
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/index.jsx
new file mode 100644
index 00000000000..a09fcc0cd9d
--- /dev/null
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/index.jsx
@@ -0,0 +1,151 @@
+import { useState, useEffect } from "react";
+import { useParams } from "react-router-dom";
+import PreLoader from "@/components/Preloader";
+import ChatModelSelection from "./ChatModelSelection";
+import { useTranslation } from "react-i18next";
+import { PROVIDER_SETUP_EVENT, SAVE_LLM_SELECTOR_EVENT } from "./action";
+import {
+ WORKSPACE_LLM_PROVIDERS,
+ autoScrollToSelectedLLMProvider,
+ hasMissingCredentials,
+ validatedModelSelection,
+} from "./utils";
+import LLMSelectorSidePanel from "./LLMSelector";
+import { NoSetupWarning } from "./SetupProvider";
+import showToast from "@/utils/toast";
+import Workspace from "@/models/workspace";
+import System from "@/models/system";
+
+export default function LLMSelectorModal() {
+ const { slug } = useParams();
+ const { t } = useTranslation();
+ const [loading, setLoading] = useState(false);
+ const [settings, setSettings] = useState(null);
+ const [selectedLLMProvider, setSelectedLLMProvider] = useState(null);
+ const [selectedLLMModel, setSelectedLLMModel] = useState("");
+ const [availableProviders, setAvailableProviders] = useState(
+ WORKSPACE_LLM_PROVIDERS
+ );
+ const [hasChanges, setHasChanges] = useState(false);
+ const [saving, setSaving] = useState(false);
+ const [missingCredentials, setMissingCredentials] = useState(false);
+
+ useEffect(() => {
+ if (!slug) return;
+ setLoading(true);
+ Promise.all([Workspace.bySlug(slug), System.keys()])
+ .then(([workspace, systemSettings]) => {
+ const selectedLLMProvider =
+ workspace.chatProvider ?? systemSettings.LLMProvider;
+ const selectedLLMModel = workspace.chatModel ?? systemSettings.LLMModel;
+
+ setSettings(systemSettings);
+ setSelectedLLMProvider(selectedLLMProvider);
+ autoScrollToSelectedLLMProvider(selectedLLMProvider);
+ setSelectedLLMModel(selectedLLMModel);
+ })
+ .finally(() => setLoading(false));
+ }, [slug]);
+
+ function handleSearch(e) {
+ const searchTerm = e.target.value.toLowerCase();
+ const filteredProviders = WORKSPACE_LLM_PROVIDERS.filter((provider) =>
+ provider.name.toLowerCase().includes(searchTerm)
+ );
+ setAvailableProviders(filteredProviders);
+ }
+
+ function handleProviderSelection(provider) {
+ setSelectedLLMProvider(provider);
+ setAvailableProviders(WORKSPACE_LLM_PROVIDERS);
+ autoScrollToSelectedLLMProvider(provider, 50);
+ document.getElementById("llm-search-input").value = "";
+ setHasChanges(true);
+ setMissingCredentials(hasMissingCredentials(settings, provider));
+ }
+
+ async function handleSave() {
+ setSaving(true);
+ try {
+ setHasChanges(false);
+ const validatedModel = validatedModelSelection(selectedLLMModel);
+ if (!validatedModel) throw new Error("Invalid model selection");
+
+ const { message } = await Workspace.update(slug, {
+ chatProvider: selectedLLMProvider,
+ chatModel: validatedModel,
+ });
+
+ if (!!message) throw new Error(message);
+ window.dispatchEvent(new Event(SAVE_LLM_SELECTOR_EVENT));
+ } catch (error) {
+ console.error(error);
+ showToast(error.message, "error", { clear: true });
+ } finally {
+ setSaving(false);
+ }
+ }
+
+ if (loading) {
+ return (
+
+
+
+ {t("chat_window.workspace_llm_manager.loading_workspace_settings")}
+
+
+ );
+ }
+
+ return (
+
+
+
+ {
+ window.dispatchEvent(
+ new CustomEvent(PROVIDER_SETUP_EVENT, {
+ detail: {
+ provider: WORKSPACE_LLM_PROVIDERS.find(
+ (p) => p.value === selectedLLMProvider
+ ),
+ settings,
+ },
+ })
+ );
+ }}
+ />
+
+ {hasChanges && (
+
+ {saving
+ ? t("chat_window.workspace_llm_manager.saving")
+ : t("chat_window.workspace_llm_manager.save")}
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/utils.js b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/utils.js
new file mode 100644
index 00000000000..b03e1b40a91
--- /dev/null
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/utils.js
@@ -0,0 +1,61 @@
+import { AVAILABLE_LLM_PROVIDERS } from "@/pages/GeneralSettings/LLMPreference";
+import { DISABLED_PROVIDERS } from "@/hooks/useGetProvidersModels";
+
+export function autoScrollToSelectedLLMProvider(
+ selectedLLMProvider,
+ timeout = 500
+) {
+ setTimeout(() => {
+ const selectedButton = document.querySelector(
+ `[data-llm-value="${selectedLLMProvider}"]`
+ );
+ if (!selectedButton) return;
+ selectedButton.scrollIntoView({ behavior: "smooth", block: "nearest" });
+ }, timeout);
+}
+
+/**
+ * Validates the model selection by checking if the model is in the select option in the available models
+ * dropdown. If the model is not in the dropdown, it will return the first model in the dropdown.
+ *
+ * This exists when the user swaps providers, but did not select a model in the new provider's dropdown
+ * and assumed the first model in the picker was OK. This prevents invalid provider<>model selection issues
+ * @param {string} model - The model to validate
+ * @returns {string} - The validated model
+ */
+export function validatedModelSelection(model) {
+ try {
+ // If the entire select element is not found, return the model as is and cross our fingers
+ const selectOption = document.getElementById(`workspace-llm-model-select`);
+ if (!selectOption) return model;
+
+ // If the model is not in the dropdown, return the first model in the dropdown
+ // to prevent invalid provider<>model selection issues
+ const selectedOption = selectOption.querySelector(
+ `option[value="${model}"]`
+ );
+ if (!selectedOption) return selectOption.querySelector(`option`).value;
+
+ // If the model is in the dropdown, return the model as is
+ return model;
+ } catch (error) {
+ return null; // If the dropdown was empty or something else went wrong, return null to abort the save
+ }
+}
+
+export function hasMissingCredentials(settings, provider) {
+ const providerEntry = AVAILABLE_LLM_PROVIDERS.find(
+ (p) => p.value === provider
+ );
+ if (!providerEntry) return false;
+
+ for (const requiredKey of providerEntry.requiredConfig) {
+ if (!settings.hasOwnProperty(requiredKey)) return true;
+ if (!settings[requiredKey]) return true;
+ }
+ return false;
+}
+
+export const WORKSPACE_LLM_PROVIDERS = AVAILABLE_LLM_PROVIDERS.filter(
+ (provider) => !DISABLED_PROVIDERS.includes(provider.value)
+);
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx
index 14c1b173479..8a441293041 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx
@@ -11,6 +11,7 @@ import AvailableAgentsButton, {
useAvailableAgents,
} from "./AgentMenu";
import TextSizeButton from "./TextSizeMenu";
+import LLMSelectorAction from "./LLMSelector/action";
import SpeechToText from "./SpeechToText";
import { Tooltip } from "react-tooltip";
import AttachmentManager from "./Attachments";
@@ -323,6 +324,7 @@ export default function PromptInput({
setShowAgents={setShowAgents}
/>
+
diff --git a/frontend/src/hooks/useGetProvidersModels.js b/frontend/src/hooks/useGetProvidersModels.js
index ad12da8bf29..82ef427cfd5 100644
--- a/frontend/src/hooks/useGetProvidersModels.js
+++ b/frontend/src/hooks/useGetProvidersModels.js
@@ -61,6 +61,7 @@ export default function useGetProviderModels(provider = null) {
useEffect(() => {
async function fetchProviderModels() {
if (!provider) return;
+ setLoading(true);
const { models = [] } = await System.customModels(provider);
if (
PROVIDER_DEFAULT_MODELS.hasOwnProperty(provider) &&
diff --git a/frontend/src/locales/ar/common.js b/frontend/src/locales/ar/common.js
index 315d81bc29f..71ed7092a3a 100644
--- a/frontend/src/locales/ar/common.js
+++ b/frontend/src/locales/ar/common.js
@@ -710,6 +710,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -913,6 +923,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/da/common.js b/frontend/src/locales/da/common.js
index 30fbf86552d..42a0c09a8a4 100644
--- a/frontend/src/locales/da/common.js
+++ b/frontend/src/locales/da/common.js
@@ -748,6 +748,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: "Rediger konto",
@@ -952,6 +962,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/de/common.js b/frontend/src/locales/de/common.js
index 23435faff0e..77b1a0aa1ac 100644
--- a/frontend/src/locales/de/common.js
+++ b/frontend/src/locales/de/common.js
@@ -746,6 +746,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: "Account bearbeiten",
@@ -957,6 +967,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js
index 422ab91520f..b6693a79196 100644
--- a/frontend/src/locales/en/common.js
+++ b/frontend/src/locales/en/common.js
@@ -978,6 +978,16 @@ const TRANSLATIONS = {
small: "Small",
normal: "Normal",
large: "Large",
+ workspace_llm_manager: {
+ search: "Search LLM providers",
+ loading_workspace_settings: "Loading workspace settings...",
+ available_models: "Available Models for {{provider}}",
+ available_models_description: "Select a model to use for this workspace.",
+ save: "Use this model",
+ saving: "Setting model as workspace default...",
+ missing_credentials: "This provider is missing credentials!",
+ missing_credentials_description: "Click to set up credentials",
+ },
},
profile_settings: {
@@ -1014,6 +1024,7 @@ const TRANSLATIONS = {
llmPreferences: "LLM Preferences",
chatSettings: "Chat Settings",
help: "Show keyboard shortcuts help",
+ showLLMSelector: "Show workspace LLM Selector",
},
},
};
diff --git a/frontend/src/locales/es/common.js b/frontend/src/locales/es/common.js
index 21e1afb0e8c..bb72a433d13 100644
--- a/frontend/src/locales/es/common.js
+++ b/frontend/src/locales/es/common.js
@@ -709,6 +709,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -917,6 +927,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/fa/common.js b/frontend/src/locales/fa/common.js
index 401da20dd0a..08599498c3e 100644
--- a/frontend/src/locales/fa/common.js
+++ b/frontend/src/locales/fa/common.js
@@ -702,6 +702,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -905,6 +915,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/fr/common.js b/frontend/src/locales/fr/common.js
index f66e53fc24b..cf43f2e9627 100644
--- a/frontend/src/locales/fr/common.js
+++ b/frontend/src/locales/fr/common.js
@@ -710,6 +710,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -913,6 +923,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/he/common.js b/frontend/src/locales/he/common.js
index 0fda5628f38..bcb2ab5f969 100644
--- a/frontend/src/locales/he/common.js
+++ b/frontend/src/locales/he/common.js
@@ -695,6 +695,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -898,6 +908,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/it/common.js b/frontend/src/locales/it/common.js
index f59be7f804f..bac64617ec1 100644
--- a/frontend/src/locales/it/common.js
+++ b/frontend/src/locales/it/common.js
@@ -708,6 +708,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -911,6 +921,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/ja/common.js b/frontend/src/locales/ja/common.js
index c8c2df458e9..e6766c3ffc9 100644
--- a/frontend/src/locales/ja/common.js
+++ b/frontend/src/locales/ja/common.js
@@ -740,6 +740,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: "アカウントを編集",
@@ -947,6 +957,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/ko/common.js b/frontend/src/locales/ko/common.js
index ab85f557167..30ef7fa528d 100644
--- a/frontend/src/locales/ko/common.js
+++ b/frontend/src/locales/ko/common.js
@@ -695,6 +695,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -898,6 +908,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/lv/common.js b/frontend/src/locales/lv/common.js
index bca0cb8654e..aaab2ffaeea 100644
--- a/frontend/src/locales/lv/common.js
+++ b/frontend/src/locales/lv/common.js
@@ -935,6 +935,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: "Rediģēt kontu",
@@ -969,6 +979,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/nl/common.js b/frontend/src/locales/nl/common.js
index b4c9625fa0a..fec9e96d7dd 100644
--- a/frontend/src/locales/nl/common.js
+++ b/frontend/src/locales/nl/common.js
@@ -705,6 +705,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -908,6 +918,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/pt_BR/common.js b/frontend/src/locales/pt_BR/common.js
index d2ad4f0ba92..343b9d27a28 100644
--- a/frontend/src/locales/pt_BR/common.js
+++ b/frontend/src/locales/pt_BR/common.js
@@ -916,6 +916,16 @@ const TRANSLATIONS = {
small: "Pequeno",
normal: "Normal",
large: "Grande",
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: "Editar conta",
@@ -950,6 +960,7 @@ const TRANSLATIONS = {
llmPreferences: "Preferências do LLM",
chatSettings: "Ajustes do chat",
help: "Exibe ajuda e atalhos",
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/ru/common.js b/frontend/src/locales/ru/common.js
index 84e247abbc2..fa80dbd13fd 100644
--- a/frontend/src/locales/ru/common.js
+++ b/frontend/src/locales/ru/common.js
@@ -749,6 +749,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: "Редактировать учётную запись",
@@ -953,6 +963,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/tr/common.js b/frontend/src/locales/tr/common.js
index 784aa829168..cf94d5308ee 100644
--- a/frontend/src/locales/tr/common.js
+++ b/frontend/src/locales/tr/common.js
@@ -705,6 +705,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -908,6 +918,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/vn/common.js b/frontend/src/locales/vn/common.js
index ed01dd8f3b4..a92e3f23fd1 100644
--- a/frontend/src/locales/vn/common.js
+++ b/frontend/src/locales/vn/common.js
@@ -704,6 +704,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -907,6 +917,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/zh/common.js b/frontend/src/locales/zh/common.js
index 9bdf04f32d0..dd20b892fce 100644
--- a/frontend/src/locales/zh/common.js
+++ b/frontend/src/locales/zh/common.js
@@ -875,6 +875,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: "编辑帐户",
@@ -909,6 +919,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/locales/zh_TW/common.js b/frontend/src/locales/zh_TW/common.js
index 721f33697f2..2abd8567e67 100644
--- a/frontend/src/locales/zh_TW/common.js
+++ b/frontend/src/locales/zh_TW/common.js
@@ -707,6 +707,16 @@ const TRANSLATIONS = {
small: null,
normal: null,
large: null,
+ workspace_llm_manager: {
+ search: null,
+ loading_workspace_settings: null,
+ available_models: null,
+ available_models_description: null,
+ save: null,
+ saving: null,
+ missing_credentials: null,
+ missing_credentials_description: null,
+ },
},
profile_settings: {
edit_account: null,
@@ -910,6 +920,7 @@ const TRANSLATIONS = {
llmPreferences: null,
chatSettings: null,
help: null,
+ showLLMSelector: null,
},
},
};
diff --git a/frontend/src/utils/keyboardShortcuts.js b/frontend/src/utils/keyboardShortcuts.js
index 024afc4db52..75da08926e0 100644
--- a/frontend/src/utils/keyboardShortcuts.js
+++ b/frontend/src/utils/keyboardShortcuts.js
@@ -1,6 +1,7 @@
import paths from "./paths";
-import { useEffect, useState } from "react";
+import { useEffect } from "react";
import { userFromStorage } from "./request";
+import { TOGGLE_LLM_SELECTOR_EVENT } from "@/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/action";
export const KEYBOARD_SHORTCUTS_HELP_EVENT = "keyboard-shortcuts-help";
export const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
@@ -61,6 +62,12 @@ export const SHORTCUTS = {
);
},
},
+ "⌘ + Shift + L": {
+ translationKey: "showLLMSelector",
+ action: () => {
+ window.dispatchEvent(new Event(TOGGLE_LLM_SELECTOR_EVENT));
+ },
+ },
};
const LISTENERS = {};
diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js
index 89e397ea64e..a7a3e752c6e 100644
--- a/server/models/systemSettings.js
+++ b/server/models/systemSettings.js
@@ -8,6 +8,7 @@ const prisma = require("../utils/prisma");
const { v4 } = require("uuid");
const { MetaGenerator } = require("../utils/boot/MetaGenerator");
const { PGVector } = require("../utils/vectorDbProviders/pgvector");
+const { getBaseLLMProviderModel } = require("../utils/helpers");
function isNullOrNaN(value) {
if (value === null) return true;
@@ -227,6 +228,7 @@ const SystemSettings = {
// LLM Provider Selection Settings & Configs
// --------------------------------------------------------
LLMProvider: llmProvider,
+ LLMModel: getBaseLLMProviderModel({ provider: llmProvider }) || null,
...this.llmPreferenceKeys(),
// --------------------------------------------------------
diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js
index a069b0dd3ca..2017c618fac 100644
--- a/server/utils/helpers/index.js
+++ b/server/utils/helpers/index.js
@@ -358,6 +358,72 @@ function getLLMProviderClass({ provider = null } = {}) {
}
}
+/**
+ * Returns the defined model (if available) for the given provider.
+ * @param {{provider: string | null} | null} params - Initialize params for LLMs provider
+ * @returns {string | null}
+ */
+function getBaseLLMProviderModel({ provider = null } = {}) {
+ switch (provider) {
+ case "openai":
+ return process.env.OPEN_MODEL_PREF;
+ case "azure":
+ return process.env.OPEN_MODEL_PREF;
+ case "anthropic":
+ return process.env.ANTHROPIC_MODEL_PREF;
+ case "gemini":
+ return process.env.GEMINI_LLM_MODEL_PREF;
+ case "lmstudio":
+ return process.env.LMSTUDIO_MODEL_PREF;
+ case "localai":
+ return process.env.LOCAL_AI_MODEL_PREF;
+ case "ollama":
+ return process.env.OLLAMA_MODEL_PREF;
+ case "togetherai":
+ return process.env.TOGETHER_AI_MODEL_PREF;
+ case "fireworksai":
+ return process.env.FIREWORKS_AI_LLM_MODEL_PREF;
+ case "perplexity":
+ return process.env.PERPLEXITY_MODEL_PREF;
+ case "openrouter":
+ return process.env.OPENROUTER_MODEL_PREF;
+ case "mistral":
+ return process.env.MISTRAL_MODEL_PREF;
+ case "huggingface":
+ return null;
+ case "groq":
+ return process.env.GROQ_MODEL_PREF;
+ case "koboldcpp":
+ return process.env.KOBOLD_CPP_MODEL_PREF;
+ case "textgenwebui":
+ return process.env.TEXT_GEN_WEB_UI_API_KEY;
+ case "cohere":
+ return process.env.COHERE_MODEL_PREF;
+ case "litellm":
+ return process.env.LITE_LLM_MODEL_PREF;
+ case "generic-openai":
+ return process.env.GENERIC_OPEN_AI_EMBEDDING_API_KEY;
+ case "bedrock":
+ return process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE;
+ case "deepseek":
+ return process.env.DEEPSEEK_MODEL_PREF;
+ case "apipie":
+ return process.env.APIPIE_LLM_API_KEY;
+ case "novita":
+ return process.env.NOVITA_LLM_MODEL_PREF;
+ case "xai":
+ return process.env.XAI_LLM_MODEL_PREF;
+ case "nvidia-nim":
+ return process.env.NVIDIA_NIM_LLM_MODEL_PREF;
+ case "ppio":
+ return process.env.PPIO_API_KEY;
+ case "dpais":
+ return process.env.DPAIS_LLM_MODEL_PREF;
+ default:
+ return null;
+ }
+}
+
// Some models have lower restrictions on chars that can be encoded in a single pass
// and by default we assume it can handle 1,000 chars, but some models use work with smaller
// chars so here we can override that value when embedding information.
@@ -383,6 +449,7 @@ module.exports = {
maximumChunkLength,
getVectorDbClass,
getLLMProviderClass,
+ getBaseLLMProviderModel,
getLLMProvider,
toChunks,
};