Skip to content

Commit a83d3da

Browse files
ZD Studiosclaude
andcommitted
fix: model picker is a real dropdown (was a datalist that didn't show) + robust fetch
The 'fetch models' felt broken because it used an <input list=datalist> — browsers only reveal those while typing, so the list looked empty. Now it's a proper <select> dropdown that clearly shows every model; picking one fills the field, with a 'custom' option to type your own. - Backend /api/models returns {models, base, error} (was a bare list) with a 15s timeout and a claude-code-api fallback, so the UI can show what happened / why. - Verified: /api/models returns base + 343 models + empty error; the select populates and pickModel sets the model field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8b05844 commit a83d3da

2 files changed

Lines changed: 37 additions & 17 deletions

File tree

aios_hub.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,33 @@ def _llm_key() -> str:
112112
or os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY") or "sk-aios")
113113

114114

115-
def fetch_provider_models() -> list:
116-
"""List the models the active provider serves (e.g. your Claude models via claude-code-api)."""
115+
def _models_from(url: str, key: str = "") -> list:
116+
hdr = {"Authorization": f"Bearer {key}"} if key else {}
117+
req = urllib.request.Request(url.rstrip("/") + "/models", headers=hdr)
118+
data = json.loads(urllib.request.urlopen(req, timeout=15).read())
119+
# OpenAI shape {"data":[{"id":..}]} or a bare list
120+
items = data.get("data", data) if isinstance(data, dict) else data
121+
return [m.get("id") for m in items if isinstance(m, dict) and m.get("id")]
122+
123+
124+
def fetch_provider_models() -> dict:
125+
"""List models the active provider serves (your Claude models via claude-code-api, etc.).
126+
Returns {models, base, error} so the UI can show what happened."""
127+
base, key = llm_base(), _llm_key()
128+
out = {"models": [], "base": base, "error": ""}
117129
try:
118-
req = urllib.request.Request(llm_base() + "/models",
119-
headers={"Authorization": f"Bearer {_llm_key()}"})
120-
data = json.loads(urllib.request.urlopen(req, timeout=8).read())
121-
return [m.get("id") for m in data.get("data", []) if m.get("id")]
122-
except Exception:
123-
return []
130+
out["models"] = _models_from(base, key)
131+
except Exception as e:
132+
out["error"] = str(e)[:200]
133+
# Fallback: if nothing came back, try claude-code-api directly (common case).
134+
if not out["models"] and CLAUDECODE.rstrip("/") + "/v1" != base:
135+
try:
136+
m = _models_from(CLAUDECODE + "/v1")
137+
if m:
138+
out.update(models=m, base=CLAUDECODE + "/v1", error="")
139+
except Exception:
140+
pass
141+
return out
124142

125143

126144
# --------------------------------------------------------------------------- #
@@ -393,7 +411,7 @@ def do_GET(self):
393411
elif self.path == "/api/system_prompt":
394412
self._send(200, {"prompt": read_system_prompt()})
395413
elif self.path == "/api/models":
396-
self._send(200, {"models": fetch_provider_models()})
414+
self._send(200, fetch_provider_models())
397415
elif self.path in ("/v1/models", "/api/v1/models"):
398416
# AIOS as an OpenAI-compatible API: its "models" are the chat targets.
399417
now = int(time.time())

docs/dashboard.html

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,9 @@ <h3>🔑 Model provider</h3>
280280
<div class="grid2" style="margin-top:16px">
281281
<div class="field"><label>Provider</label>
282282
<select id="sProvider"><option>openrouter</option><option>anthropic</option><option>openai</option><option>gemini</option><option value="claudecode">claude (Pro/Max)</option></select></div>
283-
<div class="field"><label>Default model <a onclick="loadModels()" style="cursor:pointer;font-size:.75rem">↻ fetch my models</a></label>
284-
<input id="sModel" list="modelList" placeholder="anthropic/claude-opus-4.6" autocomplete="off">
285-
<datalist id="modelList"></datalist>
283+
<div class="field"><label>Default model &nbsp;<a onclick="loadModels()" style="cursor:pointer;font-size:.75rem;color:var(--accent-strong)">↻ fetch my models</a></label>
284+
<select id="sModelSel" onchange="pickModel(this.value)" style="width:100%;margin-bottom:8px"><option value="">↻ click "fetch my models" to load…</option></select>
285+
<input id="sModel" placeholder="or type a model id" autocomplete="off">
286286
<div class="hint" id="modelListHint"></div></div>
287287
</div>
288288
<div class="field"><label>API key</label><input id="sKey" type="password" placeholder="paste your key"><div class="hint" id="keyHint"></div></div>
@@ -435,12 +435,14 @@ <h3>${ICON[key]} ${key} <span class="badge ${up?'up':'down'}">${up?'running':'st
435435
await fetch('/api/env',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({updates})});
436436
document.getElementById('modelMsg').textContent='saved — restart agents to apply';document.getElementById('sKey').value='';toast('Model settings saved');loadSettings();
437437
}
438+
function pickModel(v){if(v&&v!=='__custom__')document.getElementById('sModel').value=v;}
438439
async function loadModels(){
439-
const hint=document.getElementById('modelListHint');hint.innerHTML='<span class="spin"></span> fetching…';
440-
try{const r=await(await fetch('/api/models')).json();const list=document.getElementById('modelList');
441-
list.innerHTML=(r.models||[]).map(m=>`<option value="${m}">`).join('');
442-
hint.textContent=(r.models&&r.models.length)?(r.models.length+' models — click the field to pick one'):'No models found — connect a provider / log in first.';
443-
}catch(e){hint.textContent='could not fetch models';}
440+
const sel=document.getElementById('sModelSel'),hint=document.getElementById('modelListHint');
441+
hint.innerHTML='<span class="spin"></span> fetching…';
442+
try{const r=await(await fetch('/api/models')).json();const models=r.models||[];const cur=document.getElementById('sModel').value;
443+
sel.innerHTML='<option value="">— pick a model —</option>'+models.map(m=>`<option value="${m}" ${m===cur?'selected':''}>${m}</option>`).join('')+'<option value="__custom__">✎ custom (type below)</option>';
444+
hint.textContent=models.length?(models.length+' models loaded from '+(r.base||'provider')):(r.error?('couldn\'t fetch: '+r.error):'No models — connect a provider or run aios claude-login first.');
445+
}catch(e){sel.innerHTML='<option value="">fetch failed</option>';hint.textContent='could not reach the hub: '+e;}
444446
}
445447
async function connectClaude(){
446448
document.getElementById('claudeMsg').innerHTML='<span class="spin"></span> connecting…';

0 commit comments

Comments
 (0)