Add app settings management and update API URL handling

This commit is contained in:
2026-03-20 08:15:59 +01:00
parent 9787917d11
commit d24ea89ed1
4 changed files with 180 additions and 16 deletions

88
backend/app_settings.py Normal file
View File

@@ -0,0 +1,88 @@
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict
APP_NAME = "Heimgeist"
DEFAULT_BACKEND_API_URL = "http://127.0.0.1:8000"
DEFAULT_OLLAMA_API_URL = "http://127.0.0.1:11434"
DEFAULT_SETTINGS: Dict[str, Any] = {
"backendApiUrl": DEFAULT_BACKEND_API_URL,
"ollamaApiUrl": DEFAULT_OLLAMA_API_URL,
}
def _default_settings_dir() -> Path:
if sys.platform == "darwin":
return Path.home() / "Library" / "Application Support" / APP_NAME
if os.name == "nt":
appdata = os.getenv("APPDATA")
if appdata:
return Path(appdata) / APP_NAME
return Path.home() / "AppData" / "Roaming" / APP_NAME
return Path(os.getenv("XDG_CONFIG_HOME", str(Path.home() / ".config"))) / APP_NAME
def settings_path() -> Path:
custom_path = os.getenv("HEIMGEIST_SETTINGS_FILE")
if custom_path:
return Path(custom_path).expanduser()
return _default_settings_dir() / "settings.json"
def _looks_like_ollama_url(value: Any) -> bool:
if not isinstance(value, str):
return False
trimmed = value.strip()
if not trimmed:
return False
if ":11434" in trimmed:
return True
return trimmed.rstrip("/").endswith("/api")
def _normalize_url(value: Any, fallback: str) -> str:
if not isinstance(value, str):
return fallback
trimmed = value.strip().rstrip("/")
return trimmed or fallback
def load_app_settings() -> Dict[str, Any]:
path = settings_path()
try:
raw = json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
raw = {}
except Exception:
raw = {}
if not isinstance(raw, dict):
raw = {}
settings = {**DEFAULT_SETTINGS, **raw}
if "backendApiUrl" not in raw and isinstance(raw.get("ollamaApiUrl"), str):
if _looks_like_ollama_url(raw["ollamaApiUrl"]):
settings["backendApiUrl"] = DEFAULT_BACKEND_API_URL
settings["ollamaApiUrl"] = _normalize_url(raw["ollamaApiUrl"], DEFAULT_OLLAMA_API_URL)
else:
settings["backendApiUrl"] = _normalize_url(raw["ollamaApiUrl"], DEFAULT_BACKEND_API_URL)
settings["ollamaApiUrl"] = DEFAULT_OLLAMA_API_URL
else:
settings["backendApiUrl"] = _normalize_url(settings.get("backendApiUrl"), DEFAULT_BACKEND_API_URL)
settings["ollamaApiUrl"] = _normalize_url(settings.get("ollamaApiUrl"), DEFAULT_OLLAMA_API_URL)
return settings
def get_ollama_api_url() -> str:
settings = load_app_settings()
return _normalize_url(settings.get("ollamaApiUrl"), DEFAULT_OLLAMA_API_URL)

View File

@@ -3,11 +3,12 @@ import httpx
import json
from typing import Dict, Any, List, AsyncGenerator
OLLAMA_URL = "http://127.0.0.1:11434"
from .app_settings import get_ollama_api_url
async def list_models() -> Dict[str, Any]:
ollama_url = get_ollama_api_url()
async with httpx.AsyncClient(timeout=30.0) as client:
r = await client.get(f"{OLLAMA_URL}/api/tags")
r = await client.get(f"{ollama_url}/api/tags")
r.raise_for_status()
data = r.json()
# Normalize to a simple list of names
@@ -15,13 +16,14 @@ async def list_models() -> Dict[str, Any]:
return {"models": models}
async def chat(model: str, messages: List[Dict[str, str]]) -> str:
ollama_url = get_ollama_api_url()
payload = {
"model": model,
"messages": messages,
"stream": False
}
async with httpx.AsyncClient(timeout=600.0) as client:
r = await client.post(f"{OLLAMA_URL}/api/chat", json=payload)
r = await client.post(f"{ollama_url}/api/chat", json=payload)
r.raise_for_status()
data = r.json()
# Ollama returns full conversation; pick last message content
@@ -35,13 +37,14 @@ async def chat(model: str, messages: List[Dict[str, str]]) -> str:
return data.get("content", "")
async def chat_stream(model: str, messages: List[Dict[str, str]]) -> AsyncGenerator[str, None]:
ollama_url = get_ollama_api_url()
payload = {
"model": model,
"messages": messages,
"stream": True
}
async with httpx.AsyncClient(timeout=600.0) as client:
async with client.stream("POST", f"{OLLAMA_URL}/api/chat", json=payload) as r:
async with client.stream("POST", f"{ollama_url}/api/chat", json=payload) as r:
r.raise_for_status()
async for line in r.aiter_lines():
if line: