diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index b713b84307..6eb5d3f4b1 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -2,7 +2,7 @@ import time import logging import asyncio import sys - +import json from aiocache import cached from fastapi import Request @@ -34,6 +34,33 @@ log = logging.getLogger(__name__) log.setLevel(SRC_LOG_LEVELS["MAIN"]) + + +def translate_model_title(model_name, accept_language: str) -> str: + """ + model_name: either a dict like {"de":"test DE","en":"test EN","fr":"test","it":"test IT"} + or a JSON string representation of such a dict + accept_language: string from header, e.g., "en-US", "de-CH", "fr" + """ + # if model_name is a string, try to parse as JSON + if isinstance(model_name, str): + try: + model_name = json.loads(model_name) + except (json.JSONDecodeError, TypeError): + # if it's not valid JSON (plain string), return the string as is + return model_name + + + # Handle None or empty accept_language + if not accept_language: + accept_language = 'en' # default to English if not provided + + # normalize language code to primary subtag + lang = accept_language.split('-')[0] # "en-US" -> "en" + + # return the translation if available, else fallback to 'de' or any available + return model_name.get(lang) or model_name.get('de') or next(iter(model_name.values())) + async def fetch_ollama_models(request: Request, user: UserModel = None): raw_ollama_models = await ollama.get_all_models(request, user=user) return [ @@ -158,7 +185,7 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None) ] # Ollama may return model ids in different formats (e.g., 'llama3' vs. 'llama3:7b') ): if custom_model.is_active: - model["name"] = custom_model.name + model["name"] = translate_model_title(custom_model.name, request.headers.get("X-Language")), model["info"] = custom_model.model_dump() # Set action_ids and filter_ids @@ -209,7 +236,7 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None) models.append( { "id": f"{custom_model.id}", - "name": custom_model.name, + "name": translate_model_title(custom_model.name, request.headers.get("X-Language")), "object": "model", "created": custom_model.created_at, "owned_by": owned_by, diff --git a/src/hooks.client.ts b/src/hooks.client.ts new file mode 100644 index 0000000000..ac865d60be --- /dev/null +++ b/src/hooks.client.ts @@ -0,0 +1,33 @@ +import { get } from 'svelte/store'; +import { temporaryChatEnabled } from '$lib/stores'; +import { getI18nStore } from './lib/i18n'; + +export function customHeadersFetch() { + const originalFetch = window.fetch; + const i18nStore = getI18nStore(); + + window.fetch = async (input: RequestInfo | URL, init: RequestInit = {}) => { + // get current values + const i18n = get(i18nStore); + + // normalize headers to a plain object + const existingHeaders = + init.headers instanceof Headers + ? Object.fromEntries(init.headers.entries()) + : Array.isArray(init.headers) + ? Object.fromEntries(init.headers) + : { ...(init.headers || {}) }; + + const newHeaders: Record = { ...existingHeaders }; + + + if (i18n?.language) { + newHeaders['X-Language'] = i18n.language; + } + + return originalFetch(input, { + ...init, + headers: newHeaders, + }); + }; +} diff --git a/src/lib/apis/models/index.ts b/src/lib/apis/models/index.ts index 3e6e0d0c0b..5ea11a415b 100644 --- a/src/lib/apis/models/index.ts +++ b/src/lib/apis/models/index.ts @@ -1,34 +1,32 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; -export const getModels = async (token: string = '') => { - let error = null; +import { models, config, type Model } from '$lib/stores'; +import { get } from 'svelte/store'; +export const getModels = async (token: string = ''): Promise => { + const lang = get(config)?.default_locale || 'de'; - const res = await fetch(`${WEBUI_API_BASE_URL}/models/`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .then((json) => { - return json; - }) - .catch((err) => { - error = err; - console.error(err); - return null; - }); - if (error) { - throw error; - } + try { + const res = await fetch(`${WEBUI_API_BASE_URL}/models/`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'X-Language': lang, + Authorization: `Bearer ${token}`, + }, + }); - return res; + if (!res.ok) throw await res.json(); + + const data = (await res.json()) as Model[]; + models.set(data); // update global store so components react + return data; + } catch (err) { + console.error('Failed to fetch models:', err); + models.set([]); // clear store on error + return null; + } }; export const getBaseModels = async (token: string = '') => { diff --git a/src/lib/components/chat/ModelSelector/ModelItem.svelte b/src/lib/components/chat/ModelSelector/ModelItem.svelte index 3bc07152f2..697df0871a 100644 --- a/src/lib/components/chat/ModelSelector/ModelItem.svelte +++ b/src/lib/components/chat/ModelSelector/ModelItem.svelte @@ -41,27 +41,6 @@ }; let showMenu = false; - $: langCode = $i18n.language?.split('-')[0] || 'de'; - - function getTranslatedLabel(label, langCode) { - if (!label) return ''; - - try { - // If it's already an object, use it directly - const translations = typeof label === 'object' ? label : JSON.parse(label); - return ( - translations[langCode] || - translations.en || - translations.de || - translations.fr || - translations.it || - '' - ); - } catch (e) { - // If parsing fails, return the original value - return label; - } - }