From 82fefcec63d5b81209ac494b9d797fb4216c67fb Mon Sep 17 00:00:00 2001 From: Dries Peeters Date: Wed, 9 Sep 2026 06:15:29 +0200 Subject: [PATCH 1/2] feat(ai): add OrcaRouter as a named hosted AI provider Register orcarouter alongside openai_compatible so admins can point the AI helper at OrcaRouter's OpenAI-compatible endpoint with a Bearer key. --- CHANGELOG.md | 4 ++++ app/models/settings.py | 2 +- app/routes/admin.py | 2 +- app/services/llm_service.py | 4 ++-- app/templates/admin/settings.html | 3 ++- env.example | 7 +++++++ 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c9e15ed..99090a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### 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`. + ## [5.13.5] - 2026-09-08 ### Fixed 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 @@

{{ _('AI Helper') }}

-

{{ _('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.') }}

diff --git a/env.example b/env.example index 70539362..1d6faf0e 100644 --- a/env.example +++ b/env.example @@ -73,6 +73,13 @@ AI_ENABLED=false # AI_MODEL=... # AI_API_KEY=... +# --- Optional: OrcaRouter (hosted, OpenAI-compatible; failover + budgets) --- +# AI_ENABLED=true +# AI_PROVIDER=orcarouter +# AI_BASE_URL=https://api.orcarouter.ai +# AI_MODEL= +# AI_API_KEY= + AI_PROVIDER=ollama AI_BASE_URL=http://ollama:11434 AI_MODEL=llama3.1 From 1464f8e176f2a906be36ad1cd1be3c8f2eb35a05 Mon Sep 17 00:00:00 2001 From: Dries Peeters Date: Wed, 9 Sep 2026 06:21:16 +0200 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20release=205.14.0=20=E2=80=94=20Orc?= =?UTF-8?q?aRouter=20AI=20provider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump setup.py to 5.14.0 - Promote [Unreleased] → [5.14.0] in CHANGELOG.md - Add v5.14.0 highlights to README.md - Update version table in DESKTOP_WEBAPP_GAP.md to 5.14.0 --- CHANGELOG.md | 6 ++++++ README.md | 4 ++++ docs/mobile-desktop-apps/DESKTOP_WEBAPP_GAP.md | 6 +++--- setup.py | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99090a24..e1993220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +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/docs/mobile-desktop-apps/DESKTOP_WEBAPP_GAP.md b/docs/mobile-desktop-apps/DESKTOP_WEBAPP_GAP.md index 367d4fbe..7945fb42 100644 --- a/docs/mobile-desktop-apps/DESKTOP_WEBAPP_GAP.md +++ b/docs/mobile-desktop-apps/DESKTOP_WEBAPP_GAP.md @@ -6,9 +6,9 @@ Living checklist for bringing the Electron desktop client up to speed with the F | Component | Version | Source | |-----------|---------|--------| -| Web / backend | 5.13.5 | `setup.py` | -| Desktop | 5.13.5 | `desktop/package.json` | -| Mobile | 5.13.5 | `mobile/pubspec.yaml` | +| Web / backend | 5.14.0 | `setup.py` | +| Desktop | 5.14.0 | `desktop/package.json` | +| Mobile | 5.14.0 | `mobile/pubspec.yaml` | ## What desktop has diff --git a/setup.py b/setup.py index 8c33c5ad..d37e2123 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='timetracker', - version='5.13.5', + version='5.14.0', packages=find_packages(), include_package_data=True, package_data={