Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a219c68aa0 | |||
| 89570b3837 | |||
| 91c22163f7 | |||
| c3507f25c9 |
+37
-36
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user