Add app settings management and update API URL handling
This commit is contained in:
88
backend/app_settings.py
Normal file
88
backend/app_settings.py
Normal 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)
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user