Merge remote-tracking branch 'refs/remotes/origin/main'

This commit is contained in:
2026-05-09 17:22:53 +02:00
+33 -32
View File
@@ -2162,52 +2162,52 @@ async def update_config(request: Request) -> dict:
@app.get("/test-chat/models") @app.get("/test-chat/models")
async def test_chat_models(request: Request) -> dict: async def test_chat_models(request: Request) -> dict:
"""Return the list of models currently available in upstream_openai (Ollama).""" """Return the list of lm-studio/openai models configured in the DB."""
pool = request.app.state.pool
cfg = request.app.state.yaml_config cfg = request.app.state.yaml_config
upstream = cfg.get("upstream_openai", "") upstream = cfg.get("upstream_openai", "")
if not upstream: async with pool.acquire() as conn:
return {"ok": False, "error": "upstream_openai not configured", "models": []} rows = await conn.fetch(
url = f"{upstream.rstrip('/')}/v1/models" "SELECT model_name, base_url FROM models WHERE provider IN ('lm-studio', 'openai') ORDER BY id"
try: )
async with httpx.AsyncClient(timeout=8.0) as client: if rows:
r = await client.get(url) models = [r["model_name"] for r in rows]
if not r.is_success: return {"ok": True, "models": models, "upstream": upstream}
return {"ok": False, "error": f"HTTP {r.status_code} from {url}", "models": []} return {"ok": False, "error": "No lm-studio or openai models configured — add one via Model Manager.", "models": []}
ids = sorted(m.get("id", "") for m in r.json().get("data", []))
return {"ok": True, "models": ids, "upstream": upstream}
except httpx.ConnectError:
return {"ok": False, "error": f"Connection refused at {url} — is Ollama running?", "models": []}
except Exception as exc:
return {"ok": False, "error": str(exc), "models": []}
@app.post("/test-chat") @app.post("/test-chat")
async def test_chat(request: Request) -> dict: async def test_chat(request: Request) -> dict:
cfg = request.app.state.yaml_config cfg = request.app.state.yaml_config
pool = request.app.state.pool
data = await request.json() data = await request.json()
message = (data.get("message") or "Say hello in one sentence.").strip() message = (data.get("message") or "Say hello in one sentence.").strip()
model = (data.get("model") or "").strip() model = (data.get("model") or "").strip()
upstream = cfg.get("upstream_openai", "") upstream = cfg.get("upstream_openai", "")
if not upstream: # Load configured models from DB so the UI can refresh the dropdown
return {"ok": False, "error": "upstream_openai not configured"} async with pool.acquire() as conn:
db_rows = await conn.fetch(
# Always fetch the available model list so the UI can show it "SELECT model_name, base_url FROM models WHERE provider IN ('lm-studio', 'openai') ORDER BY id"
available: list[str] = [] )
try: available = [r["model_name"] for r in db_rows]
async with httpx.AsyncClient(timeout=8.0) as client: db_model_map = {r["model_name"]: r["base_url"] or "" for r in db_rows}
mr = await client.get(f"{upstream.rstrip('/')}/v1/models")
if mr.is_success:
available = sorted(m.get("id", "") for m in mr.json().get("data", []))
except Exception:
pass
if not model: if not model:
if not available: if not available:
return {"ok": False, "error": f"No model specified and none found at {upstream}/v1/models", "available": []} return {"ok": False, "error": "No lm-studio/openai models configured. Add one via Model Manager first.", "available": []}
model = available[0] model = available[0]
url = f"{upstream.rstrip('/')}/v1/chat/completions" # Resolve upstream: prefer the model's own base_url from DB, fall back to upstream_openai config
raw_base = db_model_map.get(model, "") or upstream
if not raw_base:
return {"ok": False, "error": "upstream_openai not configured and model has no base_url", "available": available}
# Strip /v1 suffix — we append /v1/chat/completions ourselves
effective_upstream = raw_base.rstrip("/")
if effective_upstream.endswith("/v1"):
effective_upstream = effective_upstream[:-3]
url = f"{effective_upstream}/v1/chat/completions"
body = { body = {
"model": model, "model": model,
"stream": False, "stream": False,
@@ -2222,11 +2222,12 @@ async def test_chat(request: Request) -> dict:
if not r.is_success: if not r.is_success:
return {"ok": False, "error": f"HTTP {r.status_code}: {r.text[:400]}", "url": url, "model": model, "ms": ms, "available": available} return {"ok": False, "error": f"HTTP {r.status_code}: {r.text[:400]}", "url": url, "model": model, "ms": ms, "available": available}
resp = r.json() resp = r.json()
reply = resp.get("choices", [{}])[0].get("message", {}).get("content", "(empty)") choices = resp.get("choices") or []
reply = choices[0].get("message", {}).get("content", "(empty)") if choices else "(empty response)"
return {"ok": True, "reply": reply, "model": model, "url": url, "ms": ms, "available": available} return {"ok": True, "reply": reply, "model": model, "url": url, "ms": ms, "available": available}
except httpx.ConnectError as exc: except httpx.ConnectError as exc:
ms = int((time.perf_counter() - t0) * 1000) ms = int((time.perf_counter() - t0) * 1000)
return {"ok": False, "error": f"Connection refused — is Ollama running at {upstream}? ({exc})", "url": url, "ms": ms, "available": available} return {"ok": False, "error": f"Connection refused at {effective_upstream} ({exc})", "url": url, "ms": ms, "available": available}
except httpx.TimeoutException: except httpx.TimeoutException:
ms = int((time.perf_counter() - t0) * 1000) ms = int((time.perf_counter() - t0) * 1000)
return {"ok": False, "error": f"Request timed out after {ms}ms", "url": url, "ms": ms, "available": available} return {"ok": False, "error": f"Request timed out after {ms}ms", "url": url, "ms": ms, "available": available}
@@ -3705,7 +3706,7 @@ ADMIN_HTML = """<!DOCTYPE html>
<h2>Test Chat</h2> <h2>Test Chat</h2>
<p style="font-size:0.83em;color:#666;margin-bottom:0.8em"> <p style="font-size:0.83em;color:#666;margin-bottom:0.8em">
Send a message directly to <code>upstream_openai</code> (Ollama) to verify the connection end-to-end. Send a test message via a configured model (lm-studio / openai) to verify the connection end-to-end.
</p> </p>
<div id="tc-models-row" style="font-size:0.82em;color:#555;margin-bottom:0.6em"> <div id="tc-models-row" style="font-size:0.82em;color:#555;margin-bottom:0.6em">
<span id="tc-models-label">Loading available models…</span> <span id="tc-models-label">Loading available models…</span>