Compare commits

...

4 Commits

2 changed files with 72 additions and 70 deletions
+37 -36
View File
@@ -1,38 +1,39 @@
{ {
"version": "v1.10", "version": "v1.10",
"api_keys": {}, "api_keys": {"gnommoweb": "RVWUl_-I1W3EjDpM"},
"auth_login": "", "auth_login": "",
"auth_password": "", "auth_password": "",
"root_password": "", "root_password": "",
"agent_profile": "agent0", "agent_profile": "agent0",
"agent_knowledge_subdir": "custom", "agent_knowledge_subdir": "custom",
"workdir_path": "/a0/usr/workdir", "workdir_path": "/a0/usr/workdir",
"workdir_show": true, "workdir_show": true,
"workdir_max_depth": 5, "workdir_max_depth": 5,
"workdir_max_files": 20, "workdir_max_files": 20,
"workdir_max_folders": 20, "workdir_max_folders": 20,
"workdir_max_lines": 250, "workdir_max_lines": 250,
"workdir_gitignore": "# Python environments & cache\nvenv/**\n**/__pycache__/**\n\n# Node.js dependencies\n**/node_modules/**\n**/.npm/**\n\n# Version control metadata\n**/.git/**", "workdir_gitignore": "# Python environments & cache\nvenv/**\n**/__pycache__/**\n\n# Node.js dependencies\n**/node_modules/**\n**/.npm/**\n\n# Version control metadata\n**/.git/**",
"rfc_auto_docker": true, "rfc_auto_docker": true,
"rfc_url": "localhost", "rfc_url": "localhost",
"rfc_password": "", "rfc_password": "",
"rfc_port_http": 55080, "rfc_port_http": 55080,
"websocket_server_restart_enabled": true, "websocket_server_restart_enabled": true,
"uvicorn_access_logs_enabled": false, "uvicorn_access_logs_enabled": false,
"stt_model_size": "base", "stt_model_size": "base",
"stt_language": "en", "stt_language": "en",
"stt_silence_threshold": 0.3, "stt_silence_threshold": 0.3,
"stt_silence_duration": 1000, "stt_silence_duration": 1000,
"stt_waiting_timeout": 2000, "stt_waiting_timeout": 2000,
"tts_kokoro": true, "tts_kokoro": true,
"mcp_servers": "{\n \"mcpServers\": {}\n}", "mcp_servers": "{\n \"mcpServers\": {}\n}",
"mcp_client_init_timeout": 10, "mcp_client_init_timeout": 10,
"mcp_client_tool_timeout": 120, "mcp_client_tool_timeout": 120,
"mcp_server_enabled": true, "mcp_server_enabled": false,
"a2a_server_enabled": false, "mcp_server_token": "",
"variables": "", "a2a_server_enabled": false,
"secrets": "", "variables": "",
"litellm_global_kwargs": {}, "secrets": "",
"update_check_enabled": true, "litellm_global_kwargs": {},
"chat_inherit_project": true "update_check_enabled": true,
"chat_inherit_project": true
} }
+35 -34
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>