feat: model registry Phase 2 — cloud provider UI (Anthropic + Google)
Adds cloud provider management to /settings/models: - Google Accounts section: add/remove Gemini API keys with labels - Add Model form: provider tabs (Local / Google / Anthropic) with catalog dropdowns that auto-fill label and context_k - Provider badges on model rows (Anthropic / Google / Local) - /settings/local now redirects to /settings/models (canonical URL) - save_cloud_model() in model_registry for Anthropic/Google entries - Distill role migration restored in _migrate_from_local_llm - Test fixes: version assertions updated to V2 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -296,6 +296,8 @@ def _migrate_from_local_llm(username: str, path: Path) -> dict:
|
||||
active_id = old.get("active_model_id")
|
||||
if active_id and any(m["id"] == active_id for m in data["models"]):
|
||||
data["roles"]["chat"] = {"primary": active_id}
|
||||
if settings.distill_backend_mid == "local":
|
||||
data["roles"]["distill"] = {"primary": active_id}
|
||||
|
||||
# Migrate Gemini key from auth.json
|
||||
data = _migrate_v1_to_v2(username, {"version": 1, **data})
|
||||
@@ -613,6 +615,52 @@ def save_model(username: str, model_id: str | None, host_id: str,
|
||||
return model_id
|
||||
|
||||
|
||||
def save_cloud_model(username: str, model_id: str | None,
|
||||
provider: str, model_name: str, label: str,
|
||||
account_id: str | None = None,
|
||||
credential_id: str | None = None,
|
||||
context_k: int = 0,
|
||||
tags: list[str] | None = None) -> str:
|
||||
"""
|
||||
Create or update an Anthropic or Google model entry. Returns the model ID.
|
||||
|
||||
provider: "anthropic" | "google"
|
||||
account_id: Google only — references providers.google.accounts[].id
|
||||
credential_id: Anthropic only — e.g. "cli"
|
||||
"""
|
||||
_TYPE = {"google": "gemini_api", "anthropic": "claude_cli"}
|
||||
entry_type = _TYPE.get(provider, "gemini_api")
|
||||
data = _load(username)
|
||||
tags = tags or []
|
||||
|
||||
entry: dict = {
|
||||
"type": entry_type,
|
||||
"label": label.strip() or model_name.strip(),
|
||||
"model_name": model_name.strip(),
|
||||
"provider": provider,
|
||||
"context_k": context_k,
|
||||
"tags": tags,
|
||||
}
|
||||
if account_id:
|
||||
entry["account_id"] = account_id
|
||||
if credential_id:
|
||||
entry["credential_id"] = credential_id
|
||||
|
||||
if model_id:
|
||||
for m in data["models"]:
|
||||
if m["id"] == model_id:
|
||||
m.update(entry)
|
||||
_save(username, data)
|
||||
return model_id
|
||||
model_id = None
|
||||
|
||||
model_id = secrets.token_hex(4)
|
||||
entry["id"] = model_id
|
||||
data["models"].append(entry)
|
||||
_save(username, data)
|
||||
return model_id
|
||||
|
||||
|
||||
def remove_model(username: str, model_id: str) -> bool:
|
||||
"""Remove a model and clear any role assignments pointing to it."""
|
||||
data = _load(username)
|
||||
|
||||
Reference in New Issue
Block a user