diff --git a/plugins/festinger/festinger/main.py b/plugins/festinger/festinger/main.py index e42cd43..07e0f24 100644 --- a/plugins/festinger/festinger/main.py +++ b/plugins/festinger/festinger/main.py @@ -2162,52 +2162,52 @@ async def update_config(request: Request) -> dict: @app.get("/test-chat/models") 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 upstream = cfg.get("upstream_openai", "") - if not upstream: - return {"ok": False, "error": "upstream_openai not configured", "models": []} - url = f"{upstream.rstrip('/')}/v1/models" - try: - async with httpx.AsyncClient(timeout=8.0) as client: - r = await client.get(url) - if not r.is_success: - return {"ok": False, "error": f"HTTP {r.status_code} from {url}", "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": []} + async with pool.acquire() as conn: + rows = await conn.fetch( + "SELECT model_name, base_url FROM models WHERE provider IN ('lm-studio', 'openai') ORDER BY id" + ) + if rows: + models = [r["model_name"] for r in rows] + return {"ok": True, "models": models, "upstream": upstream} + return {"ok": False, "error": "No lm-studio or openai models configured — add one via Model Manager.", "models": []} @app.post("/test-chat") async def test_chat(request: Request) -> dict: cfg = request.app.state.yaml_config + pool = request.app.state.pool data = await request.json() - message = (data.get("message") or "Say hello in one sentence.").strip() - model = (data.get("model") or "").strip() + message = (data.get("message") or "Say hello in one sentence.").strip() + model = (data.get("model") or "").strip() upstream = cfg.get("upstream_openai", "") - if not upstream: - return {"ok": False, "error": "upstream_openai not configured"} - - # Always fetch the available model list so the UI can show it - available: list[str] = [] - try: - async with httpx.AsyncClient(timeout=8.0) as client: - 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 + # Load configured models from DB so the UI can refresh the dropdown + async with pool.acquire() as conn: + db_rows = await conn.fetch( + "SELECT model_name, base_url FROM models WHERE provider IN ('lm-studio', 'openai') ORDER BY id" + ) + available = [r["model_name"] for r in db_rows] + db_model_map = {r["model_name"]: r["base_url"] or "" for r in db_rows} if not model: 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] - 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 = { "model": model, "stream": False, @@ -2222,11 +2222,12 @@ async def test_chat(request: Request) -> dict: 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} 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} except httpx.ConnectError as exc: 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: ms = int((time.perf_counter() - t0) * 1000) return {"ok": False, "error": f"Request timed out after {ms}ms", "url": url, "ms": ms, "available": available} @@ -3705,7 +3706,7 @@ ADMIN_HTML = """
- Send a message directly to upstream_openai (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.