mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
models: dedupe endpoints by base_url on create (#266)
POST /api/model-endpoints always inserted a new row, so Settings -> Add Models -> Scan for Servers re-added any endpoint a user had already registered manually — once under its model name (from the earlier manual add) and again under its host:port (auto-generated when scan posts without a name). The success toast then misreported the result as "added N new". Look up an existing endpoint with the same base_url accessible to the caller (shared or owned by them) before inserting. If found, return it with `existing: true` so the client can tell the difference between an actual add and a dedupe hit. Toast now reads, e.g., "Found 1 server with 1 model — 1 already added". Tested: POSTing the same base_url three times (incl. trailing-slash variation) returns the same id each time; only one row exists.
This commit is contained in:
@@ -909,6 +909,34 @@ def setup_model_routes(model_discovery):
|
|||||||
require_model_list = _truthy(require_models)
|
require_model_list = _truthy(require_models)
|
||||||
should_probe = require_model_list or not _truthy(skip_probe)
|
should_probe = require_model_list or not _truthy(skip_probe)
|
||||||
|
|
||||||
|
# Dedupe: if an endpoint with the same base_url already exists and
|
||||||
|
# is reachable by the caller (shared or owned by them), return it
|
||||||
|
# instead of creating a duplicate row. Fixes "Scan for Servers"
|
||||||
|
# re-adding manually-added endpoints under their host:port name.
|
||||||
|
from src.auth_helpers import get_current_user as _gcu_dedup
|
||||||
|
_caller = _gcu_dedup(request) or None
|
||||||
|
_db_dedup = SessionLocal()
|
||||||
|
try:
|
||||||
|
existing = (
|
||||||
|
_db_dedup.query(ModelEndpoint)
|
||||||
|
.filter(ModelEndpoint.base_url == base_url)
|
||||||
|
.filter((ModelEndpoint.owner.is_(None)) | (ModelEndpoint.owner == _caller))
|
||||||
|
.order_by(ModelEndpoint.owner.desc()) # prefer owned over shared
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if existing:
|
||||||
|
return {
|
||||||
|
"id": existing.id,
|
||||||
|
"name": existing.name,
|
||||||
|
"base_url": existing.base_url,
|
||||||
|
"models": json.loads(existing.cached_models) if existing.cached_models else [],
|
||||||
|
"online": True,
|
||||||
|
"status": "online",
|
||||||
|
"existing": True,
|
||||||
|
}
|
||||||
|
finally:
|
||||||
|
_db_dedup.close()
|
||||||
|
|
||||||
# Quick model list fetch (1s timeout — if endpoint is slow, it'll update on next refresh)
|
# Quick model list fetch (1s timeout — if endpoint is slow, it'll update on next refresh)
|
||||||
_probe_timeout = 3 if (":11434" in base_url or "ollama" in base_url.lower()) else 1
|
_probe_timeout = 3 if (":11434" in base_url or "ollama" in base_url.lower()) else 1
|
||||||
model_ids = _probe_endpoint(base_url, api_key.strip() or None, timeout=_probe_timeout) if should_probe else []
|
model_ids = _probe_endpoint(base_url, api_key.strip() or None, timeout=_probe_timeout) if should_probe else []
|
||||||
|
|||||||
+12
-4
@@ -952,8 +952,10 @@ function initEndpointForm() {
|
|||||||
msg.textContent = 'No model servers found. Make sure vLLM, llama.cpp, SGLang, or Ollama is running. Docker users may need OLLAMA_HOST=0.0.0.0:11434.';
|
msg.textContent = 'No model servers found. Make sure vLLM, llama.cpp, SGLang, or Ollama is running. Docker users may need OLLAMA_HOST=0.0.0.0:11434.';
|
||||||
msg.className = 'admin-error';
|
msg.className = 'admin-error';
|
||||||
} else {
|
} else {
|
||||||
// Auto-add each discovered endpoint
|
// Auto-add each discovered endpoint. Server dedupes on base_url
|
||||||
|
// and returns `existing: true` for already-registered ones.
|
||||||
let added = 0;
|
let added = 0;
|
||||||
|
let skipped = 0;
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const base = item.url.replace('/chat/completions', '').replace(/\/$/, '');
|
const base = item.url.replace('/chat/completions', '').replace(/\/$/, '');
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
@@ -961,12 +963,18 @@ function initEndpointForm() {
|
|||||||
fd.append('skip_probe', 'false');
|
fd.append('skip_probe', 'false');
|
||||||
const r = await fetch('/api/model-endpoints', { method: 'POST', body: fd });
|
const r = await fetch('/api/model-endpoints', { method: 'POST', body: fd });
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
added++;
|
try {
|
||||||
try { const dd = await r.json(); if (dd && dd.id) _recentlyAddedEpId = String(dd.id); } catch (_) {}
|
const dd = await r.json();
|
||||||
|
if (dd && dd.existing) { skipped++; }
|
||||||
|
else { added++; if (dd && dd.id) _recentlyAddedEpId = String(dd.id); }
|
||||||
|
} catch (_) { added++; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const totalModels = items.reduce((n, i) => n + (i.models ? i.models.length : 0), 0);
|
const totalModels = items.reduce((n, i) => n + (i.models ? i.models.length : 0), 0);
|
||||||
msg.innerHTML = `Found ${items.length} server${items.length !== 1 ? 's' : ''} with ${totalModels} model${totalModels !== 1 ? 's' : ''}` + (added ? ` — added ${added} new` : ' (already added)');
|
const parts = [`Found ${items.length} server${items.length !== 1 ? 's' : ''} with ${totalModels} model${totalModels !== 1 ? 's' : ''}`];
|
||||||
|
if (added) parts.push(`added ${added} new`);
|
||||||
|
if (skipped) parts.push(`${skipped} already added`);
|
||||||
|
msg.innerHTML = parts.join(' — ');
|
||||||
msg.className = 'admin-success';
|
msg.className = 'admin-success';
|
||||||
loadEndpoints();
|
loadEndpoints();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user