Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion app/models/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "",
Expand Down
2 changes: 1 addition & 1 deletion app/routes/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions app/services/llm_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion app/templates/admin/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -708,12 +708,13 @@ <h2 class="text-lg font-semibold mb-4">{{ _('AI Helper') }}</h2>
<select name="ai_provider" id="ai_provider" class="form-input">
<option value="ollama" {% if ai_config.provider == 'ollama' %}selected{% endif %}>Ollama / local OpenAI-compatible</option>
<option value="openai_compatible" {% if ai_config.provider == 'openai_compatible' %}selected{% endif %}>Hosted OpenAI-compatible</option>
<option value="orcarouter" {% if ai_config.provider == 'orcarouter' %}selected{% endif %}>OrcaRouter (hosted, OpenAI-compatible)</option>
</select>
</div>
<div>
<label for="ai_base_url" class="form-label">{{ _('Base URL') }}</label>
<input type="url" name="ai_base_url" id="ai_base_url" value="{{ settings.ai_base_url or ai_config.base_url }}" placeholder="http://127.0.0.1:11434" class="form-input">
<p class="mt-1 text-xs text-text-muted-light dark:text-text-muted-dark">{{ _('For Ollama, this is the URL from the Flask server to Ollama.') }}</p>
<p class="mt-1 text-xs text-text-muted-light dark:text-text-muted-dark">{{ _('For Ollama, this is the URL from the Flask server to Ollama. For OrcaRouter, use https://api.orcarouter.ai.') }}</p>
</div>
<div>
<label for="ai_model" class="form-label">{{ _('Model') }}</label>
Expand Down
6 changes: 3 additions & 3 deletions docs/mobile-desktop-apps/DESKTOP_WEBAPP_GAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=<model-slug>
# AI_API_KEY=<your-orcarouter-api-key>

AI_PROVIDER=ollama
AI_BASE_URL=http://ollama:11434
AI_MODEL=llama3.1
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='timetracker',
version='5.13.5',
version='5.14.0',
packages=find_packages(),
include_package_data=True,
package_data={
Expand Down
Loading