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')}