feat: select Gemini model in settings (#180)#320
Conversation
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>
There was a problem hiding this comment.
Pull request overview
Adds end-user configurability for which Gemini model MeticAI uses, wiring the selection through Settings (UI + persistence) and ensuring backend/runtime environment hydration supports it.
Changes:
- Add
geminiModelto persisted settings (defaultgemini-2.5-flash) and hydrate/hot-reloadGEMINI_MODELon the server. - Add a Gemini model selector to the Settings UI and persist the choice (server in proxy mode, localStorage in direct mode).
- Add i18n keys across all locales for the new Settings text.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/src/services/ai/BrowserAIService.ts | Reads selected model from localStorage and uses it for Gemini calls in direct mode. |
| apps/web/src/lib/constants.ts | Adds STORAGE_KEYS.GEMINI_MODEL for persisted model selection. |
| apps/web/src/components/SettingsView.tsx | Adds Gemini model dropdown; loads/saves model in both proxy and direct modes. |
| apps/web/public/locales/en/translation.json | Adds settings.geminiModel* i18n keys (and related settings keys updates). |
| apps/web/public/locales/sv/translation.json | Adds settings.geminiModel* i18n keys (and related settings keys updates). |
| apps/web/public/locales/de/translation.json | Adds settings.geminiModel* i18n keys (and related settings keys updates). |
| apps/web/public/locales/es/translation.json | Adds settings.geminiModel* i18n keys (and related settings keys updates). |
| apps/web/public/locales/fr/translation.json | Adds settings.geminiModel* i18n keys (and related settings keys updates). |
| apps/web/public/locales/it/translation.json | Adds settings.geminiModel* i18n keys (and related settings keys updates). |
| apps/server/services/settings_service.py | Adds geminiModel default to settings schema. |
| apps/server/main.py | Hydrates GEMINI_MODEL from stored settings on startup. |
| apps/server/api/routes/system.py | Returns geminiModel (env precedence) and persists/hot-reloads it on save. |
| <option value="gemini-2.5-flash">Gemini 2.5 Flash</option> | ||
| <option value="gemini-2.5-pro">Gemini 2.5 Pro</option> | ||
| <option value="gemini-2.0-flash">Gemini 2.0 Flash</option> |
There was a problem hiding this comment.
The Gemini model dropdown option labels are hard-coded strings. Repo convention requires all user-facing strings to go through t() (see .github/CONVENTIONS.md i18n section). Add i18n keys for each option label (or otherwise route these labels through t()), even if the underlying value stays as the raw model id.
| <option value="gemini-2.5-flash">Gemini 2.5 Flash</option> | |
| <option value="gemini-2.5-pro">Gemini 2.5 Pro</option> | |
| <option value="gemini-2.0-flash">Gemini 2.0 Flash</option> | |
| <option value="gemini-2.5-flash">{t('settings.geminiModelOptionFlash25')}</option> | |
| <option value="gemini-2.5-pro">{t('settings.geminiModelOptionPro25')}</option> | |
| <option value="gemini-2.0-flash">{t('settings.geminiModelOptionFlash20')}</option> |
There was a problem hiding this comment.
✅ Resolved in 5e51ea9. Wrapped all three Gemini model dropdown labels in t() with keys settings.geminiModel25Flash, settings.geminiModel25Pro, and settings.geminiModel20Flash. Added corresponding translations to all 6 locale files (en, sv, de, es, fr, it).
apps/server/api/routes/system.py
Outdated
| current_settings["geminiModel"] = str(body["geminiModel"]).strip() | ||
|
|
||
| # For IP and API key changes, also update .env file | ||
| env_updated = False |
There was a problem hiding this comment.
geminiModel from the request body is persisted as a raw stripped string, which allows an empty string to be written to settings.json. Treat blank/whitespace-only values as “unset” (e.g., delete the key or set the default) to avoid storing an invalid/ambiguous model value.
| current_settings["geminiModel"] = str(body["geminiModel"]).strip() | |
| # For IP and API key changes, also update .env file | |
| env_updated = False | |
| gemini_model = str(body["geminiModel"]).strip() | |
| if gemini_model: | |
| # Persist only non-empty model values | |
| current_settings["geminiModel"] = gemini_model | |
| else: | |
| # Treat blank/whitespace-only input as "unset" |
There was a problem hiding this comment.
✅ Resolved in 5e51ea9. Empty/whitespace-only geminiModel now falls back to the default gemini-2.5-flash instead of persisting an empty string.
| # 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}) |
There was a problem hiding this comment.
New Gemini model selection behavior in GET/POST /api/settings is not covered by existing endpoint tests (there are tests for /api/settings, but none assert geminiModel env precedence or persistence/hot-reload). Please add tests in apps/server/test_main.py for: GET returning env override; POST updating stored settings and os.environ['GEMINI_MODEL'] (and/or s6 env update call).
There was a problem hiding this comment.
✅ Resolved in 5e51ea9. Added 3 tests: model persistence round-trip, empty-string fallback to default, and geminiModel presence in GET response. All 824 backend tests passing.
- 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>
Review Findings Addressed
Issue #180 completeness: All acceptance criteria met — model selector visible in Settings, works in proxy and direct mode, selected model used for all AI operations, default model set. Ready to merge. |
Summary
Adds a model selector dropdown to Settings allowing users to choose which Gemini model to use for AI profile generation, shot analysis, and recommendations.
Changes
Backend (Python/FastAPI):
geminiModelto settings schema with defaultgemini-2.5-flashGET /api/settingsreturns current model (env var takes precedence)POST /api/settingspersists model choice and hot-reloadsGEMINI_MODELenv vargeminiModel→GEMINI_MODELenv varFrontend (React/TypeScript):
GEMINI_MODELkey toSTORAGE_KEYSconstantsBrowserAIService: replaced hard-coded model withgetGeminiModel()reading from localStorageSettingsView: model selector dropdown (Gemini 2.5 Flash / 2.5 Pro / 2.0 Flash)i18n: Translation keys added for all 6 locales (en, sv, de, es, fr, it)
Available Models
Closes #180