diff --git a/router.py b/router.py index 6352024..4f6503f 100644 --- a/router.py +++ b/router.py @@ -122,6 +122,20 @@ async def _ensure_success(resp: aiohttp.ClientResponse) -> None: if resp.status >= 400: text = await resp.text() raise HTTPException(status_code=resp.status, detail=text) + +def is_ext_openai_endpoint(endpoint: str) -> bool: + if "/v1" not in endpoint: + return False + + base_endpoint = endpoint.replace('/v1', '') + if base_endpoint in config.endpoints: + return False # It's Ollama's /v1 + + # Check for default Ollama port + if ':11434' in endpoint: + return False # It's Ollama + + return True # It's an external OpenAI endpoint class fetch: async def available_models(endpoint: str, api_key: Optional[str] = None) -> Set[str]: @@ -508,8 +522,13 @@ async def choose_endpoint(model: str) -> str: # 6️⃣ if not candidate_endpoints: if ":latest" in model: #ollama naming convention not applicable to openai - model = model.split(":latest") - model = model[0] + model_without_latest = model.split(":latest")[0] + candidate_endpoints = [ + ep for ep, models in zip(config.endpoints, advertised_sets) + if model_without_latest in models and is_ext_openai_endpoint(ep) + ] + if not candidate_endpoints: + model = model + ":latest" candidate_endpoints = [ ep for ep, models in zip(config.endpoints, advertised_sets) if model in models diff --git a/static/index.html b/static/index.html index dd0fcd1..ef6119b 100644 --- a/static/index.html +++ b/static/index.html @@ -424,76 +424,6 @@ }); }); - /* show logic */ - document.body.addEventListener("click", async (e) => { - if (!e.target.matches(".show-link")) return; - e.preventDefault(); - const model = e.target.dataset.model; - try { - const resp = await fetch( - `/api/show?model=${encodeURIComponent(model)}`, - { method: "POST" }, - ); - if (!resp.ok) - throw new Error(`Status ${resp.status}`); - const data = await resp.json(); - document.getElementById("json-output").textContent = - JSON.stringify(data, null, 2).replace( - /\\n/g, - "\n", - ); - document.getElementById( - "show-modal", - ).style.display = "flex"; - } catch (err) { - console.error(err); - alert( - `Could not load model details: ${err.message}`, - ); - } - }); - - /* pull logic */ - document - .getElementById("pull-btn") - .addEventListener("click", async () => { - const model = document - .getElementById("pull-model-input") - .value.trim(); - const statusEl = - document.getElementById("pull-status"); - if (!model) { - alert("Please enter a model name."); - return; - } - try { - const resp = await fetch( - `/api/pull?model=${encodeURIComponent(model)}`, - { method: "POST" }, - ); - if (!resp.ok) - throw new Error(`Status ${resp.status}`); - const data = await resp.json(); - statusEl.textContent = `✅ ${data.status}`; - statusEl.style.color = "green"; - loadTags(); - } catch (err) { - console.error(err); - statusEl.textContent = `❌ ${err.message}`; - statusEl.style.color = "red"; - } - }); - - /* modal close */ - const modal = document.getElementById("show-modal"); - modal.addEventListener("click", (e) => { - if ( - e.target === modal || - e.target.matches(".close-btn") - ) { - modal.style.display = "none"; - } - }); } catch (e) { console.error(e); } @@ -592,6 +522,77 @@ loadUsage(); setInterval(loadPS, 60_000); setInterval(loadEndpoints, 300_000); + + /* show logic */ + document.body.addEventListener("click", async (e) => { + if (!e.target.matches(".show-link")) return; + e.preventDefault(); + const model = e.target.dataset.model; + try { + const resp = await fetch( + `/api/show?model=${encodeURIComponent(model)}`, + { method: "POST" }, + ); + if (!resp.ok) + throw new Error(`Status ${resp.status}`); + const data = await resp.json(); + document.getElementById("json-output").textContent = + JSON.stringify(data, null, 2).replace( + /\\n/g, + "\n", + ); + document.getElementById( + "show-modal", + ).style.display = "flex"; + } catch (err) { + console.error(err); + alert( + `Could not load model details: ${err.message}`, + ); + } + }); + + /* pull logic */ + document + .getElementById("pull-btn") + .addEventListener("click", async () => { + const model = document + .getElementById("pull-model-input") + .value.trim(); + const statusEl = + document.getElementById("pull-status"); + if (!model) { + alert("Please enter a model name."); + return; + } + try { + const resp = await fetch( + `/api/pull?model=${encodeURIComponent(model)}`, + { method: "POST" }, + ); + if (!resp.ok) + throw new Error(`Status ${resp.status}`); + const data = await resp.json(); + statusEl.textContent = `✅ ${data.status}`; + statusEl.style.color = "green"; + loadTags(); + } catch (err) { + console.error(err); + statusEl.textContent = `❌ ${err.message}`; + statusEl.style.color = "red"; + } + }); + + /* modal close */ + const modal = document.getElementById("show-modal"); + modal.addEventListener("click", (e) => { + if ( + e.target === modal || + e.target.matches(".close-btn") + ) { + modal.style.display = "none"; + } + }); });