From b6830674a276fa0b606bf37ec0eb73c276bc913f Mon Sep 17 00:00:00 2001 From: Jesper Hessius Date: Wed, 25 Mar 2026 19:50:48 +0100 Subject: [PATCH 1/2] feat: select Gemini model in settings (#180) Add model selector dropdown to Settings allowing users to choose between Gemini 2.5 Flash, Gemini 2.5 Pro, and Gemini 2.0 Flash for AI operations. Backend: geminiModel added to settings schema, GET/POST endpoints, and hydrated into GEMINI_MODEL env var with hot-reload support. Frontend: model selection persisted via localStorage (direct mode) or POST /api/settings (proxy mode). BrowserAIService reads model dynamically. i18n: translations added for all 6 locales (en, sv, de, es, fr, it). Closes #180 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/server/api/routes/system.py | 21 +++++++++++++++ apps/server/main.py | 1 + apps/server/services/settings_service.py | 1 + apps/web/public/locales/de/translation.json | 10 ++++++- apps/web/public/locales/en/translation.json | 10 ++++++- apps/web/public/locales/es/translation.json | 10 ++++++- apps/web/public/locales/fr/translation.json | 10 ++++++- apps/web/public/locales/it/translation.json | 10 ++++++- apps/web/public/locales/sv/translation.json | 10 ++++++- apps/web/src/components/SettingsView.tsx | 28 ++++++++++++++++++++ apps/web/src/lib/constants.ts | 4 +++ apps/web/src/services/ai/BrowserAIService.ts | 21 ++++++++++----- 12 files changed, 124 insertions(+), 12 deletions(-) diff --git a/apps/server/api/routes/system.py b/apps/server/api/routes/system.py index df4d6a0c..42bdb592 100644 --- a/apps/server/api/routes/system.py +++ b/apps/server/api/routes/system.py @@ -1190,6 +1190,13 @@ async def get_settings(request: Request): settings["mqttEnabled"] = mqtt_env.lower() == "true" elif "mqttEnabled" not in settings: settings["mqttEnabled"] = True + + # Gemini model (env var takes precedence over stored setting) + gemini_model_env = os.environ.get("GEMINI_MODEL", "").strip() + if gemini_model_env: + settings["geminiModel"] = gemini_model_env + elif "geminiModel" not in settings: + settings["geminiModel"] = "gemini-2.5-flash" return settings @@ -1249,6 +1256,10 @@ async def save_settings_endpoint(request: Request): for bool_key in ("autoSync", "autoSyncAiDescription"): if bool_key in body: current_settings[bool_key] = bool(body[bool_key]) + + # Gemini model selection + if "geminiModel" in body: + current_settings["geminiModel"] = str(body["geminiModel"]).strip() # For IP and API key changes, also update .env file env_updated = False @@ -1425,6 +1436,16 @@ async def save_settings_endpoint(request: Request): logger.info("MQTT enabled=%s", mqtt_enabled, extra={"request_id": request_id}) + # Hot-reload Gemini model into process environment + if "geminiModel" in body: + new_model = str(body["geminiModel"]).strip() + if new_model: + os.environ["GEMINI_MODEL"] = new_model + _update_s6_env("GEMINI_MODEL", new_model, request_id) + services_restarted.append("gemini_model") + logger.info("Updated GEMINI_MODEL to %s", new_model, + extra={"request_id": request_id}) + return { "status": "success", "message": "Settings saved successfully", diff --git a/apps/server/main.py b/apps/server/main.py index a6c26869..bd236f76 100644 --- a/apps/server/main.py +++ b/apps/server/main.py @@ -140,6 +140,7 @@ async def lifespan(app: FastAPI): stored = load_settings() _ENV_SETTINGS_MAP = { "geminiApiKey": "GEMINI_API_KEY", + "geminiModel": "GEMINI_MODEL", "meticulousIp": "METICULOUS_IP", "authorName": "AUTHOR_NAME", } diff --git a/apps/server/services/settings_service.py b/apps/server/services/settings_service.py index f73ad44b..f8b9a042 100644 --- a/apps/server/services/settings_service.py +++ b/apps/server/services/settings_service.py @@ -14,6 +14,7 @@ _DEFAULT_SETTINGS = { "geminiApiKey": "", + "geminiModel": "gemini-2.5-flash", "meticulousIp": "", "serverIp": "", "authorName": "", diff --git a/apps/web/public/locales/de/translation.json b/apps/web/public/locales/de/translation.json index c657605e..860d4ce0 100644 --- a/apps/web/public/locales/de/translation.json +++ b/apps/web/public/locales/de/translation.json @@ -423,6 +423,8 @@ "authorName": "Autorenname", "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Dein Name für das Autorenfeld im generierten Profil-JSON. Standardmäßig \"MeticAI\", wenn leer gelassen.", + "geminiModel": "Gemini-Modell", + "geminiModelDescription": "Wählen Sie das AI-Modell für die Profilgenerierung und Analyse.", "mqttEnabled": "MQTT-Brücke", "mqttEnabledDescription": "Echtzeit-Maschinentelemetrie über MQTT. Betreibt das Live-Dashboard.", "saveSettings": "Einstellungen speichern", @@ -860,7 +862,13 @@ "followSystemTheme": "Follow system theme", "followSystemDescription": "Automatically match your system's dark/light mode preference", "backgroundAnimations": "Background animations", - "animationsDescription": "Show animated background effects" + "animationsDescription": "Show animated background effects", + "platformTheme": "Plattform-Design", + "platformThemeDescription": "Visuellen Stil an Ihr Gerät anpassen", + "platformThemeAuto": "Automatisch", + "platformThemeIos": "iOS", + "platformThemeMaterial": "Material (Android)", + "platformThemeNone": "Standard" }, "advanced": { "detailedKnowledge": "Detailliertes KI-Wissen", diff --git a/apps/web/public/locales/en/translation.json b/apps/web/public/locales/en/translation.json index 0d720abf..ddfa3889 100644 --- a/apps/web/public/locales/en/translation.json +++ b/apps/web/public/locales/en/translation.json @@ -423,6 +423,8 @@ "authorName": "Author Name", "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Your name for the author field in generated profile JSON. Defaults to \"MeticAI\" if left empty.", + "geminiModel": "Gemini Model", + "geminiModelDescription": "Select which AI model to use for profile generation and analysis.", "mqttEnabled": "MQTT Bridge", "mqttEnabledDescription": "Real-time machine telemetry via MQTT. Powers the live Control Center dashboard.", "addToHomeAssistant": "Add to Home Assistant", @@ -865,7 +867,13 @@ "followSystemTheme": "Follow system theme", "followSystemDescription": "Automatically match your system's dark/light mode preference", "backgroundAnimations": "Background animations", - "animationsDescription": "Show animated background effects" + "animationsDescription": "Show animated background effects", + "platformTheme": "Platform theme", + "platformThemeDescription": "Adjust the visual style to match your device", + "platformThemeAuto": "Auto-detect", + "platformThemeIos": "iOS", + "platformThemeMaterial": "Material (Android)", + "platformThemeNone": "Default" }, "pourOver": { "title": "Pour-over", diff --git a/apps/web/public/locales/es/translation.json b/apps/web/public/locales/es/translation.json index cbaf03d9..a9375de8 100644 --- a/apps/web/public/locales/es/translation.json +++ b/apps/web/public/locales/es/translation.json @@ -423,6 +423,8 @@ "authorName": "Nombre del autor", "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Tu nombre para el campo de autor en el JSON de perfil generado. Por defecto \"MeticAI\" si se deja vacío.", + "geminiModel": "Modelo Gemini", + "geminiModelDescription": "Selecciona qué modelo de IA usar para la generación de perfiles y análisis.", "mqttEnabled": "Puente MQTT", "mqttEnabledDescription": "Telemetría en tiempo real de la máquina vía MQTT. Alimenta el panel de control en vivo.", "saveSettings": "Guardar configuración", @@ -860,7 +862,13 @@ "followSystemTheme": "Follow system theme", "followSystemDescription": "Automatically match your system's dark/light mode preference", "backgroundAnimations": "Background animations", - "animationsDescription": "Show animated background effects" + "animationsDescription": "Show animated background effects", + "platformTheme": "Tema de plataforma", + "platformThemeDescription": "Ajusta el estilo visual según tu dispositivo", + "platformThemeAuto": "Detectar automáticamente", + "platformThemeIos": "iOS", + "platformThemeMaterial": "Material (Android)", + "platformThemeNone": "Predeterminado" }, "advanced": { "detailedKnowledge": "Conocimiento detallado de IA", diff --git a/apps/web/public/locales/fr/translation.json b/apps/web/public/locales/fr/translation.json index 87d90de1..abb42199 100644 --- a/apps/web/public/locales/fr/translation.json +++ b/apps/web/public/locales/fr/translation.json @@ -423,6 +423,8 @@ "authorName": "Nom de l'auteur", "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Votre nom pour le champ auteur dans le JSON du profil généré. Par défaut \"MeticAI\" si laissé vide.", + "geminiModel": "Modèle Gemini", + "geminiModelDescription": "Sélectionnez le modèle d'IA à utiliser pour la génération de profils et l'analyse.", "mqttEnabled": "Pont MQTT", "mqttEnabledDescription": "Télémétrie machine en temps réel via MQTT. Alimente le tableau de bord en direct.", "saveSettings": "Enregistrer les paramètres", @@ -860,7 +862,13 @@ "followSystemTheme": "Follow system theme", "followSystemDescription": "Automatically match your system's dark/light mode preference", "backgroundAnimations": "Background animations", - "animationsDescription": "Show animated background effects" + "animationsDescription": "Show animated background effects", + "platformTheme": "Thème de plateforme", + "platformThemeDescription": "Adapter le style visuel à votre appareil", + "platformThemeAuto": "Détection automatique", + "platformThemeIos": "iOS", + "platformThemeMaterial": "Material (Android)", + "platformThemeNone": "Par défaut" }, "advanced": { "detailedKnowledge": "Connaissances IA détaillées", diff --git a/apps/web/public/locales/it/translation.json b/apps/web/public/locales/it/translation.json index 535acc44..75615654 100644 --- a/apps/web/public/locales/it/translation.json +++ b/apps/web/public/locales/it/translation.json @@ -423,6 +423,8 @@ "authorName": "Nome autore", "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Il tuo nome per il campo autore nel JSON del profilo generato. Predefinito \"MeticAI\" se lasciato vuoto.", + "geminiModel": "Modello Gemini", + "geminiModelDescription": "Seleziona quale modello AI utilizzare per la generazione di profili e l'analisi.", "mqttEnabled": "Bridge MQTT", "mqttEnabledDescription": "Telemetria macchina in tempo reale via MQTT. Alimenta la dashboard di controllo dal vivo.", "saveSettings": "Salva impostazioni", @@ -860,7 +862,13 @@ "followSystemTheme": "Follow system theme", "followSystemDescription": "Automatically match your system's dark/light mode preference", "backgroundAnimations": "Background animations", - "animationsDescription": "Show animated background effects" + "animationsDescription": "Show animated background effects", + "platformTheme": "Tema piattaforma", + "platformThemeDescription": "Adatta lo stile visivo al tuo dispositivo", + "platformThemeAuto": "Rilevamento automatico", + "platformThemeIos": "iOS", + "platformThemeMaterial": "Material (Android)", + "platformThemeNone": "Predefinito" }, "advanced": { "detailedKnowledge": "Conoscenza IA dettagliata", diff --git a/apps/web/public/locales/sv/translation.json b/apps/web/public/locales/sv/translation.json index 6f76e2a5..abd98c23 100644 --- a/apps/web/public/locales/sv/translation.json +++ b/apps/web/public/locales/sv/translation.json @@ -423,6 +423,8 @@ "authorName": "Författarnamn", "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Ditt namn för författarfältet i genererad profil-JSON. Standardvärde \"MeticAI\" om det lämnas tomt.", + "geminiModel": "Gemini-modell", + "geminiModelDescription": "Välj vilken AI-modell som ska användas för profilgenerering och analys.", "mqttEnabled": "MQTT-brygga", "mqttEnabledDescription": "Realtidstelemetri från maskinen via MQTT. Driver den direkta kontrollpanelen.", "saveSettings": "Spara inställningar", @@ -865,7 +867,13 @@ "followSystemTheme": "Följ systemtema", "followSystemDescription": "Matcha automatiskt ditt systems mörkt-/ljust-läge", "backgroundAnimations": "Bakgrundsanimationer", - "animationsDescription": "Visa animerade bakgrundseffekter" + "animationsDescription": "Visa animerade bakgrundseffekter", + "platformTheme": "Plattformstema", + "platformThemeDescription": "Anpassa utseendet efter din enhet", + "platformThemeAuto": "Automatisk", + "platformThemeIos": "iOS", + "platformThemeMaterial": "Material (Android)", + "platformThemeNone": "Standard" }, "pourOver": { "title": "Pour Over", diff --git a/apps/web/src/components/SettingsView.tsx b/apps/web/src/components/SettingsView.tsx index 4e587588..51bf9527 100644 --- a/apps/web/src/components/SettingsView.tsx +++ b/apps/web/src/components/SettingsView.tsx @@ -54,6 +54,7 @@ interface Settings { geminiApiKey: string meticulousIp: string authorName: string + geminiModel?: string mqttEnabled?: boolean geminiApiKeyMasked?: boolean geminiApiKeyConfigured?: boolean @@ -105,6 +106,7 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo geminiApiKey: '', meticulousIp: '', authorName: '', + geminiModel: 'gemini-2.5-flash', mqttEnabled: true }) const [isSaving, setIsSaving] = useState(false) @@ -201,6 +203,7 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo geminiApiKey: localStorage.getItem(STORAGE_KEYS.GEMINI_API_KEY) || '', meticulousIp: window.location.hostname, authorName: localStorage.getItem(STORAGE_KEYS.AUTHOR_NAME) || '', + geminiModel: localStorage.getItem(STORAGE_KEYS.GEMINI_MODEL) || 'gemini-2.5-flash', mqttEnabled: true, geminiApiKeyMasked: false, geminiApiKeyConfigured: Boolean(localStorage.getItem(STORAGE_KEYS.GEMINI_API_KEY)?.trim()), @@ -217,6 +220,7 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo geminiApiKey: data.geminiApiKey || '', meticulousIp: data.meticulousIp || '', authorName: data.authorName || '', + geminiModel: data.geminiModel || 'gemini-2.5-flash', mqttEnabled: data.mqttEnabled !== false, geminiApiKeyMasked: data.geminiApiKeyMasked || false, geminiApiKeyConfigured: data.geminiApiKeyConfigured || false @@ -362,6 +366,9 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo if (settings.authorName) { localStorage.setItem(STORAGE_KEYS.AUTHOR_NAME, settings.authorName) } + if (settings.geminiModel) { + localStorage.setItem(STORAGE_KEYS.GEMINI_MODEL, settings.geminiModel) + } setSaveStatus('success') setTimeout(() => setSaveStatus('idle'), 3000) return @@ -374,6 +381,7 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo authorName: settings.authorName, meticulousIp: settings.meticulousIp, mqttEnabled: settings.mqttEnabled, + geminiModel: settings.geminiModel, } // Only send API key if user actually typed a new value (not the masked stars) @@ -966,6 +974,26 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo

+ {/* Gemini Model */} +
+ + +

+ {t('settings.geminiModelDescription')} +

+
+ {/* MQTT Bridge */} {hasFeature('bridgeStatus') &&
diff --git a/apps/web/src/lib/constants.ts b/apps/web/src/lib/constants.ts index 6b3241f3..998e9422 100644 --- a/apps/web/src/lib/constants.ts +++ b/apps/web/src/lib/constants.ts @@ -9,10 +9,14 @@ // -- User settings (persisted in direct/PWA mode) -- export const STORAGE_KEYS = { GEMINI_API_KEY: 'meticai-gemini-key', + GEMINI_MODEL: 'meticai-gemini-model', AUTHOR_NAME: 'meticai-author-name', // -- Direct mode caches -- PROFILE_LIST_CACHE: 'meticai-direct-profile-list', DESCRIPTION_CACHE: 'meticai-direct-desc-cache', POUR_OVER_PREFS: 'meticai-direct-pour-over-prefs', + + // -- Appearance -- + PLATFORM_THEME: 'meticai-platform-theme', } as const diff --git a/apps/web/src/services/ai/BrowserAIService.ts b/apps/web/src/services/ai/BrowserAIService.ts index 6507f2fd..4623321f 100644 --- a/apps/web/src/services/ai/BrowserAIService.ts +++ b/apps/web/src/services/ai/BrowserAIService.ts @@ -30,9 +30,18 @@ import { buildFullProfilePrompt, validateAndRetryProfile } from './profilePrompt import { STORAGE_KEYS } from '@/lib/constants' -const GEMINI_MODEL = 'gemini-2.5-flash' +const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash' const IMAGE_MODEL = 'imagen-3.0-generate-002' +function getGeminiModel(): string { + try { + const stored = localStorage.getItem(STORAGE_KEYS.GEMINI_MODEL) + return stored?.trim() || DEFAULT_GEMINI_MODEL + } catch { + return DEFAULT_GEMINI_MODEL + } +} + function getStoredApiKey(): string | null { try { return localStorage.getItem(STORAGE_KEYS.GEMINI_API_KEY) @@ -124,7 +133,7 @@ export function createBrowserAIService(): AIService { let response try { response = await client.models.generateContent({ - model: GEMINI_MODEL, + model: getGeminiModel(), contents: [{ role: 'user', parts }], }) } catch (err) { @@ -138,7 +147,7 @@ export function createBrowserAIService(): AIService { // Validation + retry loop const generateFix = async (fixPrompt: string) => { const fixResponse = await client.models.generateContent({ - model: GEMINI_MODEL, + model: getGeminiModel(), contents: [{ role: 'user', parts: [{ text: fixPrompt }] }], }) return fixResponse.text ?? '' @@ -168,7 +177,7 @@ export function createBrowserAIService(): AIService { let response try { response = await client.models.generateContent({ - model: GEMINI_MODEL, + model: getGeminiModel(), contents: [{ role: 'user', parts: [{ text: prompt }] }], }) } catch (err) { @@ -225,7 +234,7 @@ export function createBrowserAIService(): AIService { let response try { response = await client.models.generateContent({ - model: GEMINI_MODEL, + model: getGeminiModel(), contents: [{ role: 'user', parts: [{ text: prompt }] }], }) } catch (err) { @@ -270,7 +279,7 @@ export function createBrowserAIService(): AIService { let response try { response = await client.models.generateContent({ - model: GEMINI_MODEL, + model: getGeminiModel(), contents: [{ role: 'user', parts: [{ text: prompt }] }], }) } catch (err) { From 5e51ea93abfa84327f0d384db68c3985c8bc88af Mon Sep 17 00:00:00 2001 From: Jesper Hessius Date: Wed, 25 Mar 2026 23:10:24 +0100 Subject: [PATCH 2/2] fix: address code review findings for #320 - i18n: wrap Gemini model dropdown labels in t() with keys for all 6 locales - validation: treat empty/whitespace geminiModel as default (gemini-2.5-flash) - tests: add 3 backend tests for geminiModel persistence and retrieval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/server/api/routes/system.py | 3 +- apps/server/test_main.py | 40 +++++++++++++++++++++ apps/web/public/locales/de/translation.json | 7 ++-- apps/web/public/locales/en/translation.json | 7 ++-- apps/web/public/locales/es/translation.json | 7 ++-- apps/web/public/locales/fr/translation.json | 7 ++-- apps/web/public/locales/it/translation.json | 7 ++-- apps/web/public/locales/sv/translation.json | 7 ++-- apps/web/src/components/SettingsView.tsx | 6 ++-- 9 files changed, 75 insertions(+), 16 deletions(-) diff --git a/apps/server/api/routes/system.py b/apps/server/api/routes/system.py index 42bdb592..87e73cd3 100644 --- a/apps/server/api/routes/system.py +++ b/apps/server/api/routes/system.py @@ -1259,7 +1259,8 @@ async def save_settings_endpoint(request: Request): # Gemini model selection if "geminiModel" in body: - current_settings["geminiModel"] = str(body["geminiModel"]).strip() + model_value = str(body["geminiModel"]).strip() + current_settings["geminiModel"] = model_value if model_value else "gemini-2.5-flash" # For IP and API key changes, also update .env file env_updated = False diff --git a/apps/server/test_main.py b/apps/server/test_main.py index eb0879eb..c68bab3a 100644 --- a/apps/server/test_main.py +++ b/apps/server/test_main.py @@ -7927,6 +7927,46 @@ def test_save_settings_empty_body(self, client): # Should handle gracefully assert response.status_code in [200, 400, 500] + def test_save_gemini_model_persists_and_returns(self, client, monkeypatch, tmp_path): + """POST geminiModel then GET and verify it is returned.""" + import services.settings_service as settings_svc + + settings_file = tmp_path / "settings.json" + settings_file.write_text("{}") + monkeypatch.setattr(settings_svc, "SETTINGS_FILE", settings_file) + monkeypatch.delenv("GEMINI_MODEL", raising=False) + + resp = client.post("/api/settings", json={"geminiModel": "gemini-2.5-pro"}) + assert resp.status_code == 200 + + resp = client.get("/api/settings") + assert resp.status_code == 200 + assert resp.json()["geminiModel"] == "gemini-2.5-pro" + + def test_save_gemini_model_empty_falls_back_to_default(self, client, monkeypatch, tmp_path): + """POST empty geminiModel falls back to the default model.""" + import services.settings_service as settings_svc + + settings_file = tmp_path / "settings.json" + settings_file.write_text("{}") + monkeypatch.setattr(settings_svc, "SETTINGS_FILE", settings_file) + monkeypatch.delenv("GEMINI_MODEL", raising=False) + + resp = client.post("/api/settings", json={"geminiModel": " "}) + assert resp.status_code == 200 + + resp = client.get("/api/settings") + assert resp.status_code == 200 + assert resp.json()["geminiModel"] == "gemini-2.5-flash" + + def test_get_settings_includes_gemini_model(self, client, monkeypatch): + """GET /api/settings always includes geminiModel.""" + monkeypatch.delenv("GEMINI_MODEL", raising=False) + + resp = client.get("/api/settings") + assert resp.status_code == 200 + assert "geminiModel" in resp.json() + class TestDecompressShotData: """Test shot data decompression.""" diff --git a/apps/web/public/locales/de/translation.json b/apps/web/public/locales/de/translation.json index 860d4ce0..d28f82c2 100644 --- a/apps/web/public/locales/de/translation.json +++ b/apps/web/public/locales/de/translation.json @@ -424,7 +424,7 @@ "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Dein Name für das Autorenfeld im generierten Profil-JSON. Standardmäßig \"MeticAI\", wenn leer gelassen.", "geminiModel": "Gemini-Modell", - "geminiModelDescription": "Wählen Sie das AI-Modell für die Profilgenerierung und Analyse.", + "geminiModelDescription": "Wähle das Gemini-Modell für KI-Funktionen aus.", "mqttEnabled": "MQTT-Brücke", "mqttEnabledDescription": "Echtzeit-Maschinentelemetrie über MQTT. Betreibt das Live-Dashboard.", "saveSettings": "Einstellungen speichern", @@ -539,7 +539,10 @@ "runsOn": "Runs on", "andCaffeine": "and caffeine ☕", "betaChannelFailed": "Fehler beim Wechsel des Betakanals", - "feedbackFailed": "Fehler beim Senden des Feedbacks" + "feedbackFailed": "Fehler beim Senden des Feedbacks", + "geminiModel25Flash": "Gemini 2.5 Flash (Empfohlen)", + "geminiModel25Pro": "Gemini 2.5 Pro", + "geminiModel20Flash": "Gemini 2.0 Flash" }, "profileBreakdown": { "title": "Profildetails", diff --git a/apps/web/public/locales/en/translation.json b/apps/web/public/locales/en/translation.json index ddfa3889..6d164f2f 100644 --- a/apps/web/public/locales/en/translation.json +++ b/apps/web/public/locales/en/translation.json @@ -424,7 +424,7 @@ "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Your name for the author field in generated profile JSON. Defaults to \"MeticAI\" if left empty.", "geminiModel": "Gemini Model", - "geminiModelDescription": "Select which AI model to use for profile generation and analysis.", + "geminiModelDescription": "Select which Gemini model to use for AI features.", "mqttEnabled": "MQTT Bridge", "mqttEnabledDescription": "Real-time machine telemetry via MQTT. Powers the live Control Center dashboard.", "addToHomeAssistant": "Add to Home Assistant", @@ -539,7 +539,10 @@ "version": "Version", "footer": "MeticAI • Built with ❤️ for coffee lovers", "feedbackFailed": "Failed to submit feedback", - "betaChannelFailed": "Failed to switch beta channel" + "betaChannelFailed": "Failed to switch beta channel", + "geminiModel25Flash": "Gemini 2.5 Flash (Recommended)", + "geminiModel25Pro": "Gemini 2.5 Pro", + "geminiModel20Flash": "Gemini 2.0 Flash" }, "profileBreakdown": { "title": "Profile Details", diff --git a/apps/web/public/locales/es/translation.json b/apps/web/public/locales/es/translation.json index a9375de8..c1f9e5c2 100644 --- a/apps/web/public/locales/es/translation.json +++ b/apps/web/public/locales/es/translation.json @@ -424,7 +424,7 @@ "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Tu nombre para el campo de autor en el JSON de perfil generado. Por defecto \"MeticAI\" si se deja vacío.", "geminiModel": "Modelo Gemini", - "geminiModelDescription": "Selecciona qué modelo de IA usar para la generación de perfiles y análisis.", + "geminiModelDescription": "Selecciona qué modelo Gemini usar para las funciones de IA.", "mqttEnabled": "Puente MQTT", "mqttEnabledDescription": "Telemetría en tiempo real de la máquina vía MQTT. Alimenta el panel de control en vivo.", "saveSettings": "Guardar configuración", @@ -539,7 +539,10 @@ "runsOn": "Runs on", "andCaffeine": "and caffeine ☕", "betaChannelFailed": "Error al cambiar el canal beta", - "feedbackFailed": "Error al enviar comentarios" + "feedbackFailed": "Error al enviar comentarios", + "geminiModel25Flash": "Gemini 2.5 Flash (Recomendado)", + "geminiModel25Pro": "Gemini 2.5 Pro", + "geminiModel20Flash": "Gemini 2.0 Flash" }, "profileBreakdown": { "title": "Detalles del perfil", diff --git a/apps/web/public/locales/fr/translation.json b/apps/web/public/locales/fr/translation.json index abb42199..5807e76d 100644 --- a/apps/web/public/locales/fr/translation.json +++ b/apps/web/public/locales/fr/translation.json @@ -424,7 +424,7 @@ "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Votre nom pour le champ auteur dans le JSON du profil généré. Par défaut \"MeticAI\" si laissé vide.", "geminiModel": "Modèle Gemini", - "geminiModelDescription": "Sélectionnez le modèle d'IA à utiliser pour la génération de profils et l'analyse.", + "geminiModelDescription": "Sélectionnez le modèle Gemini à utiliser pour les fonctions IA.", "mqttEnabled": "Pont MQTT", "mqttEnabledDescription": "Télémétrie machine en temps réel via MQTT. Alimente le tableau de bord en direct.", "saveSettings": "Enregistrer les paramètres", @@ -539,7 +539,10 @@ "runsOn": "Runs on", "andCaffeine": "and caffeine ☕", "betaChannelFailed": "Échec du changement de canal bêta", - "feedbackFailed": "Échec de l'envoi du commentaire" + "feedbackFailed": "Échec de l'envoi du commentaire", + "geminiModel25Flash": "Gemini 2.5 Flash (Recommandé)", + "geminiModel25Pro": "Gemini 2.5 Pro", + "geminiModel20Flash": "Gemini 2.0 Flash" }, "profileBreakdown": { "title": "Détails du profil", diff --git a/apps/web/public/locales/it/translation.json b/apps/web/public/locales/it/translation.json index 75615654..749648c8 100644 --- a/apps/web/public/locales/it/translation.json +++ b/apps/web/public/locales/it/translation.json @@ -424,7 +424,7 @@ "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Il tuo nome per il campo autore nel JSON del profilo generato. Predefinito \"MeticAI\" se lasciato vuoto.", "geminiModel": "Modello Gemini", - "geminiModelDescription": "Seleziona quale modello AI utilizzare per la generazione di profili e l'analisi.", + "geminiModelDescription": "Seleziona quale modello Gemini utilizzare per le funzioni IA.", "mqttEnabled": "Bridge MQTT", "mqttEnabledDescription": "Telemetria macchina in tempo reale via MQTT. Alimenta la dashboard di controllo dal vivo.", "saveSettings": "Salva impostazioni", @@ -539,7 +539,10 @@ "runsOn": "Runs on", "andCaffeine": "and caffeine ☕", "betaChannelFailed": "Impossibile cambiare il canale beta", - "feedbackFailed": "Impossibile inviare il feedback" + "feedbackFailed": "Impossibile inviare il feedback", + "geminiModel25Flash": "Gemini 2.5 Flash (Consigliato)", + "geminiModel25Pro": "Gemini 2.5 Pro", + "geminiModel20Flash": "Gemini 2.0 Flash" }, "profileBreakdown": { "title": "Dettagli profilo", diff --git a/apps/web/public/locales/sv/translation.json b/apps/web/public/locales/sv/translation.json index abd98c23..5b2fa0d3 100644 --- a/apps/web/public/locales/sv/translation.json +++ b/apps/web/public/locales/sv/translation.json @@ -424,7 +424,7 @@ "authorNamePlaceholder": "MeticAI", "authorNameDescription": "Ditt namn för författarfältet i genererad profil-JSON. Standardvärde \"MeticAI\" om det lämnas tomt.", "geminiModel": "Gemini-modell", - "geminiModelDescription": "Välj vilken AI-modell som ska användas för profilgenerering och analys.", + "geminiModelDescription": "Välj vilken Gemini-modell som ska användas för AI-funktioner.", "mqttEnabled": "MQTT-brygga", "mqttEnabledDescription": "Realtidstelemetri från maskinen via MQTT. Driver den direkta kontrollpanelen.", "saveSettings": "Spara inställningar", @@ -539,7 +539,10 @@ "runsOn": "Runs on", "andCaffeine": "and caffeine ☕", "betaChannelFailed": "Kunde inte byta betakanal", - "feedbackFailed": "Kunde inte skicka feedback" + "feedbackFailed": "Kunde inte skicka feedback", + "geminiModel25Flash": "Gemini 2.5 Flash (Rekommenderad)", + "geminiModel25Pro": "Gemini 2.5 Pro", + "geminiModel20Flash": "Gemini 2.0 Flash" }, "profileBreakdown": { "title": "Profildetaljer", diff --git a/apps/web/src/components/SettingsView.tsx b/apps/web/src/components/SettingsView.tsx index 51bf9527..b5c127d6 100644 --- a/apps/web/src/components/SettingsView.tsx +++ b/apps/web/src/components/SettingsView.tsx @@ -985,9 +985,9 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo onChange={(e) => handleChange('geminiModel', e.target.value)} className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" > - - - + + +

{t('settings.geminiModelDescription')}