diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c9e15ed..e1993220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.14.0] - 2026-09-09 + +### Added + +- **OrcaRouter AI provider** — Added `orcarouter` as a named AI provider option (OpenAI-compatible endpoint, standard Bearer-token auth). Admins can select it in System Settings → AI Helper or set `AI_PROVIDER=orcarouter` with `AI_BASE_URL=https://api.orcarouter.ai`. + +### Documentation + +- **Version** — Bumped `setup.py` to **5.14.0** (single source of truth for the application version). + ## [5.13.5] - 2026-09-08 ### Fixed diff --git a/README.md b/README.md index 53404a9e..33f0b6b2 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,10 @@ TimeTracker has been continuously enhanced with powerful new features! Here's wh **Current version** is defined in `setup.py` (single source of truth). See [CHANGELOG.md](CHANGELOG.md) for versioned release history. +### ✨ Highlights of v5.14.0 + +**Minor (5.14.0):** **OrcaRouter AI provider** — `orcarouter` is now a named AI provider option (OpenAI-compatible endpoint, Bearer-token auth). Select it in System Settings → AI Helper or set `AI_PROVIDER=orcarouter` with `AI_BASE_URL=https://api.orcarouter.ai`. See [CHANGELOG.md](CHANGELOG.md#5140---2026-09-09). + ### ✨ Highlights of v5.13.5 **Patch (5.13.5):** **Timezone `local_date`** — date-only values (e.g. task due dates) no longer crash the dashboard filter. **Idle skip paused (#752)** — paused timers are excluded from idle notify / needs-review / auto-stop. **Manual entry single-client (#753)** — restores the readonly pre-filled client lock when the org has one client. See [CHANGELOG.md](CHANGELOG.md#5135---2026-09-08). diff --git a/app/models/settings.py b/app/models/settings.py index 708b8276..4ad03ae3 100644 --- a/app/models/settings.py +++ b/app/models/settings.py @@ -467,7 +467,7 @@ def cfg(name, default=None): return { "enabled": bool(enabled), - "provider": provider if provider in {"ollama", "openai_compatible"} else "ollama", + "provider": provider if provider in {"ollama", "openai_compatible", "orcarouter"} else "ollama", "base_url": base_url.rstrip("/"), "model": model, "api_key": api_key if include_secrets else "", diff --git a/app/routes/admin.py b/app/routes/admin.py index c05b8e04..caf3dcc8 100644 --- a/app/routes/admin.py +++ b/app/routes/admin.py @@ -1645,7 +1645,7 @@ def settings(): settings_obj.ai_enabled = None ai_provider = (request.form.get("ai_provider") or "ollama").strip().lower() - if ai_provider not in ("ollama", "openai_compatible"): + if ai_provider not in ("ollama", "openai_compatible", "orcarouter"): ai_provider = "ollama" settings_obj.ai_provider = ai_provider settings_obj.ai_base_url = (request.form.get("ai_base_url") or "").strip() diff --git a/app/services/llm_service.py b/app/services/llm_service.py index dc24cad7..33cf7c03 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -80,7 +80,7 @@ def ensure_enabled(self) -> None: raise AIServiceError("AI helper is disabled.", "ai_disabled", 503) if not self.config.base_url or not self.config.model: raise AIServiceError("AI helper is not fully configured.", "ai_not_configured", 400) - if self.config.provider == "openai_compatible" and not self.config.api_key: + if self.config.provider in ("openai_compatible", "orcarouter") and not self.config.api_key: raise AIServiceError("Hosted AI provider requires an API key.", "ai_missing_api_key", 400) def test_connection(self) -> Dict[str, Any]: @@ -186,7 +186,7 @@ def confirm_action(self, user: User, action: Dict[str, Any]) -> Dict[str, Any]: def _chat_completion(self, messages: List[Dict[str, str]], max_tokens: int = 700) -> Dict[str, Any]: url = f"{self.config.base_url.rstrip('/')}/v1/chat/completions" headers = {"Accept": "application/json", "Content-Type": "application/json"} - if self.config.provider == "openai_compatible" and self.config.api_key: + if self.config.provider in ("openai_compatible", "orcarouter") and self.config.api_key: headers["Authorization"] = f"Bearer {self.config.api_key}" payload = { "model": self.config.model, diff --git a/app/templates/admin/settings.html b/app/templates/admin/settings.html index 43f02ec0..276ceda1 100644 --- a/app/templates/admin/settings.html +++ b/app/templates/admin/settings.html @@ -708,12 +708,13 @@
{{ _('For Ollama, this is the URL from the Flask server to Ollama.') }}
+{{ _('For Ollama, this is the URL from the Flask server to Ollama. For OrcaRouter, use https://api.orcarouter.ai.') }}