mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
Show user all models they can call (Across teams) on UI (#11948)
* fix(proxy_cli.py): check for module not found error on proxy import
Fixes https://github.com/BerriAI/litellm/issues/11836
* feat(proxy_server.py): utility function to get all models across all teams user is in
Allows user to see all team models on UI
* feat(proxy_server.py): return models accessible via team id's in `/v2/model/info`
Allows UI to tell user which team they can use to access model
* feat(columsn.tsx): initial PR to add 'accessible via Team ID's on model hub
allows user to know what teams they can access a model through
* Revert "feat(columsn.tsx): initial PR to add 'accessible via Team ID's on model hub"
This reverts commit f844c79383.
* fix(proxy_server.py): backend model info endpoint improvements
* UI Improvements for Default User access (#11952)
* feat(ui/): add a 'current team' and 'view' filters to the models page
allow user to see what all models they have access to within a specific team
* feat: working ui for seeing models in teams
* fix(model_dashboard.tsx): make current team filter more prominent
* style(model_dashboard.tsx): add a helpful note telling user how to create a model for the team they've selected
* style(model_dashboard.tsx): only show helpful note when current view is team, not for global
* fix(team_dropdown.tsx): allow searching by team id on create key modal
* feat(create_key_button.tsx): add helpful message when team selection is required
* fix: fix linting checks
* fix: fix ui linting error
* docs(team_endpoints.py): document new param
This commit is contained in:
parent
6c17e856d1
commit
2404d3fb1b
12 changed files with 521 additions and 65 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,6 @@
|
|||
3:I[78401,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","402","static/chunks/402-d418d0a7bf158a22.js","313","static/chunks/313-fe5a1ed341fdff45.js","899","static/chunks/899-dd9d2ed8efc1b4ac.js","339","static/chunks/339-9ad819586efb0d68.js","250","static/chunks/250-46ca089b25e2e50d.js","699","static/chunks/699-2e0b76ba9cd1d301.js","931","static/chunks/app/page-cbefa9dd90304eaf.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["H40ScCE0aF9WfwOsAFKzy",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["H40ScCE0aF9WfwOsAFKzy",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
3:I[52829,["402","static/chunks/402-d418d0a7bf158a22.js","313","static/chunks/313-fe5a1ed341fdff45.js","250","static/chunks/250-46ca089b25e2e50d.js","699","static/chunks/699-2e0b76ba9cd1d301.js","418","static/chunks/app/model_hub/page-ce40c5a05f3174ca.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["H40ScCE0aF9WfwOsAFKzy",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["H40ScCE0aF9WfwOsAFKzy",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","402","static/chunks/402-d418d0a7bf158a22.js","899","static/chunks/899-dd9d2ed8efc1b4ac.js","250","static/chunks/250-46ca089b25e2e50d.js","461","static/chunks/app/onboarding/page-e05c770288debeda.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["H40ScCE0aF9WfwOsAFKzy",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["H40ScCE0aF9WfwOsAFKzy",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ import os
|
|||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.parse as urlparse
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
import click
|
||||
import httpx
|
||||
from dotenv import load_dotenv
|
||||
import urllib.parse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import FastAPI
|
||||
else:
|
||||
|
|
@ -539,6 +540,10 @@ def run_server( # noqa: PLR0915
|
|||
app,
|
||||
save_worker_config,
|
||||
)
|
||||
except ModuleNotFoundError as e:
|
||||
raise ModuleNotFoundError(
|
||||
f"Missing dependency {e}. Run `pip install 'litellm[proxy]'`"
|
||||
)
|
||||
except ImportError as e:
|
||||
if "litellm[proxy]" in str(e):
|
||||
# user is missing a proxy dependency, ask them to pip install litellm[proxy]
|
||||
|
|
@ -788,7 +793,9 @@ def run_server( # noqa: PLR0915
|
|||
|
||||
# Skip server startup if requested (after all setup is done)
|
||||
if skip_server_startup:
|
||||
print("LiteLLM: Setup complete. Skipping server startup as requested.") # noqa
|
||||
print( # noqa
|
||||
"LiteLLM: Setup complete. Skipping server startup as requested."
|
||||
)
|
||||
return
|
||||
|
||||
uvicorn_args = ProxyInitializationHelpers._get_default_unvicorn_init_args(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ from typing import (
|
|||
TYPE_CHECKING,
|
||||
Any,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
|
|
@ -5531,6 +5533,139 @@ async def non_admin_all_models(
|
|||
return unique_models
|
||||
|
||||
|
||||
async def get_all_team_models(
|
||||
user_teams: Union[List[str], Literal["*"]],
|
||||
prisma_client: PrismaClient,
|
||||
llm_router: Router,
|
||||
) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Get all models across all teams user is in.
|
||||
|
||||
1. Get all teams user is in
|
||||
2. Get all models across all teams
|
||||
3. Return {"model_id": ["team_id1", "team_id2"]}
|
||||
"""
|
||||
team_models: Dict[str, Set[str]] = {}
|
||||
team_db_objects_typed: List[LiteLLM_TeamTable] = []
|
||||
|
||||
if user_teams == "*":
|
||||
team_db_objects = await prisma_client.db.litellm_teamtable.find_many()
|
||||
team_db_objects_typed = [
|
||||
LiteLLM_TeamTable(**team_db_object.model_dump())
|
||||
for team_db_object in team_db_objects
|
||||
]
|
||||
else:
|
||||
team_db_objects = await prisma_client.db.litellm_teamtable.find_many(
|
||||
where={"team_id": {"in": user_teams}}
|
||||
)
|
||||
team_db_objects_typed = [
|
||||
LiteLLM_TeamTable(**team_db_object.model_dump())
|
||||
for team_db_object in team_db_objects
|
||||
]
|
||||
|
||||
for team_object in team_db_objects_typed:
|
||||
if (
|
||||
len(team_object.models) == 0 # empty list = all model access
|
||||
or SpecialModelNames.all_proxy_models.value in team_object.models
|
||||
):
|
||||
_model_ids = llm_router.get_model_ids()
|
||||
if _model_ids is not None:
|
||||
for model_id in _model_ids:
|
||||
team_models.setdefault(model_id, set()).add(team_object.team_id)
|
||||
else:
|
||||
for model_name in team_object.models:
|
||||
_models = llm_router.get_model_list(model_name=model_name)
|
||||
if _models is not None:
|
||||
for model in _models:
|
||||
model_id = model.get("model_info", {}).get("id", None)
|
||||
if model_id is not None:
|
||||
team_models.setdefault(model_id, set()).add(
|
||||
team_object.team_id
|
||||
)
|
||||
|
||||
# convert set to list
|
||||
returned_team_models: Dict[str, List[str]] = {}
|
||||
for model_id, team_ids in team_models.items():
|
||||
returned_team_models[model_id] = list(team_ids)
|
||||
|
||||
return returned_team_models
|
||||
|
||||
|
||||
def get_direct_access_models(
|
||||
user_db_object: LiteLLM_UserTable,
|
||||
llm_router: Router,
|
||||
) -> List[str]:
|
||||
"""
|
||||
Get all models that user has direct access to
|
||||
"""
|
||||
|
||||
direct_access_models: List[str] = []
|
||||
for model in user_db_object.models:
|
||||
deployments = llm_router.get_model_list(model_name=model)
|
||||
if deployments is not None:
|
||||
for deployment in deployments:
|
||||
model_id = deployment.get("model_info", {}).get("id", None)
|
||||
if model_id is not None:
|
||||
direct_access_models.append(model_id)
|
||||
return direct_access_models
|
||||
|
||||
|
||||
async def get_all_team_and_direct_access_models(
|
||||
user_api_key_dict: UserAPIKeyAuth,
|
||||
prisma_client: PrismaClient,
|
||||
llm_router: Router,
|
||||
all_models: List[Dict],
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get all models across all teams user is in.
|
||||
"""
|
||||
|
||||
user_teams: Optional[Union[List[str], Literal["*"]]] = None
|
||||
direct_access_models: List[str] = []
|
||||
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN:
|
||||
user_teams = "*"
|
||||
direct_access_models = llm_router.get_model_ids() # has access to all models
|
||||
else:
|
||||
user_db_object = await prisma_client.db.litellm_usertable.find_unique(
|
||||
where={"user_id": user_api_key_dict.user_id}
|
||||
)
|
||||
if user_db_object is not None:
|
||||
user_object = LiteLLM_UserTable(**user_db_object.model_dump())
|
||||
user_teams = user_object.teams or []
|
||||
direct_access_models = get_direct_access_models(
|
||||
user_db_object=user_object,
|
||||
llm_router=llm_router,
|
||||
)
|
||||
## ADD ACCESS_VIA_TEAM_IDS TO ALL MODELS
|
||||
if user_teams is not None:
|
||||
team_models = await get_all_team_models(
|
||||
user_teams=user_teams,
|
||||
prisma_client=prisma_client,
|
||||
llm_router=llm_router,
|
||||
)
|
||||
for _model in all_models:
|
||||
model_id = _model.get("model_info", {}).get("id", None)
|
||||
if model_id is not None:
|
||||
_model["model_info"]["access_via_team_ids"] = team_models.get(
|
||||
model_id, []
|
||||
)
|
||||
|
||||
## ADD DIRECT_ACCESS TO RELEVANT MODELS
|
||||
for _model in all_models:
|
||||
model_id = _model.get("model_info", {}).get("id", None)
|
||||
if model_id is not None and model_id in direct_access_models:
|
||||
_model["model_info"]["direct_access"] = True
|
||||
|
||||
## FILTER OUT MODELS THAT ARE NOT IN DIRECT_ACCESS_MODELS OR ACCESS_VIA_TEAM_IDS - only show user models they can call
|
||||
all_models = [
|
||||
_model
|
||||
for _model in all_models
|
||||
if _model.get("model_info", {}).get("direct_access", False)
|
||||
or _model.get("model_info", {}).get("access_via_team_ids", [])
|
||||
]
|
||||
return all_models
|
||||
|
||||
|
||||
@router.get(
|
||||
"/v2/model/info",
|
||||
description="v2 - returns models available to the user based on their API key permissions. Shows model info from config.yaml (except api key and api base). Filter to just user-added models with ?user_models_only=true",
|
||||
|
|
@ -5546,6 +5681,9 @@ async def model_info_v2(
|
|||
user_models_only: Optional[bool] = fastapi.Query(
|
||||
False, description="Only return models added by this user"
|
||||
),
|
||||
include_team_models: Optional[bool] = fastapi.Query(
|
||||
False, description="Return all models across all teams user is in."
|
||||
),
|
||||
debug: Optional[bool] = False,
|
||||
):
|
||||
"""
|
||||
|
|
@ -5561,6 +5699,12 @@ async def model_info_v2(
|
|||
},
|
||||
)
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail={"error": CommonProxyErrors.db_not_connected_error.value},
|
||||
)
|
||||
|
||||
# Load existing config
|
||||
await proxy_config.get_config()
|
||||
all_models = copy.deepcopy(llm_router.model_list)
|
||||
|
|
@ -5569,10 +5713,6 @@ async def model_info_v2(
|
|||
# if user does not use a config.yaml, https://github.com/BerriAI/litellm/issues/2061
|
||||
all_models += [user_model]
|
||||
|
||||
# check all models user has access to in user_api_key_dict
|
||||
if len(user_api_key_dict.models) > 0:
|
||||
pass
|
||||
|
||||
if model is not None:
|
||||
all_models = [m for m in all_models if m["model_name"] == model]
|
||||
|
||||
|
|
@ -5584,6 +5724,13 @@ async def model_info_v2(
|
|||
prisma_client=prisma_client,
|
||||
)
|
||||
|
||||
if include_team_models:
|
||||
all_models = await get_all_team_and_direct_access_models(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
prisma_client=prisma_client,
|
||||
llm_router=llm_router,
|
||||
all_models=all_models,
|
||||
)
|
||||
# fill in model info based on config.yaml and litellm model_prices_and_context_window.json
|
||||
for _model in all_models:
|
||||
# provided model_info in config.yaml
|
||||
|
|
@ -5635,10 +5782,7 @@ async def model_info_v2(
|
|||
_model["model_info"] = model_info
|
||||
# don't return the api key / vertex credentials
|
||||
# don't return the llm credentials
|
||||
_model["litellm_params"].pop("api_key", None)
|
||||
_model["litellm_params"].pop("vertex_credentials", None)
|
||||
_model["litellm_params"].pop("aws_access_key_id", None)
|
||||
_model["litellm_params"].pop("aws_secret_access_key", None)
|
||||
_model = remove_sensitive_info_from_deployment(_model)
|
||||
|
||||
verbose_proxy_logger.debug("all_models: %s", all_models)
|
||||
return {"data": all_models}
|
||||
|
|
@ -8024,7 +8168,7 @@ async def delete_callback(
|
|||
Delete specific logging callback from configuration.
|
||||
"""
|
||||
global prisma_client, proxy_config
|
||||
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
|
@ -8054,24 +8198,28 @@ async def delete_callback(
|
|||
# Get current configuration
|
||||
config = await proxy_config.get_config()
|
||||
callback_name = data.callback_name.lower()
|
||||
|
||||
|
||||
# Check if callback exists in current configuration
|
||||
litellm_settings = config.get("litellm_settings", {})
|
||||
success_callbacks = litellm_settings.get("success_callback", [])
|
||||
|
||||
|
||||
if callback_name not in success_callbacks:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail={"error": f"Callback '{callback_name}' not found in active configuration"},
|
||||
detail={
|
||||
"error": f"Callback '{callback_name}' not found in active configuration"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# Remove callback from success_callback list
|
||||
success_callbacks.remove(callback_name)
|
||||
config.setdefault("litellm_settings", {})["success_callback"] = success_callbacks
|
||||
|
||||
config.setdefault("litellm_settings", {})[
|
||||
"success_callback"
|
||||
] = success_callbacks
|
||||
|
||||
# Save the updated configuration
|
||||
await proxy_config.save_config(new_config=config)
|
||||
|
||||
|
||||
# Restart the proxy to apply changes
|
||||
await proxy_config.add_deployment(
|
||||
prisma_client=prisma_client, proxy_logging_obj=proxy_logging_obj
|
||||
|
|
|
|||
|
|
@ -267,3 +267,170 @@ def test_embedding_input_array_of_tokens(mock_aembedding, client_no_auth):
|
|||
assert len(result["data"][0]["embedding"]) > 10 # this usually has len==1536 so
|
||||
except Exception as e:
|
||||
pytest.fail(f"LiteLLM Proxy test failed. Exception - {str(e)}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_team_models():
|
||||
"""
|
||||
Test get_all_team_models function with both "*" and specific team IDs
|
||||
"""
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from litellm.proxy._types import LiteLLM_TeamTable
|
||||
from litellm.proxy.proxy_server import get_all_team_models
|
||||
|
||||
# Mock team data
|
||||
mock_team1 = MagicMock()
|
||||
mock_team1.model_dump.return_value = {
|
||||
"team_id": "team1",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"],
|
||||
"team_alias": "Team 1",
|
||||
}
|
||||
|
||||
mock_team2 = MagicMock()
|
||||
mock_team2.model_dump.return_value = {
|
||||
"team_id": "team2",
|
||||
"models": ["claude-3", "gpt-4"],
|
||||
"team_alias": "Team 2",
|
||||
}
|
||||
|
||||
# Mock model data returned by router
|
||||
mock_models_gpt4 = [
|
||||
{"model_info": {"id": "gpt-4-model-1"}},
|
||||
{"model_info": {"id": "gpt-4-model-2"}},
|
||||
]
|
||||
mock_models_gpt35 = [
|
||||
{"model_info": {"id": "gpt-3.5-turbo-model-1"}},
|
||||
]
|
||||
mock_models_claude = [
|
||||
{"model_info": {"id": "claude-3-model-1"}},
|
||||
]
|
||||
|
||||
# Mock prisma client
|
||||
mock_prisma_client = MagicMock()
|
||||
mock_db = MagicMock()
|
||||
mock_litellm_teamtable = MagicMock()
|
||||
|
||||
mock_prisma_client.db = mock_db
|
||||
mock_db.litellm_teamtable = mock_litellm_teamtable
|
||||
|
||||
# Make find_many async
|
||||
mock_litellm_teamtable.find_many = AsyncMock()
|
||||
|
||||
# Mock router
|
||||
mock_router = MagicMock()
|
||||
|
||||
def mock_get_model_list(model_name):
|
||||
if model_name == "gpt-4":
|
||||
return mock_models_gpt4
|
||||
elif model_name == "gpt-3.5-turbo":
|
||||
return mock_models_gpt35
|
||||
elif model_name == "claude-3":
|
||||
return mock_models_claude
|
||||
return None
|
||||
|
||||
mock_router.get_model_list.side_effect = mock_get_model_list
|
||||
|
||||
# Test Case 1: user_teams = "*" (all teams)
|
||||
mock_litellm_teamtable.find_many.return_value = [mock_team1, mock_team2]
|
||||
|
||||
with patch("litellm.proxy.proxy_server.LiteLLM_TeamTable") as mock_team_table_class:
|
||||
# Configure the mock class to return proper instances
|
||||
def mock_team_table_constructor(**kwargs):
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.team_id = kwargs["team_id"]
|
||||
mock_instance.models = kwargs["models"]
|
||||
return mock_instance
|
||||
|
||||
mock_team_table_class.side_effect = mock_team_table_constructor
|
||||
|
||||
result = await get_all_team_models(
|
||||
user_teams="*",
|
||||
prisma_client=mock_prisma_client,
|
||||
llm_router=mock_router,
|
||||
)
|
||||
|
||||
# Verify find_many was called without where clause for "*"
|
||||
mock_litellm_teamtable.find_many.assert_called_with()
|
||||
|
||||
# Verify router.get_model_list was called for each model
|
||||
expected_calls = [
|
||||
mock.call(model_name="gpt-4"),
|
||||
mock.call(model_name="gpt-3.5-turbo"),
|
||||
mock.call(model_name="claude-3"),
|
||||
mock.call(model_name="gpt-4"), # Called again for team2
|
||||
]
|
||||
mock_router.get_model_list.assert_has_calls(expected_calls, any_order=True)
|
||||
|
||||
# Test Case 2: user_teams = specific list
|
||||
mock_litellm_teamtable.reset_mock()
|
||||
mock_router.reset_mock()
|
||||
mock_router.get_model_list.side_effect = mock_get_model_list
|
||||
|
||||
# Only return team1 for specific team query
|
||||
mock_litellm_teamtable.find_many.return_value = [mock_team1]
|
||||
|
||||
with patch("litellm.proxy.proxy_server.LiteLLM_TeamTable") as mock_team_table_class:
|
||||
mock_team_table_class.side_effect = mock_team_table_constructor
|
||||
|
||||
result = await get_all_team_models(
|
||||
user_teams=["team1"],
|
||||
prisma_client=mock_prisma_client,
|
||||
llm_router=mock_router,
|
||||
)
|
||||
|
||||
# Verify find_many was called with where clause for specific teams
|
||||
mock_litellm_teamtable.find_many.assert_called_with(
|
||||
where={"team_id": {"in": ["team1"]}}
|
||||
)
|
||||
|
||||
# Verify router.get_model_list was called only for team1 models
|
||||
expected_calls = [
|
||||
mock.call(model_name="gpt-4"),
|
||||
mock.call(model_name="gpt-3.5-turbo"),
|
||||
]
|
||||
mock_router.get_model_list.assert_has_calls(expected_calls, any_order=True)
|
||||
|
||||
# Test Case 3: Empty teams list
|
||||
mock_litellm_teamtable.reset_mock()
|
||||
mock_router.reset_mock()
|
||||
mock_litellm_teamtable.find_many.return_value = []
|
||||
|
||||
result = await get_all_team_models(
|
||||
user_teams=[],
|
||||
prisma_client=mock_prisma_client,
|
||||
llm_router=mock_router,
|
||||
)
|
||||
|
||||
# Verify find_many was called with empty list
|
||||
mock_litellm_teamtable.find_many.assert_called_with(where={"team_id": {"in": []}})
|
||||
|
||||
# Should return empty list when no teams
|
||||
assert result == {}
|
||||
|
||||
# Test Case 4: Router returns None for some models
|
||||
mock_litellm_teamtable.reset_mock()
|
||||
mock_router.reset_mock()
|
||||
mock_litellm_teamtable.find_many.return_value = [mock_team1]
|
||||
|
||||
def mock_get_model_list_with_none(model_name):
|
||||
if model_name == "gpt-4":
|
||||
return mock_models_gpt4
|
||||
# Return None for gpt-3.5-turbo to test None handling
|
||||
return None
|
||||
|
||||
mock_router.get_model_list.side_effect = mock_get_model_list_with_none
|
||||
|
||||
with patch("litellm.proxy.proxy_server.LiteLLM_TeamTable") as mock_team_table_class:
|
||||
mock_team_table_class.side_effect = mock_team_table_constructor
|
||||
|
||||
result = await get_all_team_models(
|
||||
user_teams=["team1"],
|
||||
prisma_client=mock_prisma_client,
|
||||
llm_router=mock_router,
|
||||
)
|
||||
|
||||
# Should handle None return gracefully
|
||||
assert isinstance(result, dict)
|
||||
print("result: ", result)
|
||||
assert result == {"gpt-4-model-1": ["team1"], "gpt-4-model-2": ["team1"]}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,16 @@ const TeamDropdown: React.FC<TeamDropdownProps> = ({ teams, value, onChange }) =
|
|||
onChange={onChange}
|
||||
filterOption={(input, option) => {
|
||||
if (!option) return false;
|
||||
const teamAlias = option.children?.[0]?.props?.children || '';
|
||||
return teamAlias.toLowerCase().includes(input.toLowerCase());
|
||||
// Get team data from the option key
|
||||
const team = teams?.find(t => t.team_id === option.key);
|
||||
if (!team) return false;
|
||||
|
||||
const searchTerm = input.toLowerCase().trim();
|
||||
const teamAlias = (team.team_alias || '').toLowerCase();
|
||||
const teamId = (team.team_id || '').toLowerCase();
|
||||
|
||||
// Search in both team alias and team ID
|
||||
return teamAlias.includes(searchTerm) || teamId.includes(searchTerm);
|
||||
}}
|
||||
optionFilterProp="children"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -233,6 +233,10 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
fetchPossibleRoles();
|
||||
}, [accessToken]);
|
||||
|
||||
// Check if team selection is required
|
||||
const isTeamSelectionRequired = modelsToPick.includes('no-default-models');
|
||||
const isFormDisabled = isTeamSelectionRequired && !selectedCreateKeyTeam;
|
||||
|
||||
const handleCreate = async (formValues: Record<string, any>) => {
|
||||
try {
|
||||
const newKeyAlias = formValues?.key_alias ?? "";
|
||||
|
|
@ -487,9 +491,19 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
|
||||
</div>
|
||||
|
||||
{/* Show message when team selection is required */}
|
||||
{isFormDisabled && (
|
||||
<div className="mb-8 p-4 bg-blue-50 border border-blue-200 rounded-md">
|
||||
<Text className="text-blue-800 text-sm">
|
||||
Please select a team to continue configuring your API key.
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Section 2: Key Details */}
|
||||
<div className="mb-8">
|
||||
<Title className="mb-4">Key Details</Title>
|
||||
{!isFormDisabled && (
|
||||
<div className="mb-8">
|
||||
<Title className="mb-4">Key Details</Title>
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
|
|
@ -543,10 +557,12 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Section 3: Optional Settings */}
|
||||
<div className="mb-8">
|
||||
<Accordion className="mt-4 mb-4">
|
||||
{!isFormDisabled && (
|
||||
<div className="mb-8">
|
||||
<Accordion className="mt-4 mb-4">
|
||||
<AccordionHeader>
|
||||
<Title className="m-0">Optional Settings</Title>
|
||||
</AccordionHeader>
|
||||
|
|
@ -815,9 +831,16 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
</AccordionBody>
|
||||
</Accordion>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button2 htmlType="submit">Create Key</Button2>
|
||||
<Button2
|
||||
htmlType="submit"
|
||||
disabled={isFormDisabled}
|
||||
style={{ opacity: isFormDisabled ? 0.5 : 1 }}
|
||||
>
|
||||
Create Key
|
||||
</Button2>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ import {
|
|||
} from "@heroicons/react/outline";
|
||||
import DeleteModelButton from "./delete_model_button";
|
||||
const { Title: Title2, Link } = Typography;
|
||||
import { UploadOutlined } from "@ant-design/icons";
|
||||
import { InfoCircleOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import type { UploadProps } from "antd";
|
||||
import { Upload } from "antd";
|
||||
import TimeToFirstToken from "./model_metrics/time_to_first_token";
|
||||
|
|
@ -260,6 +260,10 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
const [selectedTeamFilter, setSelectedTeamFilter] = useState<string | null>(null);
|
||||
const [selectedModelAccessGroupFilter, setSelectedModelAccessGroupFilter] = useState<string | null>(null);
|
||||
|
||||
// Add new state for current team and model view mode
|
||||
const [currentTeam, setCurrentTeam] = useState<string>('personal'); // 'personal' or team_id
|
||||
const [modelViewMode, setModelViewMode] = useState<'current_team' | 'all'>('current_team');
|
||||
|
||||
const [showColumnDropdown, setShowColumnDropdown] = useState(false);
|
||||
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
|
@ -1137,7 +1141,97 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="border-b px-6 py-4">
|
||||
<div className="flex flex-col space-y-4">
|
||||
{/* Search and Filter Controls */}
|
||||
{/* Current Team Selector - Prominent */}
|
||||
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-4 border-2 border-gray-200">
|
||||
<div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Text className="text-lg font-semibold text-gray-900">Current Team:</Text>
|
||||
<Select
|
||||
className="w-80"
|
||||
defaultValue="personal"
|
||||
value={currentTeam}
|
||||
onValueChange={(value) => setCurrentTeam(value)}
|
||||
>
|
||||
<SelectItem value="personal">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<span className="font-medium">Personal</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
{teams?.filter(team => team.team_id).map((team) => (
|
||||
<SelectItem
|
||||
key={team.team_id}
|
||||
value={team.team_id}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span className="font-medium">
|
||||
{team.team_alias
|
||||
? `${team.team_alias.slice(0, 30)}...`
|
||||
: `Team ${team.team_id.slice(0, 30)}...`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
{
|
||||
modelViewMode === "current_team" && (
|
||||
|
||||
<div className="flex items-start gap-2 mt-2 bg-gray-50 rounded">
|
||||
<InfoCircleOutlined className="text-gray-400 mt-0.5 flex-shrink-0 text-xs" />
|
||||
<div className="text-xs text-gray-500">
|
||||
{currentTeam === "personal" ? (
|
||||
<span>
|
||||
To access these models: Create a Virtual Key without selecting a team on the {" "}
|
||||
<a href="/?login=success&page=api-keys" className="text-gray-600 hover:text-gray-800 underline">
|
||||
Virtual Keys page
|
||||
</a>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
To access these models: Create a Virtual Key and select Team as "{currentTeam}" on the {" "}
|
||||
<a href="/?login=success&page=api-keys" className="text-gray-600 hover:text-gray-800 underline">
|
||||
Virtual Keys page
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Model View Mode Toggle - Also prominent */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Text className="text-lg font-semibold text-gray-900">View:</Text>
|
||||
<Select
|
||||
className="w-64"
|
||||
defaultValue="current_team"
|
||||
value={modelViewMode}
|
||||
onValueChange={(value) => setModelViewMode(value as 'current_team' | 'all')}
|
||||
>
|
||||
<SelectItem value="current_team">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-purple-500 rounded-full"></div>
|
||||
<span className="font-medium">Current Team Models</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="all">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<span className="font-medium">All Available Models</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{/* Other Filters */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Model Name Filter */}
|
||||
|
|
@ -1161,29 +1255,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Team Filter */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Text>Filter by Team:</Text>
|
||||
<Select
|
||||
className="w-64"
|
||||
defaultValue="all"
|
||||
value={selectedTeamFilter ?? "all"}
|
||||
onValueChange={(value) => setSelectedTeamFilter(value === "all" ? null : value)}
|
||||
>
|
||||
<SelectItem value="all">All Teams</SelectItem>
|
||||
{teams?.filter(team => team.team_id).map((team) => (
|
||||
<SelectItem
|
||||
key={team.team_id}
|
||||
value={team.team_id}
|
||||
>
|
||||
{team.team_alias
|
||||
? `${team.team_alias} (${team.team_id.slice(0, 8)}...)`
|
||||
: `Team ${team.team_id.slice(0, 8)}...`
|
||||
}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Text>Filter by Model Access Group:</Text>
|
||||
<Select
|
||||
|
|
@ -1210,7 +1281,21 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
{/* Results Count */}
|
||||
<div className="flex justify-between items-center">
|
||||
<Text className="text-sm text-gray-700">
|
||||
Showing {modelData && modelData.data.length > 0 ? modelData.data.length : 0} results
|
||||
Showing {modelData && modelData.data.length > 0 ?
|
||||
modelData.data.filter((model: any) => {
|
||||
const modelNameMatch = selectedModelGroup === "all" || model.model_name === selectedModelGroup || !selectedModelGroup;
|
||||
const accessGroupMatch = selectedModelAccessGroupFilter === "all" || model.model_info["access_groups"]?.includes(selectedModelAccessGroupFilter) || !selectedModelAccessGroupFilter;
|
||||
let teamAccessMatch = true;
|
||||
if (modelViewMode === 'current_team') {
|
||||
if (currentTeam === 'personal') {
|
||||
teamAccessMatch = model.model_info?.direct_access === true;
|
||||
} else {
|
||||
teamAccessMatch = model.model_info?.access_via_team_ids?.includes(currentTeam) === true;
|
||||
}
|
||||
}
|
||||
|
||||
return modelNameMatch && accessGroupMatch && teamAccessMatch;
|
||||
}).length : 0} results
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1231,11 +1316,28 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
setExpandedRows,
|
||||
)}
|
||||
data={modelData.data.filter(
|
||||
(model: any) => (
|
||||
(selectedModelGroup === "all" || model.model_name === selectedModelGroup || !selectedModelGroup) &&
|
||||
(selectedTeamFilter === "all" || model.model_info["team_id"] === selectedTeamFilter || !selectedTeamFilter) &&
|
||||
(selectedModelAccessGroupFilter === "all" || model.model_info["access_groups"]?.includes(selectedModelAccessGroupFilter) || !selectedModelAccessGroupFilter)
|
||||
)
|
||||
(model: any) => {
|
||||
// Model name filter
|
||||
const modelNameMatch = selectedModelGroup === "all" || model.model_name === selectedModelGroup || !selectedModelGroup;
|
||||
|
||||
// Model access group filter
|
||||
const accessGroupMatch = selectedModelAccessGroupFilter === "all" || model.model_info["access_groups"]?.includes(selectedModelAccessGroupFilter) || !selectedModelAccessGroupFilter;
|
||||
|
||||
// Team access filter based on current team and view mode
|
||||
let teamAccessMatch = true;
|
||||
if (modelViewMode === 'current_team') {
|
||||
if (currentTeam === 'personal') {
|
||||
// Show only models with direct access
|
||||
teamAccessMatch = model.model_info?.direct_access === true;
|
||||
} else {
|
||||
// Show only models accessible by the current team
|
||||
teamAccessMatch = model.model_info?.access_via_team_ids?.includes(currentTeam) === true;
|
||||
}
|
||||
}
|
||||
// For 'all' mode, show all models (teamAccessMatch remains true)
|
||||
|
||||
return modelNameMatch && accessGroupMatch && teamAccessMatch;
|
||||
}
|
||||
)}
|
||||
isLoading={false}
|
||||
table={tableRef}
|
||||
|
|
|
|||
|
|
@ -1611,11 +1611,12 @@ export const modelInfoCall = async (
|
|||
try {
|
||||
console.log("modelInfoCall:", accessToken, userID, userRole);
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/model/info` : `/v2/model/info`;
|
||||
|
||||
if (!all_admin_roles.includes(userRole as string)) {
|
||||
// only show users models they've added
|
||||
url += `?user_models_only=true`;
|
||||
const params = new URLSearchParams();
|
||||
params.append("include_team_models", "true");
|
||||
if (params.toString()) {
|
||||
url += `?${params.toString()}`;
|
||||
}
|
||||
|
||||
//message.info("Requesting model data");
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue