diff --git a/dashboard/app.py b/dashboard/app.py index 4077ee3..c8bf038 100644 --- a/dashboard/app.py +++ b/dashboard/app.py @@ -224,13 +224,89 @@ def verify_magic_link(token: str, response: Response, db: Session = Depends(get_ @app.get("/api/me") def me(user: dict = Depends(require_auth), db: Session = Depends(get_db)): u = db.query(User).filter(User.id == user["user_id"]).first() + llm = u.llm_config or {} return { "id": u.id, "username": u.username, "email": u.email or "", "display_name": u.display_name, "role": u.role, + "has_llm": bool(llm.get("api_key")), + "llm_provider": llm.get("provider_type", ""), + "llm_model": llm.get("default_model", ""), "created_at": u.created_at.isoformat() if u.created_at else None, } +class UserLLMConfig(BaseModel): + provider_type: str = "" # anthropic, openai, litellm, ollama + api_url: str = "" + api_key: str = "" + default_model: str = "" + + +@app.get("/api/me/llm") +def get_my_llm(user: dict = Depends(require_auth), db: Session = Depends(get_db)): + """Get current user's LLM config.""" + u = db.query(User).filter(User.id == user["user_id"]).first() + llm = u.llm_config or {} + return { + "provider_type": llm.get("provider_type", ""), + "api_url": llm.get("api_url", ""), + "api_key": "***" if llm.get("api_key") else "", + "default_model": llm.get("default_model", ""), + "configured": bool(llm.get("api_key")), + } + + +@app.put("/api/me/llm") +def update_my_llm(data: UserLLMConfig, user: dict = Depends(require_auth), db: Session = Depends(get_db)): + """Update current user's LLM config (bring your own LLM).""" + u = db.query(User).filter(User.id == user["user_id"]).first() + current = u.llm_config or {} + update = data.model_dump() + # Only update api_key if a real value was provided (not "***") + if update.get("api_key") == "***" or not update.get("api_key"): + update["api_key"] = current.get("api_key", "") + u.llm_config = update + db.commit() + return {"status": "updated"} + + +@app.delete("/api/me/llm") +def delete_my_llm(user: dict = Depends(require_auth), db: Session = Depends(get_db)): + """Remove user's LLM config (fall back to system default).""" + u = db.query(User).filter(User.id == user["user_id"]).first() + u.llm_config = {} + db.commit() + return {"status": "removed"} + + +@app.get("/api/users/{user_id}/llm") +def get_user_llm(user_id: int, db: Session = Depends(get_db)): + """Internal: resolve LLM config for a user. Returns user's own config if set, otherwise system default.""" + u = db.query(User).filter(User.id == user_id).first() + if not u: + raise HTTPException(status_code=404) + user_llm = u.llm_config or {} + if user_llm.get("api_key"): + return { + "source": "user", + "provider_type": user_llm.get("provider_type", ""), + "api_url": user_llm.get("api_url", ""), + "api_key": user_llm["api_key"], + "default_model": user_llm.get("default_model", ""), + } + # Fall back to system default + default = db.query(LLMProvider).filter(LLMProvider.is_default == True).first() + if default: + return { + "source": "system", + "provider_type": default.provider_type, + "api_url": default.api_url, + "api_key": default.api_key, + "default_model": default.default_model, + } + return {"source": "none", "provider_type": "", "api_url": "", "api_key": "", "default_model": ""} + + # --- Health --- @app.get("/api/health") diff --git a/dashboard/models.py b/dashboard/models.py index 33e62e4..05940cd 100644 --- a/dashboard/models.py +++ b/dashboard/models.py @@ -13,6 +13,7 @@ class User(Base): password_hash = Column(String, nullable=False) display_name = Column(String, default="") role = Column(String, default="user") # admin or user + llm_config = Column(JSON, default=dict) # user's own LLM provider config created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) instances = relationship("AgentInstance", back_populates="user") diff --git a/dashboard/static/index.html b/dashboard/static/index.html index 5c20093..2fcdbb6 100644 --- a/dashboard/static/index.html +++ b/dashboard/static/index.html @@ -106,6 +106,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
0 agents + @@ -340,6 +341,71 @@ async function enableAgent(catalogId,name){ if(res.ok){closeModal();refresh()} } +// --- LLM Settings --- +async function showLLMSettings(){ + const res=await fetch(API+'/api/me/llm'); + if(res.status===401){location.href='/login';return} + const llm=await res.json(); + document.getElementById('modal-content').innerHTML=` + +Bring your own LLM API key, or leave blank to use the system default.
++ ${llm.configured?'Status: Configured ('+llm.provider_type+')':'Status: Using system default'} +
+