feat(ui): shadcn migration foundation: Tailwind v4, shadcn init, antd cascade fix (#31995)

* feat(ui): shadcn migration foundation: Tailwind v4, shadcn init, antd cascade fix

Upgrade the dashboard from Tailwind v3 to v4 with CSS-first config: the
official upgrade codemod renamed utilities across 151 files, and
tailwind.config.js (plus the dead tailwind.config.ts) is replaced by
@theme tokens, @source globs, and @plugin directives in globals.css. The
Tremor safelist becomes @source inline patterns and the legacy tremor
theme tokens carry over verbatim. ui_colors.json was build-time only and
fed the dying Tremor palette, so its brand values are inlined and the
file removed; runtime theming replaces that path next.

shadcn is initialized with a hand-authored components.json (rsc,
cssVariables, baseColor gray) pointing utils at the existing
lib/cva.config.ts, which now exports cn (cva beta cx + twMerge) instead
of adding class-variance-authority as a second variant library. The two
ad-hoc cn helpers fold into it. Button lands as the canary primitive,
adapted to cva beta and React 18 forwardRef, with tests covering the
variant, twMerge, asChild, and ref seams. --radius is 0.5rem so the
shadcn radius scale reproduces Tailwind defaults and legacy rounded-*
classes render unchanged.

antd v5 emits unlayered CSS-in-JS that would beat every layered v4
utility, so AntdGlobalProvider now wraps the app in StyleProvider layer
and ConfigProvider cssVar, and globals.css declares
@layer theme, base, antd, components, utilities. antd wins over
preflight but yields to utilities, which is what lets migrated shadcn
pages coexist with legacy antd pages. Preflight stays global with the
three v3 behaviors pinned (default border color, button cursor,
placeholder color).

* fix(ui): restore tremor opacity tints removed by tailwind v4

Tailwind v4 removed the *-opacity-* utilities, but the precompiled
@tremor/react dist still composes them with shade-500 palette classes
(bg-opacity-10 over bg-<color>-500 etc.), so Badge, BadgeDelta, Callout,
light Icon and Button, BarList, and ProgressBar lost their tints and
rendered solid 500-shade fills. Adversarial review caught it; the
original smoke pages only exercised antd Tags.

tremor-v3-compat.css restores exactly the pairs tremor emits: for each
of the 22 safelisted colors, bg-opacity-{10,20,40}, hover/group-hover
bg-opacity-{20,30}, and ring-opacity-{20,40} against the -500 shade,
via color-mix into the utilities layer. Tremor's colorPalette maps both
background and iconRing to 500, so the -500 pairing covers every
composition in the dist; dark: variants are inert until dark mode ships.
The shim dies with @tremor/react at the end of the migration.

The upgrade codemod also missed two hand-rolled modal scrims using
bg-black bg-opacity-{30,50} (solid black under v4); now bg-black/30 and
bg-black/50. Removed the docker/build_admin_ui.sh copy of
enterprise_colors.json into the deleted ui_colors.json; that build-time
rebrand path is retired and its runtime replacement lands with the
theming phase.

* fix(ui): pair ring-opacity-40 with shade 300 in tremor compat shim

Tremor's colorPalette maps ring to shade 300, and the only consumer of
ring-opacity-40 (Icon variant outlined) composes it with that shade,
so the shade-500 rows were dead and outlined icon rings would render
at full opacity. Latent today (no dashboard usage of the outlined
variant); caught by adversarial review. ring-opacity-20 stays at 500
(iconRing), matching Badge and BadgeDelta.
This commit is contained in:
ryan-crabbe-berri 2026-07-02 19:02:27 -07:00 committed by GitHub
parent 138a69bd61
commit 27069bd74f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
169 changed files with 4561 additions and 2150 deletions

2
.gitignore vendored
View file

@ -130,3 +130,5 @@ crash.*.log
# pytest coverage data
.coverage
ui/litellm-dashboard/out/

View file

@ -141,6 +141,6 @@
"limit": 1005
},
"reportUnusedVariable": {
"limit": 1298
"limit": 1297
}
}

View file

@ -57,8 +57,6 @@ source ~/.nvm/nvm.sh
nvm install v18.17.0
nvm use v18.17.0
# copy _enterprise.json from this directory to /ui/litellm-dashboard, and rename it to ui_colors.json
cp enterprise/enterprise_ui/enterprise_colors.json ui/litellm-dashboard/ui_colors.json
# cd in to /ui/litellm-dashboard
cd ui/litellm-dashboard

View file

@ -1,5 +1,5 @@
import os
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from typing import TYPE_CHECKING, Any, Optional
from litellm._logging import verbose_logger
@ -175,7 +175,7 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM):
self,
headers: dict,
model: str,
messages: List[AllMessageValues],
messages: list[AllMessageValues],
optional_params: dict,
litellm_params: dict,
api_key: Optional[str] = None,
@ -217,10 +217,10 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM):
contents = [{"role": "user", "parts": [{"text": prompt}]}]
# Prepare generation config
generation_config: Dict[str, Any] = {"responseModalities": ["IMAGE"]}
generation_config: dict[str, Any] = {"responseModalities": ["IMAGE"]}
# Seed from user-supplied imageConfig dict; flat params are overlaid for backward compat.
image_config: Dict[str, Any] = dict(optional_params.get("imageConfig") or {})
image_config: dict[str, Any] = dict(optional_params.get("imageConfig") or {})
if "aspectRatio" in optional_params:
image_config["aspectRatio"] = optional_params["aspectRatio"]
@ -241,7 +241,7 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM):
elif "n" in optional_params:
generation_config["candidateCount"] = optional_params["n"]
request_body: Dict[str, Any] = {
request_body: dict[str, Any] = {
"contents": contents,
"generationConfig": generation_config,
}

View file

@ -14,7 +14,7 @@ import os
import re
import time
from contextlib import asynccontextmanager
from typing import Any, AsyncIterator, Callable, Dict, List, Literal, Optional, Set, Tuple, Union, cast
from typing import Any, AsyncIterator, Callable, Literal, Optional, Union, cast
from urllib.parse import urlparse
import anyio
@ -156,7 +156,7 @@ _AZURE_ENTRA_HOSTS = {
# BYOK credential cache. Keyed by (user_id, server_id); value is
# (values_dict, monotonic_timestamp). Keeps the tool-call and tool-listing
# paths off the DB on every request within the TTL window.
_user_env_vars_cache: Dict[Tuple[str, str], Tuple[Dict[str, str], float]] = {}
_user_env_vars_cache: dict[tuple[str, str], tuple[dict[str, str], float]] = {}
_USER_ENV_VARS_CACHE_TTL = 60 # seconds
_USER_ENV_VARS_CACHE_MAX_SIZE = 4096 # cap to prevent unbounded growth
@ -167,7 +167,7 @@ def invalidate_user_env_vars_cache(user_id: str, server_id: str) -> None:
_user_env_vars_cache.pop((user_id, server_id), None)
def _write_user_env_vars_cache(user_id: str, server_id: str, values: Dict[str, str]) -> None:
def _write_user_env_vars_cache(user_id: str, server_id: str, values: dict[str, str]) -> None:
cache_key = (user_id, server_id)
# Re-insert at the tail so eviction drops the oldest-written entry, not a
# freshly refreshed one, and only sheds a single entry instead of wiping the
@ -180,7 +180,7 @@ def _write_user_env_vars_cache(user_id: str, server_id: str, values: Dict[str, s
def _should_strip_caller_authorization(
mcp_server: MCPServer,
raw_headers: Optional[Dict[str, str]],
raw_headers: Optional[dict[str, str]],
user_api_key_auth: Optional[UserAPIKeyAuth],
) -> bool:
"""Decide whether the caller's ``Authorization`` header must NOT be
@ -244,7 +244,7 @@ def _without_authorization(
def _extract_upstream_auth_failure(
exc: BaseException,
) -> Optional[Tuple[int, Optional[str]]]:
) -> Optional[tuple[int, Optional[str]]]:
"""Walk the exception tree looking for an HTTP 401/403 response from the
upstream MCP server.
@ -256,8 +256,8 @@ def _extract_upstream_auth_failure(
Returns ``(status_code, www_authenticate)`` on match, else ``None``.
"""
seen: Set[int] = set()
stack: List[BaseException] = [exc]
seen: set[int] = set()
stack: list[BaseException] = [exc]
while stack:
current = stack.pop()
if id(current) in seen:
@ -338,7 +338,7 @@ def _warn_internal_delegate_pkce_if_applicable(server: MCPServer, *, source: str
)
def _deserialize_json_dict(data: Any) -> Optional[Dict[str, str]]:
def _deserialize_json_dict(data: Any) -> Optional[dict[str, str]]:
"""
Deserialize optional JSON mappings stored in the database.
@ -359,7 +359,7 @@ def _deserialize_json_dict(data: Any) -> Optional[Dict[str, str]]:
return data
def _deserialize_json_list(data: Any) -> Optional[List[Dict[str, Any]]]:
def _deserialize_json_list(data: Any) -> Optional[list[dict[str, Any]]]:
"""Deserialize a JSON array stored in the DB (``env_vars`` and friends).
Returns ``None`` for empty / null / unparseable input. Accepts strings
@ -533,8 +533,8 @@ class MCPServerManager:
self._cred_provider = cred_provider or UpstreamCredentialProvider(
oauth_token_store=LazyPerUserOAuthTokenStore(self.get_mcp_server_by_id)
)
self.registry: Dict[str, MCPServer] = {}
self.config_mcp_servers: Dict[str, MCPServer] = {}
self.registry: dict[str, MCPServer] = {}
self.config_mcp_servers: dict[str, MCPServer] = {}
"""
eg.
[
@ -555,18 +555,18 @@ class MCPServerManager:
# each server's max_concurrent_requests. Keyed by server_id so the cap
# survives the registry atomic-swap on config reload; a missing key means
# the server has no configured limit.
self._server_call_semaphores: Dict[str, asyncio.Semaphore] = {}
self.tool_name_to_mcp_server_name_mapping: Dict[str, str] = {}
self._server_call_semaphores: dict[str, asyncio.Semaphore] = {}
self.tool_name_to_mcp_server_name_mapping: dict[str, str] = {}
"""
{
"gmail_send_email": "zapier_mcp_server",
}
"""
self._upstream_initialize_instructions_by_server_id: Dict[str, str] = {}
self._upstream_initialize_instructions_by_server_id: dict[str, str] = {}
# Per-server monotonic timestamp of last upstream prefetch attempt (success,
# empty result, or failure). Used to throttle re-probes for servers that do
# not return instructions, and to apply a short cooldown after failures.
self._upstream_initialize_instructions_probed_at: Dict[str, float] = {}
self._upstream_initialize_instructions_probed_at: dict[str, float] = {}
def _remember_upstream_initialize_instructions(self, server: MCPServer, client: MCPClient) -> None:
raw = getattr(client, "_last_initialize_instructions", None)
@ -622,7 +622,7 @@ class MCPServerManager:
user_api_key_auth=None,
raise_on_missing=False,
)
extra_headers: Optional[Dict[str, str]] = dict(resolved_static_headers) if resolved_static_headers else None
extra_headers: Optional[dict[str, str]] = dict(resolved_static_headers) if resolved_static_headers else None
client = await self._create_mcp_client(
server=server,
mcp_auth_header=None,
@ -642,7 +642,7 @@ class MCPServerManager:
e,
)
def get_registry(self) -> Dict[str, MCPServer]:
def get_registry(self) -> dict[str, MCPServer]:
"""
Get the registered MCP Servers from the registry and union with the config MCP Servers
"""
@ -650,8 +650,8 @@ class MCPServerManager:
async def load_servers_from_config(
self,
mcp_servers_config: Dict[str, Any],
mcp_aliases: Optional[Dict[str, str]] = None,
mcp_servers_config: dict[str, Any],
mcp_aliases: Optional[dict[str, str]] = None,
):
"""
Load the MCP Servers from the config
@ -669,7 +669,7 @@ class MCPServerManager:
for server_name, server_config in mcp_servers_config.items():
validate_mcp_server_name(server_name)
_mcp_info: Dict[str, Any] = server_config.get("mcp_info", None) or {}
_mcp_info: dict[str, Any] = server_config.get("mcp_info", None) or {}
# Preserve all custom fields from config while setting defaults for core fields
mcp_info: MCPInfo = _mcp_info.copy()
# Set default values for core fields if not present
@ -858,7 +858,7 @@ class MCPServerManager:
server_prefix = get_server_prefix(server)
# Build headers from server configuration
headers: Dict[str, str] = {}
headers: dict[str, str] = {}
# Add authentication headers if configured
if server.authentication_token:
@ -968,7 +968,7 @@ class MCPServerManager:
openapi_key_prefix = prefix_root + MCP_TOOL_PREFIX_SEPARATOR
global_mcp_tool_registry.unregister_tools_with_prefix(openapi_key_prefix)
owned_raw: Set[str] = set()
owned_raw: set[str] = set()
for p in iter_known_server_prefixes(server):
if p:
owned_raw.add(p)
@ -977,7 +977,7 @@ class MCPServerManager:
owned_normalized = {normalize_server_name(x) for x in owned_raw}
stale_mapping_keys: List[str] = []
stale_mapping_keys: list[str] = []
for tool_name, mapped_server in list(self.tool_name_to_mcp_server_name_mapping.items()):
if mapped_server in owned_raw:
stale_mapping_keys.append(tool_name)
@ -1005,7 +1005,7 @@ class MCPServerManager:
mcp_server: LiteLLM_MCPServerTable,
*,
env_vars_are_encrypted: bool,
) -> Optional[List[Dict[str, Any]]]:
) -> Optional[list[dict[str, Any]]]:
env_vars_list = _deserialize_json_list(getattr(mcp_server, "env_vars", None))
if env_vars_are_encrypted:
from litellm.proxy._experimental.mcp_server.db import ( # noqa: PLC0415
@ -1080,7 +1080,7 @@ class MCPServerManager:
# AWS SigV4 credential fields
aws_creds = self._extract_aws_credentials(credentials_dict, credentials_are_encrypted)
scopes: Optional[List[str]] = None
scopes: Optional[list[str]] = None
if credentials_dict:
scopes_value = credentials_dict.get("scopes")
if scopes_value is not None:
@ -1243,14 +1243,14 @@ class MCPServerManager:
verbose_logger.debug(f"Failed to udpate MCP server: {str(e)}")
raise e
def get_all_mcp_server_ids(self) -> Set[str]:
def get_all_mcp_server_ids(self) -> set[str]:
"""
Get all MCP server IDs
"""
all_servers = list(self.get_registry().values())
return {server.server_id for server in all_servers}
def get_allow_all_keys_server_ids(self) -> List[str]:
def get_allow_all_keys_server_ids(self) -> list[str]:
"""Return server IDs that bypass per-key restrictions."""
return [server.server_id for server in self.get_registry().values() if server.allow_all_keys is True]
@ -1315,7 +1315,7 @@ class MCPServerManager:
return [server_id for server_id in submitted_server_ids if self.get_mcp_server_by_id(server_id) is not None]
async def get_allowed_mcp_servers(self, user_api_key_auth: Optional[UserAPIKeyAuth] = None) -> List[str]:
async def get_allowed_mcp_servers(self, user_api_key_auth: Optional[UserAPIKeyAuth] = None) -> list[str]:
"""
Get the allowed MCP Servers for the user.
@ -1412,8 +1412,8 @@ class MCPServerManager:
async def resolve_toolset_tool_permissions(
self,
toolset_ids: List[str],
) -> Dict[str, List[str]]:
toolset_ids: list[str],
) -> dict[str, list[str]]:
"""
Resolve a list of toolset IDs into a mcp_tool_permissions dict.
@ -1435,7 +1435,7 @@ class MCPServerManager:
try:
toolsets = await list_mcp_toolsets(prisma_client, toolset_ids=toolset_ids)
tool_permissions: Dict[str, List[str]] = {}
tool_permissions: dict[str, list[str]] = {}
for toolset in toolsets:
for tool in toolset.tools:
raw_name = tool["tool_name"]
@ -1528,7 +1528,7 @@ class MCPServerManager:
)
return toolset
def filter_server_ids_by_ip(self, server_ids: List[str], client_ip: Optional[str]) -> List[str]:
def filter_server_ids_by_ip(self, server_ids: list[str], client_ip: Optional[str]) -> list[str]:
"""
Filter server IDs by client IP external callers only see public servers.
@ -1538,8 +1538,8 @@ class MCPServerManager:
return filtered
def filter_server_ids_by_ip_with_info(
self, server_ids: List[str], client_ip: Optional[str]
) -> Tuple[List[str], int]:
self, server_ids: list[str], client_ip: Optional[str]
) -> tuple[list[str], int]:
"""
Filter server IDs by client IP external callers only see public servers.
@ -1559,7 +1559,7 @@ class MCPServerManager:
blocked += 1
return allowed, blocked
async def get_tools_for_server(self, server_id: str) -> List[MCPTool]:
async def get_tools_for_server(self, server_id: str) -> list[MCPTool]:
"""
Get the tools for a given server
"""
@ -1577,8 +1577,8 @@ class MCPServerManager:
self,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
mcp_auth_header: Optional[str] = None,
mcp_server_auth_headers: Optional[Dict[str, Union[str, Dict[str, str]]]] = None,
) -> List[MCPTool]:
mcp_server_auth_headers: Optional[dict[str, Union[str, dict[str, str]]]] = None,
) -> list[MCPTool]:
"""
List all tools available across all MCP Servers.
@ -1595,7 +1595,7 @@ class MCPServerManager:
verbose_logger.debug("SERVER MANAGER LISTING TOOLS")
async def _fetch_server_tools(server_id: str) -> List[MCPTool]:
async def _fetch_server_tools(server_id: str) -> list[MCPTool]:
"""Fetch tools from a single server with error handling."""
server = self.get_mcp_server_by_id(server_id)
if server is None:
@ -1603,7 +1603,7 @@ class MCPServerManager:
return []
# Get server-specific auth header if available
server_auth_header: Optional[Union[str, Dict[str, str]]] = None
server_auth_header: Optional[Union[str, dict[str, str]]] = None
if mcp_server_auth_headers:
from litellm.proxy._experimental.mcp_server.utils import (
lookup_mcp_server_auth_in_headers,
@ -1637,7 +1637,7 @@ class MCPServerManager:
results = await asyncio.gather(*tasks)
# Flatten results into single list
list_tools_result: List[MCPTool] = [tool for tools in results for tool in tools]
list_tools_result: list[MCPTool] = [tool for tools in results for tool in tools]
verbose_logger.info(f"Successfully fetched {len(list_tools_result)} tools total from all servers")
return list_tools_result
@ -1647,8 +1647,8 @@ class MCPServerManager:
#########################################################
@staticmethod
def _extract_bearer_token(
oauth2_headers: Optional[Dict[str, str]],
raw_headers: Optional[Dict[str, str]],
oauth2_headers: Optional[dict[str, str]],
raw_headers: Optional[dict[str, str]],
) -> Optional[str]:
"""Extract the bare Bearer token from oauth2_headers or raw_headers.
@ -1671,14 +1671,14 @@ class MCPServerManager:
def _build_stdio_env(
self,
server: MCPServer,
raw_headers: Optional[Dict[str, str]] = None,
) -> Optional[Dict[str, str]]:
raw_headers: Optional[dict[str, str]] = None,
) -> Optional[dict[str, str]]:
"""Resolve stdio env values, supporting header-driven placeholders."""
if server.transport != MCPTransport.stdio or not server.env:
return None
resolved_env: Dict[str, str] = {}
resolved_env: dict[str, str] = {}
normalized_headers = {k.lower(): v for k, v in (raw_headers or {}).items()}
for env_key, env_value in server.env.items():
@ -1720,7 +1720,7 @@ class MCPServerManager:
user_api_key_auth: Optional[UserAPIKeyAuth],
*,
raise_on_missing: bool = True,
) -> Optional[Dict[str, str]]:
) -> Optional[dict[str, str]]:
"""Return server.static_headers with ``${NAME}`` interpolated.
Globals come from ``server.env_vars`` entries with ``scope=="global"``.
@ -1763,7 +1763,7 @@ class MCPServerManager:
referenced_user_vars = referenced & user_var_names
required_user_vars = {name for name in referenced_user_vars if name not in global_values}
user_values: Dict[str, str] = {}
user_values: dict[str, str] = {}
if required_user_vars:
try:
user_values = await self._load_user_env_vars(server, user_api_key_auth)
@ -1802,7 +1802,7 @@ class MCPServerManager:
# admin globals win, so a stale row from when a var was user-scoped can
# never override the global value the admin set after switching it.
scoped_user_values = {name: value for name, value in user_values.items() if name in user_var_names}
merged_vars: Dict[str, str] = {**scoped_user_values, **global_values}
merged_vars: dict[str, str] = {**scoped_user_values, **global_values}
if not static_headers:
return static_headers
return interpolate_headers(static_headers, merged_vars)
@ -1813,7 +1813,7 @@ class MCPServerManager:
user_api_key_auth: Optional[UserAPIKeyAuth],
*,
force_refresh: bool = False,
) -> Dict[str, str]:
) -> dict[str, str]:
"""Look up the calling user's env var values for ``server``.
Returns an empty dict when no user is available. Results are cached in a
@ -1861,9 +1861,9 @@ class MCPServerManager:
async def _create_mcp_client(
self,
server: MCPServer,
mcp_auth_header: Optional[Union[str, Dict[str, str]]] = None,
extra_headers: Optional[Dict[str, str]] = None,
stdio_env: Optional[Dict[str, str]] = None,
mcp_auth_header: Optional[Union[str, dict[str, str]]] = None,
extra_headers: Optional[dict[str, str]] = None,
stdio_env: Optional[dict[str, str]] = None,
subject_token: Optional[str] = None,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
cred_provider: Optional[UpstreamCredentialProvider] = None,
@ -2021,12 +2021,12 @@ class MCPServerManager:
async def _get_tools_from_server(
self,
server: MCPServer,
mcp_auth_header: Optional[Union[str, Dict[str, str]]] = None,
extra_headers: Optional[Dict[str, str]] = None,
mcp_auth_header: Optional[Union[str, dict[str, str]]] = None,
extra_headers: Optional[dict[str, str]] = None,
add_prefix: bool = True,
raw_headers: Optional[Dict[str, str]] = None,
raw_headers: Optional[dict[str, str]] = None,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
) -> List[MCPTool]:
) -> list[MCPTool]:
"""
Helper method to get tools from a single MCP server with prefixed names.
@ -2160,11 +2160,11 @@ class MCPServerManager:
async def get_prompts_from_server(
self,
server: MCPServer,
mcp_auth_header: Optional[Union[str, Dict[str, str]]] = None,
extra_headers: Optional[Dict[str, str]] = None,
mcp_auth_header: Optional[Union[str, dict[str, str]]] = None,
extra_headers: Optional[dict[str, str]] = None,
add_prefix: bool = True,
raw_headers: Optional[Dict[str, str]] = None,
) -> List[Prompt]:
raw_headers: Optional[dict[str, str]] = None,
) -> list[Prompt]:
"""
Helper method to get prompts from a single MCP server with prefixed names.
@ -2209,11 +2209,11 @@ class MCPServerManager:
async def get_resources_from_server(
self,
server: MCPServer,
mcp_auth_header: Optional[Union[str, Dict[str, str]]] = None,
extra_headers: Optional[Dict[str, str]] = None,
mcp_auth_header: Optional[Union[str, dict[str, str]]] = None,
extra_headers: Optional[dict[str, str]] = None,
add_prefix: bool = True,
raw_headers: Optional[Dict[str, str]] = None,
) -> List[Resource]:
raw_headers: Optional[dict[str, str]] = None,
) -> list[Resource]:
"""Fetch available resources from a single MCP server."""
verbose_logger.debug(f"Connecting to url: {server.url}")
@ -2249,11 +2249,11 @@ class MCPServerManager:
async def get_resource_templates_from_server(
self,
server: MCPServer,
mcp_auth_header: Optional[Union[str, Dict[str, str]]] = None,
extra_headers: Optional[Dict[str, str]] = None,
mcp_auth_header: Optional[Union[str, dict[str, str]]] = None,
extra_headers: Optional[dict[str, str]] = None,
add_prefix: bool = True,
raw_headers: Optional[Dict[str, str]] = None,
) -> List[ResourceTemplate]:
raw_headers: Optional[dict[str, str]] = None,
) -> list[ResourceTemplate]:
"""Fetch available resource templates from a single MCP server."""
verbose_logger.debug(f"Connecting to url: {server.url}")
@ -2292,9 +2292,9 @@ class MCPServerManager:
self,
server: MCPServer,
url: AnyUrl,
mcp_auth_header: Optional[Union[str, Dict[str, str]]] = None,
extra_headers: Optional[Dict[str, str]] = None,
raw_headers: Optional[Dict[str, str]] = None,
mcp_auth_header: Optional[Union[str, dict[str, str]]] = None,
extra_headers: Optional[dict[str, str]] = None,
raw_headers: Optional[dict[str, str]] = None,
) -> ReadResourceResult:
"""Read resource contents from a specific MCP server."""
@ -2321,10 +2321,10 @@ class MCPServerManager:
self,
server: MCPServer,
prompt_name: str,
arguments: Optional[Dict[str, Any]] = None,
mcp_auth_header: Optional[Union[str, Dict[str, str]]] = None,
extra_headers: Optional[Dict[str, str]] = None,
raw_headers: Optional[Dict[str, str]] = None,
arguments: Optional[dict[str, Any]] = None,
mcp_auth_header: Optional[Union[str, dict[str, str]]] = None,
extra_headers: Optional[dict[str, str]] = None,
raw_headers: Optional[dict[str, str]] = None,
) -> GetPromptResult:
"""Fetch a specific prompt definition from a single MCP server."""
@ -2468,7 +2468,7 @@ class MCPServerManager:
verbose_logger.debug("MCP OAuth discovery failed for %s: %s", server_url, exc)
return None
def _parse_www_authenticate_header(self, header_value: Optional[str]) -> Tuple[Optional[str], Optional[List[str]]]:
def _parse_www_authenticate_header(self, header_value: Optional[str]) -> tuple[Optional[str], Optional[list[str]]]:
if not header_value:
return None, None
@ -2476,7 +2476,7 @@ class MCPServerManager:
params_section = params_section or header_value
param_pattern = re.compile(r"([a-zA-Z0-9_]+)\s*=\s*\"?([^\",]+)\"?")
params: Dict[str, str] = {
params: dict[str, str] = {
match.group(1).lower(): match.group(2).strip() for match in param_pattern.finditer(params_section)
}
@ -2490,7 +2490,7 @@ class MCPServerManager:
async def _fetch_oauth_metadata_from_resource(
self, resource_metadata_url: str, server_url: str
) -> Tuple[List[str], Optional[List[str]]]:
) -> tuple[list[str], Optional[list[str]]]:
if not resource_metadata_url:
return [], None
@ -2525,7 +2525,7 @@ class MCPServerManager:
return authorization_servers, scopes
async def _attempt_well_known_discovery(self, server_url: str) -> Tuple[List[str], Optional[List[str]]]:
async def _attempt_well_known_discovery(self, server_url: str) -> tuple[list[str], Optional[list[str]]]:
try:
parsed = urlparse(server_url)
except Exception:
@ -2538,7 +2538,7 @@ class MCPServerManager:
path = parsed.path or ""
path = path.strip("/")
candidate_urls: List[str] = []
candidate_urls: list[str] = []
if path:
candidate_urls.append(f"{base}/.well-known/oauth-protected-resource/{path}")
candidate_urls.append(f"{base}/.well-known/oauth-protected-resource")
@ -2554,7 +2554,7 @@ class MCPServerManager:
return [], None
async def _fetch_authorization_server_metadata(
self, authorization_servers: List[str], server_url: str
self, authorization_servers: list[str], server_url: str
) -> Optional[MCPOAuthMetadata]:
for issuer in authorization_servers:
metadata = await self._fetch_single_authorization_server_metadata(issuer, server_url)
@ -2576,7 +2576,7 @@ class MCPServerManager:
base = f"{parsed.scheme}://{parsed.netloc}"
path = (parsed.path or "").strip("/")
candidate_urls: List[str] = []
candidate_urls: list[str] = []
if path:
candidate_urls.append(f"{base}/.well-known/oauth-authorization-server/{path}")
candidate_urls.append(f"{base}/.well-known/openid-configuration/{path}")
@ -2662,9 +2662,9 @@ class MCPServerManager:
def _extract_aws_credentials(
self,
credentials_dict: Optional[Dict[str, str]],
credentials_dict: Optional[dict[str, str]],
credentials_are_encrypted: bool,
) -> Dict[str, Optional[str]]:
) -> dict[str, Optional[str]]:
"""Extract and decrypt AWS SigV4 credential fields from credentials dict."""
if not credentials_dict:
return {}
@ -2690,7 +2690,7 @@ class MCPServerManager:
"aws_session_name": credentials_dict.get("aws_session_name"),
}
def _extract_scopes(self, scopes_value: Any) -> Optional[List[str]]:
def _extract_scopes(self, scopes_value: Any) -> Optional[list[str]]:
if isinstance(scopes_value, str):
scopes = [s.strip() for s in scopes_value.split() if s.strip()]
return scopes or None
@ -2703,7 +2703,7 @@ class MCPServerManager:
self,
client: MCPClient,
server_name: str,
) -> List[MCPTool]:
) -> list[MCPTool]:
"""
Fetch tools from MCP client with timeout and error handling.
@ -2760,7 +2760,7 @@ class MCPServerManager:
def _assign_unique_short_prefix(
self,
server: MCPServer,
registry: Optional[Dict[str, MCPServer]] = None,
registry: Optional[dict[str, MCPServer]] = None,
) -> None:
"""Resolve and cache a collision-free short tool prefix on ``server``.
@ -2784,7 +2784,7 @@ class MCPServerManager:
if not server.server_id:
return
used: Dict[str, str] = {}
used: dict[str, str] = {}
registry_for_collision_check = registry or self.get_registry()
for other in registry_for_collision_check.values():
if other.server_id == server.server_id:
@ -2817,7 +2817,7 @@ class MCPServerManager:
"attempts; the 3-character prefix space is too crowded."
)
def _create_prefixed_tools(self, tools: List[MCPTool], server: MCPServer, add_prefix: bool = True) -> List[MCPTool]:
def _create_prefixed_tools(self, tools: list[MCPTool], server: MCPServer, add_prefix: bool = True) -> list[MCPTool]:
"""
Create prefixed tools and update tool mapping.
@ -2855,8 +2855,8 @@ class MCPServerManager:
return prefixed_tools
def _create_prefixed_prompts(
self, prompts: List[Prompt], server: MCPServer, add_prefix: bool = True
) -> List[Prompt]:
self, prompts: list[Prompt], server: MCPServer, add_prefix: bool = True
) -> list[Prompt]:
"""
Create prefixed prompts and update prompt mapping.
@ -2882,11 +2882,11 @@ class MCPServerManager:
return prefixed_prompts
def _create_prefixed_resources(
self, resources: List[Resource], server: MCPServer, add_prefix: bool = True
) -> List[Resource]:
self, resources: list[Resource], server: MCPServer, add_prefix: bool = True
) -> list[Resource]:
"""Prefix resource names and track origin server for read requests."""
prefixed_resources: List[Resource] = []
prefixed_resources: list[Resource] = []
prefix = get_server_prefix(server)
for resource in resources:
@ -2899,13 +2899,13 @@ class MCPServerManager:
def _create_prefixed_resource_templates(
self,
resource_templates: List[ResourceTemplate],
resource_templates: list[ResourceTemplate],
server: MCPServer,
add_prefix: bool = True,
) -> List[ResourceTemplate]:
) -> list[ResourceTemplate]:
"""Prefix resource template names for multi-server scenarios."""
prefixed_templates: List[ResourceTemplate] = []
prefixed_templates: list[ResourceTemplate] = []
prefix = get_server_prefix(server)
for resource_template in resource_templates:
@ -2938,7 +2938,7 @@ class MCPServerManager:
)
return True
def validate_allowed_params(self, tool_name: str, arguments: Dict[str, Any], server: MCPServer) -> None:
def validate_allowed_params(self, tool_name: str, arguments: dict[str, Any], server: MCPServer) -> None:
"""
Filter arguments to only include allowed parameters for the given tool.
@ -3029,7 +3029,7 @@ class MCPServerManager:
self,
server: MCPServer,
tool_name: str,
arguments: Dict[str, Any],
arguments: dict[str, Any],
) -> CallToolResult:
"""
Call an OpenAPI tool handler directly.
@ -3086,13 +3086,13 @@ class MCPServerManager:
async def pre_call_tool_check(
self,
name: str,
arguments: Dict[str, Any],
arguments: dict[str, Any],
server_name: str,
user_api_key_auth: Optional[UserAPIKeyAuth],
proxy_logging_obj: ProxyLogging,
server: MCPServer,
raw_headers: Optional[Dict[str, str]] = None,
) -> Dict[str, Any]:
raw_headers: Optional[dict[str, str]] = None,
) -> dict[str, Any]:
"""
Run pre-call checks and guardrail hooks for an MCP tool call.
@ -3152,7 +3152,7 @@ class MCPServerManager:
# Convert to LLM format for existing guardrail compatibility
synthetic_llm_data = proxy_logging_obj._convert_mcp_to_llm_format(mcp_request_obj, pre_hook_kwargs)
hook_result: Dict[str, Any] = {}
hook_result: dict[str, Any] = {}
try:
# Use standard pre_call_hook
modified_data = await proxy_logging_obj.pre_call_hook(
@ -3182,7 +3182,7 @@ class MCPServerManager:
def _create_during_hook_task(
self,
name: str,
arguments: Dict[str, Any],
arguments: dict[str, Any],
server_name_from_prefix: Optional[str],
user_api_key_auth: Optional[UserAPIKeyAuth],
proxy_logging_obj: ProxyLogging,
@ -3240,15 +3240,15 @@ class MCPServerManager:
self,
mcp_server: MCPServer,
original_tool_name: str,
arguments: Dict[str, Any],
tasks: List,
arguments: dict[str, Any],
tasks: list,
mcp_auth_header: Optional[str],
mcp_server_auth_headers: Optional[Dict[str, Dict[str, str]]],
oauth2_headers: Optional[Dict[str, str]],
raw_headers: Optional[Dict[str, str]],
mcp_server_auth_headers: Optional[dict[str, dict[str, str]]],
oauth2_headers: Optional[dict[str, str]],
raw_headers: Optional[dict[str, str]],
proxy_logging_obj: Optional[ProxyLogging],
host_progress_callback: Optional[Callable] = None,
hook_extra_headers: Optional[Dict[str, str]] = None,
hook_extra_headers: Optional[dict[str, str]] = None,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
) -> CallToolResult:
"""
@ -3279,7 +3279,7 @@ class MCPServerManager:
# Get server-specific auth header if available (case-insensitive)
# FIX: Added case-insensitive matching to handle auth header keys that may not match
# the exact case of server alias/name (e.g., '1litellmagcgateway' vs '1LiteLLMAGCGateway')
server_auth_header: Optional[Union[Dict[str, str], str]] = None
server_auth_header: Optional[Union[dict[str, str], str]] = None
if mcp_server_auth_headers:
# Normalize keys for case-insensitive lookup
from litellm.proxy._experimental.mcp_server.utils import (
@ -3298,7 +3298,7 @@ class MCPServerManager:
# Extract subject token for OAuth2 Token Exchange (OBO) flow
subject_token: Optional[str] = None
extra_headers: Optional[Dict[str, str]] = None
extra_headers: Optional[dict[str, str]] = None
if mcp_server.auth_type == MCPAuth.oauth2_token_exchange:
subject_token = self._extract_bearer_token(oauth2_headers, raw_headers)
elif mcp_server.auth_type == MCPAuth.oauth2:
@ -3486,9 +3486,9 @@ class MCPServerManager:
async def _resolve_oauth2_headers_for_tool_call(
self,
mcp_server: MCPServer,
oauth2_headers: Optional[Dict[str, str]],
oauth2_headers: Optional[dict[str, str]],
user_api_key_auth: Optional[UserAPIKeyAuth],
) -> Optional[Dict[str, str]]:
) -> Optional[dict[str, str]]:
"""Look up per-user OAuth headers when the client did not supply a token."""
if not mcp_server.needs_user_oauth_token or oauth2_headers or user_api_key_auth is None:
return oauth2_headers
@ -3525,7 +3525,7 @@ class MCPServerManager:
async def _gather_openapi_tool_tasks(
self,
tasks: List[Any],
tasks: list[Any],
proxy_logging_obj: Optional[ProxyLogging],
) -> CallToolResult:
"""Await OpenAPI tool tasks and return the tool call result."""
@ -3545,13 +3545,13 @@ class MCPServerManager:
self,
server_name: str,
name: str,
arguments: Dict[str, Any],
arguments: dict[str, Any],
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
mcp_auth_header: Optional[str] = None,
mcp_server_auth_headers: Optional[Dict[str, Dict[str, str]]] = None,
mcp_server_auth_headers: Optional[dict[str, dict[str, str]]] = None,
proxy_logging_obj: Optional[ProxyLogging] = None,
oauth2_headers: Optional[Dict[str, str]] = None,
raw_headers: Optional[Dict[str, str]] = None,
oauth2_headers: Optional[dict[str, str]] = None,
raw_headers: Optional[dict[str, str]] = None,
host_progress_callback: Optional[Callable] = None,
) -> CallToolResult:
"""
@ -3578,7 +3578,7 @@ class MCPServerManager:
# Allow validation and modification of tool calls before execution
# Using standard pre_call_hook
#########################################################
hook_result: Dict[str, Any] = {}
hook_result: dict[str, Any] = {}
if proxy_logging_obj:
hook_result = await self.pre_call_tool_check(
name=name,
@ -3704,7 +3704,7 @@ class MCPServerManager:
# Build prefix → server lookup covering every known form a tool name
# may take (alias / server_name / server_id / short ID). This is what
# makes the short-prefix mode work without breaking historical names.
prefix_to_server: Dict[str, MCPServer] = {}
prefix_to_server: dict[str, MCPServer] = {}
for server in registry_servers:
for known_prefix in iter_known_server_prefixes(server):
normalised = normalize_server_name(known_prefix)
@ -3765,7 +3765,7 @@ class MCPServerManager:
verbose_logger.info(f"Found {len(db_mcp_servers)} MCP servers in database")
previous_registry = self.registry
new_registry: Dict[str, MCPServer] = {}
new_registry: dict[str, MCPServer] = {}
# Stage one: build every server. Stage two assigns short prefixes
# against the *full* set so dedup is deterministic regardless of
@ -3811,7 +3811,7 @@ class MCPServerManager:
# Assign short prefixes against the full candidate set without
# publishing the staged registry to concurrent callers.
registered_registry: Dict[str, MCPServer] = {}
registered_registry: dict[str, MCPServer] = {}
registered_openapi_tools = False
for server_id, new_server in new_registry.items():
try:
@ -3837,7 +3837,7 @@ class MCPServerManager:
verbose_logger.debug("MCP registry refreshed (%s servers in registry)", len(registered_registry))
def get_mcp_servers_from_ids(self, server_ids: List[str]) -> List[MCPServer]:
def get_mcp_servers_from_ids(self, server_ids: list[str]) -> list[MCPServer]:
servers = []
registry = self.get_registry()
for server in registry.values():
@ -3845,7 +3845,7 @@ class MCPServerManager:
servers.append(server)
return servers
def _get_general_settings(self) -> Dict[str, Any]:
def _get_general_settings(self) -> dict[str, Any]:
"""Get general_settings, importing lazily to avoid circular imports."""
try:
from litellm.proxy.proxy_server import (
@ -3888,7 +3888,7 @@ class MCPServerManager:
return server
return None
def get_public_mcp_servers(self) -> List[MCPServer]:
def get_public_mcp_servers(self) -> list[MCPServer]:
"""
Return the MCP servers published to the AI Hub via /v1/mcp/make_public.
@ -3918,7 +3918,7 @@ class MCPServerManager:
if server.available_on_public_internet or server.server_id in public_ids
]
def expand_permission_list(self, identifiers: List[str]) -> List[str]:
def expand_permission_list(self, identifiers: list[str]) -> list[str]:
"""
Expand a permission list of server_ids/names/aliases into concrete
server_ids against the current region's config + DB registry union.
@ -3934,12 +3934,12 @@ class MCPServerManager:
if not identifiers:
return []
registry = self.get_registry()
expanded: Set[str] = set()
expanded: set[str] = set()
for identifier in identifiers:
if identifier in registry:
expanded.add(identifier)
continue
matches: List[str] = [
matches: list[str] = [
server_id
for server_id, server in registry.items()
if server.alias == identifier or server.server_name == identifier or server.name == identifier
@ -3960,8 +3960,8 @@ class MCPServerManager:
def expand_tool_permissions(
self,
tool_permissions: Optional[Dict[str, List[str]]],
) -> Dict[str, List[str]]:
tool_permissions: Optional[dict[str, list[str]]],
) -> dict[str, list[str]]:
"""
Rewrite an ``mcp_tool_permissions`` dict keyed by id/name/alias so
every key is a concrete server_id where possible. Tool lists from
@ -3976,7 +3976,7 @@ class MCPServerManager:
"""
if not tool_permissions:
return {}
result: Dict[str, List[str]] = {}
result: dict[str, list[str]] = {}
for key, tools in tool_permissions.items():
for server_id in self.expand_permission_list([key]):
result.setdefault(server_id, []).extend(tools or [])
@ -4017,7 +4017,7 @@ class MCPServerManager:
return server
return None
def get_filtered_registry(self, client_ip: Optional[str] = None) -> Dict[str, MCPServer]:
def get_filtered_registry(self, client_ip: Optional[str] = None) -> dict[str, MCPServer]:
"""
Get registry filtered by client IP access control.
@ -4186,8 +4186,8 @@ class MCPServerManager:
async def get_all_mcp_servers_with_health_and_teams(
self,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
server_ids: Optional[List[str]] = None,
) -> List[LiteLLM_MCPServerTable]:
server_ids: Optional[list[str]] = None,
) -> list[LiteLLM_MCPServerTable]:
"""
Get all MCP servers that the user has access to, with health status and team information.
@ -4216,7 +4216,7 @@ class MCPServerManager:
async def get_all_allowed_mcp_servers(
self,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
) -> List[LiteLLM_MCPServerTable]:
) -> list[LiteLLM_MCPServerTable]:
"""
Get all MCP servers that the user has access to.
@ -4229,7 +4229,7 @@ class MCPServerManager:
# Get allowed server IDs
allowed_server_ids = await self.get_allowed_mcp_servers(user_api_key_auth)
list_mcp_servers: List[LiteLLM_MCPServerTable] = []
list_mcp_servers: list[LiteLLM_MCPServerTable] = []
for server_id in allowed_server_ids:
server = self.get_mcp_server_by_id(server_id)
@ -4244,8 +4244,8 @@ class MCPServerManager:
@staticmethod
def _env_vars_to_models(
env_vars: Optional[List[Dict[str, Any]]],
) -> Optional[List[MCPEnvVar]]:
env_vars: Optional[list[dict[str, Any]]],
) -> Optional[list[MCPEnvVar]]:
if env_vars is None:
return None
return [MCPEnvVar.model_validate(env_var) for env_var in env_vars]
@ -4291,21 +4291,21 @@ class MCPServerManager:
max_concurrent_requests=server.max_concurrent_requests,
)
async def get_all_mcp_servers_unfiltered(self) -> List[LiteLLM_MCPServerTable]:
async def get_all_mcp_servers_unfiltered(self) -> list[LiteLLM_MCPServerTable]:
"""Return all MCP servers from registry without applying access controls."""
registry = self.get_registry()
if not registry:
return []
servers: List[LiteLLM_MCPServerTable] = []
servers: list[LiteLLM_MCPServerTable] = []
for server in registry.values():
servers.append(self._build_mcp_server_table(server))
return servers
async def get_all_mcp_servers_with_health_unfiltered(
self, server_ids: Optional[List[str]] = None
) -> List[LiteLLM_MCPServerTable]:
self, server_ids: Optional[list[str]] = None
) -> list[LiteLLM_MCPServerTable]:
"""Return health info for all servers in registry regardless of user access."""
registry = self.get_registry()
@ -4322,7 +4322,7 @@ class MCPServerManager:
return await self._run_health_checks(target_server_ids)
async def _run_health_checks(self, target_server_ids: List[str]) -> List[LiteLLM_MCPServerTable]:
async def _run_health_checks(self, target_server_ids: list[str]) -> list[LiteLLM_MCPServerTable]:
if not target_server_ids:
return []

View file

@ -222,7 +222,7 @@
"limit": 38
},
"RET504": {
"limit": 722
"limit": 721
},
"RUF010": {
"limit": 874
@ -249,7 +249,7 @@
"limit": 6
},
"RUF059": {
"limit": 74
"limit": 73
},
"RUF100": {
"limit": 480
@ -306,7 +306,7 @@
"limit": 9
},
"TID251": {
"limit": 2714
"limit": 2710
},
"TRY002": {
"limit": 548
@ -324,7 +324,7 @@
"limit": 883
},
"UP006": {
"limit": 13041
"limit": 12870
},
"UP007": {
"limit": 2570
@ -354,7 +354,7 @@
"limit": 4
},
"UP035": {
"limit": 2300
"limit": 2295
},
"UP036": {
"limit": 4

View file

@ -31,9 +31,6 @@ if [ $? -ne 0 ]; then
exit 1
fi
# print contents of ui_colors.json
echo "Contents of ui_colors.json:"
cat ui_colors.json
# Run npm build
npm run build

View file

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "gray",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks",
"utils": "@/lib/cva.config"
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,13 @@
"entry": ["scripts/**/*.{ts,mjs}"],
"project": ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}", "scripts/**/*.{ts,mjs}", "e2e_tests/**/*.ts"],
"ignore": ["src/lib/http/schema.d.ts"],
"ignoreDependencies": ["openapi-typescript"],
"ignoreDependencies": [
"openapi-typescript",
"@headlessui/tailwindcss",
"@tailwindcss/forms",
"tailwindcss",
"tw-animate-css"
],
"playwright": {
"config": "e2e_tests/playwright.config.ts",
"entry": ["e2e_tests/**/*.spec.ts", "e2e_tests/**/*.setup.ts", "e2e_tests/globalSetup.ts"]

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@
"gen:api": "node scripts/gen-api-types.mjs"
},
"dependencies": {
"@ant-design/cssinjs": "1.24.0",
"@anthropic-ai/sdk": "0.92.0",
"@headlessui/tailwindcss": "0.2.2",
"@heroicons/react": "1.0.6",
@ -42,6 +43,7 @@
"next": "16.2.6",
"openai": "4.104.0",
"papaparse": "5.5.3",
"radix-ui": "1.6.1",
"react": "18.3.1",
"react-copy-to-clipboard": "5.1.1",
"react-dom": "18.3.1",
@ -55,6 +57,7 @@
"@eslint/js": "9.39.2",
"@playwright/test": "1.58.1",
"@tailwindcss/forms": "0.5.11",
"@tailwindcss/postcss": "4.3.2",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.2",
@ -67,7 +70,6 @@
"@types/react-syntax-highlighter": "15.5.13",
"@vitest/coverage-v8": "3.2.6",
"@vitest/ui": "3.2.6",
"autoprefixer": "10.4.24",
"eslint": "9.39.2",
"eslint-config-next": "16.2.6",
"eslint-config-prettier": "10.1.8",
@ -77,7 +79,8 @@
"openapi-typescript": "7.13.0",
"postcss": "8.5.13",
"prettier": "3.2.5",
"tailwindcss": "3.4.19",
"tailwindcss": "4.3.2",
"tw-animate-css": "1.4.0",
"typescript": "5.9.3",
"typescript-eslint": "8.60.1",
"vitest": "3.2.6"

View file

@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
"@tailwindcss/postcss": {},
},
};

View file

@ -1,9 +1,7 @@
import React from "react";
import { ExternalLink } from "lucide-react";
function cn(...parts: Array<string | undefined>) {
return parts.filter(Boolean).join(" ");
}
import { cn } from "@/lib/cva.config";
export type DocLinkProps = {
href?: string;
@ -18,8 +16,8 @@ const DocLink = ({ href, className }: DocLinkProps) => {
rel="noopener noreferrer"
title="Open documentation in a new tab"
className={cn(
"inline-flex items-center gap-2 rounded-xl border border-zinc-200 bg-white/80 px-3.5 py-2 text-sm font-medium text-zinc-700 shadow-sm",
"hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 active:translate-y-[0.5px]",
"inline-flex items-center gap-2 rounded-xl border border-zinc-200 bg-white/80 px-3.5 py-2 text-sm font-medium text-zinc-700 shadow-xs",
"hover:bg-white focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 active:translate-y-[0.5px]",
className,
)}
>

View file

@ -151,7 +151,7 @@ const HealthCheckDetails: React.FC<{ response: any }> = ({ response }) => {
};
return (
<div className="bg-white rounded-lg shadow">
<div className="bg-white rounded-lg shadow-sm">
<TabGroup>
<TabList className="border-b border-gray-200 px-4">
<Tab className="px-4 py-2 text-sm font-medium text-gray-600 hover:text-gray-800">Summary</Tab>
@ -225,7 +225,7 @@ const HealthCheckDetails: React.FC<{ response: any }> = ({ response }) => {
<TabPanel className="p-4">
<div className="bg-gray-50 rounded-md p-4 font-mono text-sm">
<pre className="whitespace-pre-wrap break-words overflow-auto max-h-[500px]">
<pre className="whitespace-pre-wrap wrap-break-word overflow-auto max-h-[500px]">
{(() => {
try {
const data = {

View file

@ -175,7 +175,7 @@ const CostTrackingSettings: React.FC<CostTrackingSettingsProps> = ({ userID, use
</div>
{/* Main Content Card with Accordions */}
<div className="bg-white rounded-lg shadow w-full max-w-full space-y-4">
<div className="bg-white rounded-lg shadow-sm w-full max-w-full space-y-4">
{/* Accordion 1: Provider Discounts - Only for proxy admins */}
{isProxyAdmin && (
<Accordion>

View file

@ -31,7 +31,7 @@ const HowItWorks: React.FC = () => {
<Text className="font-medium text-gray-900 text-sm mb-1">Cost Calculation</Text>
<Text className="text-xs text-gray-600">
Discounts are applied to provider costs:{" "}
<code className="bg-gray-100 px-1.5 py-0.5 rounded text-xs">
<code className="bg-gray-100 px-1.5 py-0.5 rounded-sm text-xs">
final_cost = base_cost × (1 - discount%/100)
</code>
</Text>
@ -65,19 +65,19 @@ const HowItWorks: React.FC = () => {
<Text className="text-xs text-gray-600 mt-3 mb-2">Look for these headers in the response:</Text>
<div className="space-y-1.5">
<div className="flex items-start gap-3">
<code className="bg-gray-100 px-2 py-1 rounded text-xs font-mono text-gray-800 whitespace-nowrap">
<code className="bg-gray-100 px-2 py-1 rounded-sm text-xs font-mono text-gray-800 whitespace-nowrap">
x-litellm-response-cost
</code>
<Text className="text-xs text-gray-600">Final cost after discount</Text>
</div>
<div className="flex items-start gap-3">
<code className="bg-gray-100 px-2 py-1 rounded text-xs font-mono text-gray-800 whitespace-nowrap">
<code className="bg-gray-100 px-2 py-1 rounded-sm text-xs font-mono text-gray-800 whitespace-nowrap">
x-litellm-response-cost-original
</code>
<Text className="text-xs text-gray-600">Original cost before discount</Text>
</div>
<div className="flex items-start gap-3">
<code className="bg-gray-100 px-2 py-1 rounded text-xs font-mono text-gray-800 whitespace-nowrap">
<code className="bg-gray-100 px-2 py-1 rounded-sm text-xs font-mono text-gray-800 whitespace-nowrap">
x-litellm-response-cost-discount-amount
</code>
<Text className="text-xs text-gray-600">Amount discounted</Text>

View file

@ -201,9 +201,9 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
)}
{record.loading && <Spin indicator={<LoadingOutlined spin />} size="small" />}
</div>
{record.error && <div className="text-xs text-red-600 bg-red-50 px-2 py-1 rounded"> {record.error}</div>}
{record.error && <div className="text-xs text-red-600 bg-red-50 px-2 py-1 rounded-sm"> {record.error}</div>}
{record.hasZeroCost && !record.error && (
<div className="text-xs text-amber-600 bg-amber-50 px-2 py-1 rounded">
<div className="text-xs text-amber-600 bg-amber-50 px-2 py-1 rounded-sm">
No pricing data found for this model. Set base_model in config.
</div>
)}
@ -295,7 +295,7 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult, timePe
</div>
{/* Combined Totals - Always show when there are results */}
<Card size="small" className="bg-gradient-to-r from-slate-50 to-blue-50 border-slate-200">
<Card size="small" className="bg-linear-to-r from-slate-50 to-blue-50 border-slate-200">
<Row gutter={[16, 8]}>
<Col xs={24} sm={12}>
<Statistic

View file

@ -242,7 +242,7 @@ export function GuardrailsOverview({
)}
<div className="px-6 py-4 border-b border-gray-200 flex items-start justify-between gap-4">
<div>
<Typography.Title level={5} className="!mb-0 text-gray-900">
<Typography.Title level={5} className="mb-0! text-gray-900">
Guardrail Performance
</Typography.Title>
<p className="text-xs text-gray-500 mt-0.5">Click a guardrail to view details, logs, and configuration</p>

View file

@ -331,7 +331,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
{/* Missing Provider Banner */}
{showMissingProviderBanner && (
<div className="mb-4 px-4 py-3 bg-blue-50 rounded-lg border border-blue-100 flex items-center gap-4">
<div className="flex-shrink-0 w-10 h-10 bg-white rounded-full flex items-center justify-center border border-blue-200">
<div className="shrink-0 w-10 h-10 bg-white rounded-full flex items-center justify-center border border-blue-200">
<PlusCircleOutlined style={{ fontSize: "18px", color: "#6366f1" }} />
</div>
<div className="flex-1 min-w-0">
@ -345,7 +345,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
href="https://models.litellm.ai/?request=true"
target="_blank"
rel="noopener noreferrer"
className="flex-shrink-0 inline-flex items-center gap-2 px-4 py-2 bg-[#6366f1] hover:bg-[#5558e3] text-white text-sm font-medium rounded-lg transition-colors"
className="shrink-0 inline-flex items-center gap-2 px-4 py-2 bg-[#6366f1] hover:bg-[#5558e3] text-white text-sm font-medium rounded-lg transition-colors"
>
Request Provider
<svg
@ -368,7 +368,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
setShowMissingProviderBanner(false);
localStorage.setItem("hideMissingProviderBanner", "true");
}}
className="flex-shrink-0 p-1 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-colors"
className="shrink-0 p-1 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-colors"
aria-label="Dismiss banner"
>
<svg

View file

@ -239,7 +239,7 @@ const AllModelsTab = ({
<TabPanel>
<Grid>
<div className="flex flex-col space-y-4">
<div className="bg-white rounded-lg shadow">
<div className="bg-white rounded-lg shadow-sm">
{/* Current Team and View Mode Selector - Prominent Section */}
<div className="border-b px-6 py-4 bg-gray-50">
<div className="flex items-center justify-between">
@ -339,7 +339,7 @@ const AllModelsTab = ({
{modelViewMode === "current_team" && (
<div className="flex items-start gap-2 mt-3">
<InfoCircleOutlined className="text-gray-400 mt-0.5 flex-shrink-0 text-xs" />
<InfoCircleOutlined className="text-gray-400 mt-0.5 shrink-0 text-xs" />
<div className="text-xs text-gray-500">
{currentTeam === "personal" ? (
<span>
@ -381,7 +381,7 @@ const AllModelsTab = ({
type="text"
placeholder="Search model names..."
data-testid="model-search-input"
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={modelNameSearch}
onChange={(e) => setModelNameSearch(e.target.value)}
/>

View file

@ -194,7 +194,9 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
{taskId && (
<div className="mb-1.5 flex items-center">
<span className="font-medium text-gray-700 w-24">Task ID:</span>
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded text-xs font-mono">{taskId}</code>
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded-sm text-xs font-mono">
{taskId}
</code>
<CopyOutlined
className="ml-2 cursor-pointer text-gray-400 hover:text-blue-500"
onClick={() => copyToClipboard(taskId)}
@ -205,7 +207,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
{contextId && (
<div className="mb-1.5 flex items-center">
<span className="font-medium text-gray-700 w-24">Session ID:</span>
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded text-xs font-mono">
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded-sm text-xs font-mono">
{contextId}
</code>
<CopyOutlined
@ -219,7 +221,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
{metadata && Object.keys(metadata).length > 0 && (
<div className="mt-3">
<span className="font-medium text-gray-700">Custom Metadata:</span>
<pre className="mt-1.5 p-2 bg-white border border-gray-200 rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap">
<pre className="mt-1.5 p-2 bg-white border border-gray-200 rounded-sm text-xs font-mono overflow-x-auto whitespace-pre-wrap">
{JSON.stringify(metadata, null, 2)}
</pre>
</div>

View file

@ -102,7 +102,7 @@ function ConnectTabContent({
<div className="mx-auto max-w-3xl space-y-6">
<div>
<h3 className="text-sm font-semibold text-gray-900 mb-1">Proxy base URL</h3>
<p className="text-sm text-gray-600 font-mono bg-gray-50 px-2 py-1.5 rounded border border-gray-200 break-all">
<p className="text-sm text-gray-600 font-mono bg-gray-50 px-2 py-1.5 rounded-sm border border-gray-200 break-all">
{baseUrl}
</p>
</div>
@ -442,7 +442,7 @@ export default function AgentBuilderView({
return (
<div className="flex h-full flex-col bg-white text-gray-900">
<div className="flex flex-shrink-0 flex-col border-b border-gray-200">
<div className="flex shrink-0 flex-col border-b border-gray-200">
<div className="flex h-12 items-center justify-between px-4">
<span className="text-sm font-medium text-gray-900">Agent Builder</span>
{isNewAgent ? (
@ -460,7 +460,7 @@ export default function AgentBuilderView({
)}
</div>
<div className="flex items-center gap-2 border-t border-amber-200 bg-amber-50 px-4 py-2 text-xs text-amber-800">
<ExperimentOutlined className="flex-shrink-0 text-amber-600" />
<ExperimentOutlined className="shrink-0 text-amber-600" />
<span>
Agent Builder is experimental and may change or be removed without notice. Wed love your feedbackemail us
at{" "}
@ -474,7 +474,7 @@ export default function AgentBuilderView({
<div className="flex flex-1 overflow-hidden">
{/* Roster */}
<div className="w-60 flex-shrink-0 border-r border-gray-200 bg-white flex flex-col">
<div className="w-60 shrink-0 border-r border-gray-200 bg-white flex flex-col">
<div className="flex items-center justify-between border-b border-gray-200 p-3">
<span className="text-xs font-semibold uppercase tracking-wide text-gray-500">Agents</span>
<Button type="text" size="small" icon={<PlusOutlined />} onClick={handleAddAgent} aria-label="Add agent" />
@ -542,7 +542,7 @@ export default function AgentBuilderView({
{isNewAgent || selectedAgent ? (
<div className="mx-auto max-w-xl space-y-4">
{!selectedAgentModelId && selectedAgent && (
<div className="rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
<div className="rounded-sm border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
This agent cannot be updated or deleted here (missing model id). Manage it from Models
&amp; Endpoints.
</div>
@ -616,8 +616,8 @@ export default function AgentBuilderView({
{selectedAgent && draftTools.length > 0 && (
<p className="mt-1 text-xs text-gray-500">
{draftTools.length} MCP server{draftTools.length !== 1 ? "s" : ""} saved. Use the same{" "}
<code className="rounded bg-gray-100 px-1">tools</code> array in chat completions when
calling this agent.
<code className="rounded-sm bg-gray-100 px-1">tools</code> array in chat completions
when calling this agent.
</p>
)}
</div>

View file

@ -27,7 +27,7 @@ const ChatImageRenderer: React.FC<ChatImageRendererProps> = ({ message }) => {
alt="User uploaded image"
width={256}
height={200}
className="max-w-64 rounded-md border border-gray-200 shadow-sm"
className="max-w-64 rounded-md border border-gray-200 shadow-xs"
style={{ maxHeight: "200px", width: "auto", height: "auto" }}
/>
)}

View file

@ -43,7 +43,7 @@ function ChatMessageBubble({
return (
<div className={`mb-4 ${isUser ? "text-right" : "text-left"}`}>
<div
className="inline-block max-w-[80%] rounded-lg shadow-sm p-3.5 px-4"
className="inline-block max-w-[80%] rounded-lg shadow-xs p-3.5 px-4"
style={{
backgroundColor: isUser ? "#f0f8ff" : "#ffffff",
border: isUser ? "1px solid #e6f0fa" : "1px solid #f0f0f0",
@ -66,7 +66,9 @@ function ChatMessageBubble({
</div>
<strong className="text-sm capitalize">{message.role}</strong>
{message.role === "assistant" && message.model && (
<span className="text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-normal">{message.model}</span>
<span className="text-xs px-2 py-0.5 rounded-sm bg-gray-100 text-gray-600 font-normal">
{message.model}
</span>
)}
</div>
@ -103,7 +105,7 @@ function ChatMessageBubble({
{/* Message body */}
<div
className="whitespace-pre-wrap break-words max-w-full message-content"
className="whitespace-pre-wrap wrap-break-word max-w-full message-content"
style={{
wordWrap: "break-word",
overflowWrap: "break-word",
@ -115,7 +117,7 @@ function ChatMessageBubble({
<img
src={typeof message.content === "string" ? message.content : ""}
alt="Generated image"
className="max-w-full rounded-md border border-gray-200 shadow-sm"
className="max-w-full rounded-md border border-gray-200 shadow-xs"
style={{ maxHeight: "500px" }}
/>
) : message.isAudio ? (
@ -153,7 +155,7 @@ function ChatMessageBubble({
</SyntaxHighlighter>
) : (
<code
className={`${className} px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono`}
className={`${className} px-1.5 py-0.5 rounded-sm bg-gray-100 text-sm font-mono`}
style={{ wordBreak: "break-word" }}
{...props}
>
@ -173,7 +175,7 @@ function ChatMessageBubble({
<img
src={message.image.url}
alt="Generated image"
className="max-w-full rounded-md border border-gray-200 shadow-sm"
className="max-w-full rounded-md border border-gray-200 shadow-xs"
style={{ maxHeight: "500px" }}
/>
</div>

View file

@ -1447,7 +1447,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div className="flex items-center gap-1">
<span className="font-medium">{toolset.toolset_name}</span>
<span
className="text-xs px-1 rounded"
className="text-xs px-1 rounded-sm"
style={{ background: "#ede9fe", color: "#7c3aed" }}
>
Toolset
@ -1540,7 +1540,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
if (tools.length === 0) return null;
return (
<div key={serverId} className="border rounded p-2">
<div key={serverId} className="border rounded-sm p-2">
<Text className="text-xs text-gray-600 mb-1">
Limit tools for {server?.alias || server?.server_name || serverId}:
</Text>
@ -1583,7 +1583,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
return (
<div
key={serverId}
className="border border-blue-100 rounded p-2 bg-blue-50 flex items-center justify-between"
className="border border-blue-100 rounded-sm p-2 bg-blue-50 flex items-center justify-between"
>
<Text className="text-xs text-blue-700">{serverName} requires your API key</Text>
{server.has_user_credential ? (
@ -1770,7 +1770,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
chatHistory[chatHistory.length - 1].role === "user" && (
<div className="text-left mb-4">
<div
className="inline-block max-w-[80%] rounded-lg shadow-sm p-3.5 px-4"
className="inline-block max-w-[80%] rounded-lg shadow-xs p-3.5 px-4"
style={{
backgroundColor: "#ffffff",
border: "1px solid #f0f0f0",
@ -1834,7 +1834,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
className="max-w-32 max-h-32 rounded-md border border-gray-200 object-cover"
/>
<button
className="absolute top-1 right-1 bg-white shadow-sm border border-gray-200 rounded px-1 py-1 text-red-500 hover:bg-red-50 text-xs"
className="absolute top-1 right-1 bg-white shadow-xs border border-gray-200 rounded-sm px-1 py-1 text-red-500 hover:bg-red-50 text-xs"
onClick={() => handleRemoveImage(index)}
>
<DeleteOutlined />
@ -1894,7 +1894,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
</span>
</div>
<button
className="bg-white shadow-sm border border-gray-200 rounded px-2 py-1 text-red-500 hover:bg-red-50 text-xs"
className="bg-white shadow-xs border border-gray-200 rounded-sm px-2 py-1 text-red-500 hover:bg-red-50 text-xs"
onClick={handleRemoveAudio}
>
<DeleteOutlined /> Remove
@ -1924,7 +1924,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
{/* Code Interpreter indicator and sample prompts when enabled */}
{endpointType === EndpointType.RESPONSES && codeInterpreter.enabled && (
<div className="mb-2 space-y-2">
<div className="px-3 py-2 bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg border border-blue-200 flex items-center justify-between">
<div className="px-3 py-2 bg-linear-to-r from-blue-50 to-purple-50 rounded-lg border border-blue-200 flex items-center justify-between">
<div className="flex items-center gap-2">
{isLoading ? (
<>
@ -1988,7 +1988,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div className="flex items-center gap-2">
<div className="flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]">
{/* Left: attachment and code interpreter icons */}
<div className="flex-shrink-0 mr-2 flex items-center gap-1">
<div className="shrink-0 mr-2 flex items-center gap-1">
{endpointType === EndpointType.RESPONSES && !responsesUploadedImage && (
<ResponsesImageUpload
responsesUploadedImage={responsesUploadedImage}
@ -2116,7 +2116,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
? !uploadedAudio
: !inputMessage.trim())
}
className="flex-shrink-0 ml-2 !w-8 !h-8 !min-w-8 !p-0 !rounded-full !bg-blue-600 hover:!bg-blue-700 disabled:!bg-gray-300 !border-none !text-white disabled:!text-gray-500 !flex !items-center !justify-center"
className="shrink-0 ml-2 w-8! h-8! min-w-8! p-0! rounded-full! bg-blue-600! hover:bg-blue-700! disabled:bg-gray-300! border-none! text-white! disabled:text-gray-500! flex! items-center! justify-center!"
>
<ArrowUpOutlined style={{ fontSize: "14px" }} />
</TremorButton>
@ -2225,7 +2225,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<li>The tool call is routed to the correct underlying MCP server automatically.</li>
</ol>
</div>
<div className="bg-purple-50 border border-purple-200 rounded p-3">
<div className="bg-purple-50 border border-purple-200 rounded-sm p-3">
<p className="text-sm text-purple-800">
<strong>Example:</strong> A &quot;GitHub Read-only&quot; toolset might include only{" "}
<code>list_repos</code> and <code>get_file</code> from a GitHub MCP server preventing agents from making

View file

@ -46,7 +46,7 @@ const CodeInterpreterTool: React.FC<CodeInterpreterToolProps> = ({
};
return (
<div className="border border-gray-200 rounded-lg p-3 bg-gradient-to-r from-blue-50 to-purple-50">
<div className="border border-gray-200 rounded-lg p-3 bg-linear-to-r from-blue-50 to-purple-50">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<CodeOutlined className="text-blue-500" />

View file

@ -24,7 +24,7 @@ const ResponsesImageRenderer: React.FC<ResponsesImageRendererProps> = ({ message
<img
src={message.imagePreviewUrl}
alt="User uploaded image"
className="max-w-64 rounded-md border border-gray-200 shadow-sm"
className="max-w-64 rounded-md border border-gray-200 shadow-xs"
style={{ maxHeight: "200px" }}
/>
)}

View file

@ -63,18 +63,18 @@ export function SearchResultsDisplay({ searchResults }: SearchResultsDisplayProp
>
<div className="flex items-center gap-2 flex-1 min-w-0">
<svg
className={`w-4 h-4 text-gray-400 transition-transform flex-shrink-0 ${isResultExpanded ? "transform rotate-90" : ""}`}
className={`w-4 h-4 text-gray-400 transition-transform shrink-0 ${isResultExpanded ? "transform rotate-90" : ""}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
<FileTextOutlined className="text-gray-400 flex-shrink-0" style={{ fontSize: "12px" }} />
<FileTextOutlined className="text-gray-400 shrink-0" style={{ fontSize: "12px" }} />
<span className="text-xs font-medium text-gray-700 truncate">
{result.filename || result.file_id || `Result ${resultIndex + 1}`}
</span>
<span className="text-xs px-2 py-0.5 rounded bg-blue-100 text-blue-700 font-mono flex-shrink-0">
<span className="text-xs px-2 py-0.5 rounded-sm bg-blue-100 text-blue-700 font-mono shrink-0">
{result.score.toFixed(3)}
</span>
</div>
@ -85,7 +85,7 @@ export function SearchResultsDisplay({ searchResults }: SearchResultsDisplayProp
<div className="p-3 space-y-2">
{result.content.map((content, contentIndex) => (
<div key={contentIndex}>
<div className="text-xs font-mono bg-gray-50 p-2 rounded text-gray-800 whitespace-pre-wrap break-words">
<div className="text-xs font-mono bg-gray-50 p-2 rounded-sm text-gray-800 whitespace-pre-wrap wrap-break-word">
{content.text}
</div>
</div>

View file

@ -87,7 +87,7 @@ const SessionManagement: React.FC<SessionManagementProps> = ({
title={
<div className="text-xs">
<div className="mb-1">Copy response ID to continue session:</div>
<div className="bg-gray-800 text-gray-100 p-2 rounded font-mono text-xs whitespace-pre-wrap">
<div className="bg-gray-800 text-gray-100 p-2 rounded-sm font-mono text-xs whitespace-pre-wrap">
{`curl -X POST "your-proxy-url/v1/responses" \\
-H "Authorization: Bearer your-api-key" \\
-H "Content-Type: application/json" \\
@ -102,7 +102,10 @@ const SessionManagement: React.FC<SessionManagementProps> = ({
}
overlayStyle={{ maxWidth: "500px" }}
>
<button onClick={handleCopySessionId} className="ml-2 p-1 hover:bg-green-100 rounded transition-colors">
<button
onClick={handleCopySessionId}
className="ml-2 p-1 hover:bg-green-100 rounded-sm transition-colors"
>
<CopyOutlined style={{ fontSize: "12px" }} />
</button>
</Tooltip>

View file

@ -689,7 +689,7 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
const showSuggestedPrompts = !hasMessages && !isAnyComparisonLoading && !hasAttachment;
return (
<div className="w-full h-full p-4 bg-white">
<div className="rounded-2xl border border-gray-200 bg-white shadow-sm min-h-[calc(100vh-160px)] flex flex-col">
<div className="rounded-2xl border border-gray-200 bg-white shadow-xs min-h-[calc(100vh-160px)] flex flex-col">
<div className="border-b px-4 py-2">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-2">
@ -746,7 +746,7 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
</div>
<div
className="grid flex-1 min-h-0 auto-rows-[minmax(0,1fr)]"
className="grid flex-1 min-h-0 auto-rows-fr"
style={{
gridTemplateColumns: `repeat(${comparisons.length}, minmax(0, 1fr))`,
}}

View file

@ -95,7 +95,7 @@ export function ComparisonPanel({
{/* Close button in top right */}
<button
onClick={handleClosePopover}
className="absolute top-0 right-0 p-1 hover:bg-gray-100 rounded transition-colors text-gray-500 hover:text-gray-700 z-10"
className="absolute top-0 right-0 p-1 hover:bg-gray-100 rounded-sm transition-colors text-gray-500 hover:text-gray-700 z-10"
>
<X size={14} />
</button>

View file

@ -49,7 +49,7 @@ export function MessageDisplay({ messages, isLoading }: MessageDisplayProps) {
const renderMessageBody = (message: MessageType) => (
<div
className="whitespace-pre-wrap break-words"
className="whitespace-pre-wrap wrap-break-word"
style={{
wordWrap: "break-word",
overflowWrap: "break-word",
@ -84,7 +84,7 @@ export function MessageDisplay({ messages, isLoading }: MessageDisplayProps) {
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code className={`${className} px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono`} {...props}>
<code className={`${className} px-1.5 py-0.5 rounded-sm bg-gray-100 text-sm font-mono`} {...props}>
{children}
</code>
);
@ -127,7 +127,7 @@ export function MessageDisplay({ messages, isLoading }: MessageDisplayProps) {
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-700">{displayModel}</span>
{assistantMessage.toolName && (
<span className="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600">
<span className="rounded-sm bg-gray-100 px-2 py-0.5 text-xs text-gray-600">
{assistantMessage.toolName}
</span>
)}

View file

@ -28,7 +28,7 @@ export function MessageInput({ value, onChange, onSend, disabled, hasAttachment,
return (
<div className="flex items-center gap-2">
<div className="flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]">
{uploadComponent && <div className="flex-shrink-0 mr-2">{uploadComponent}</div>}
{uploadComponent && <div className="shrink-0 mr-2">{uploadComponent}</div>}
<TextArea
value={value}
onChange={(e) => onChange(e.target.value)}

View file

@ -696,9 +696,9 @@ export default function ComplianceUI({
return (
<div className="w-full h-full p-4 bg-white">
<div className="rounded-2xl border border-gray-200 bg-white shadow-sm min-h-[calc(100vh-160px)] flex flex-col overflow-hidden">
<div className="rounded-2xl border border-gray-200 bg-white shadow-xs min-h-[calc(100vh-160px)] flex flex-col overflow-hidden">
{/* Top config */}
<div className="flex-shrink-0 border-b border-gray-200 px-6 py-4">
<div className="shrink-0 border-b border-gray-200 px-6 py-4">
<div className="mb-3">
<h3 className="text-sm font-semibold text-gray-900">Test Configuration</h3>
<p className="text-xs text-gray-500 mt-0.5">Select policies, guardrails, or both to test against.</p>
@ -719,7 +719,7 @@ export default function ComplianceUI({
)}
</div>
<div className="flex flex-col items-center pt-6 flex-shrink-0">
<div className="flex flex-col items-center pt-6 shrink-0">
<div className="w-px h-4 bg-gray-200" />
<span className="text-[10px] font-medium text-gray-400 my-1">or</span>
<div className="w-px h-4 bg-gray-200" />
@ -755,7 +755,7 @@ export default function ComplianceUI({
className="w-full flex items-center gap-2.5 px-3 py-2 text-sm text-left hover:bg-gray-50"
>
<div
className={`w-4 h-4 rounded border flex items-center justify-center flex-shrink-0 ${selectedGuardrails.includes(g.id) ? "bg-blue-500 border-blue-500" : "border-gray-300"}`}
className={`w-4 h-4 rounded-sm border flex items-center justify-center shrink-0 ${selectedGuardrails.includes(g.id) ? "bg-blue-500 border-blue-500" : "border-gray-300"}`}
>
{selectedGuardrails.includes(g.id) && <Check className="w-3 h-3 text-white" />}
</div>
@ -776,7 +776,7 @@ export default function ComplianceUI({
return (
<span
key={id}
className="inline-flex items-center gap-1 text-[11px] bg-indigo-50 text-indigo-700 px-1.5 py-0.5 rounded font-medium"
className="inline-flex items-center gap-1 text-[11px] bg-indigo-50 text-indigo-700 px-1.5 py-0.5 rounded-sm font-medium"
>
{g?.name}
<button
@ -794,7 +794,7 @@ export default function ComplianceUI({
)}
</div>
<div className="flex flex-col gap-1.5 pt-6 flex-shrink-0">
<div className="flex flex-col gap-1.5 pt-6 shrink-0">
{isRunning ? (
<button
type="button"
@ -837,7 +837,7 @@ export default function ComplianceUI({
{/* Panels */}
<div className="flex flex-1 min-h-0 overflow-hidden">
{/* Left: Prompt library */}
<div className="w-[400px] flex-shrink-0 border-r border-gray-200 flex flex-col bg-white overflow-hidden">
<div className="w-[400px] shrink-0 border-r border-gray-200 flex flex-col bg-white overflow-hidden">
<div className="flex-1 overflow-y-auto min-h-0">
<div className="px-4 pt-4 pb-2">
<div className="flex items-center justify-between mb-2.5">
@ -854,7 +854,7 @@ export default function ComplianceUI({
value={searchPrompt}
onChange={(e) => setSearchPrompt(e.target.value)}
placeholder="Search prompts..."
className="w-full border border-gray-200 rounded-lg pl-8 pr-3 py-1.5 text-xs placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400"
className="w-full border border-gray-200 rounded-lg pl-8 pr-3 py-1.5 text-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400"
/>
</div>
@ -883,7 +883,7 @@ export default function ComplianceUI({
setShowAddPrompt(!showAddPrompt);
setShowCsvUpload(false);
}}
className={`flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded transition-colors ${showAddPrompt ? "bg-blue-50 text-blue-600" : "text-gray-500 hover:bg-gray-100"}`}
className={`flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded-sm transition-colors ${showAddPrompt ? "bg-blue-50 text-blue-600" : "text-gray-500 hover:bg-gray-100"}`}
>
<Plus className="w-3 h-3" /> Add
</button>
@ -893,7 +893,7 @@ export default function ComplianceUI({
setShowCsvUpload(!showCsvUpload);
setShowAddPrompt(false);
}}
className={`flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded transition-colors ${showCsvUpload ? "bg-blue-50 text-blue-600" : "text-gray-500 hover:bg-gray-100"}`}
className={`flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded-sm transition-colors ${showCsvUpload ? "bg-blue-50 text-blue-600" : "text-gray-500 hover:bg-gray-100"}`}
>
<Upload className="w-3 h-3" /> CSV
</button>
@ -908,21 +908,21 @@ export default function ComplianceUI({
onChange={(e) => setNewPromptText(e.target.value)}
placeholder="Enter your test prompt..."
rows={2}
className="w-full border border-gray-200 rounded px-2.5 py-1.5 text-xs text-gray-700 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400 resize-none bg-white"
className="w-full border border-gray-200 rounded-sm px-2.5 py-1.5 text-xs text-gray-700 placeholder:text-gray-400 focus:outline-hidden focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400 resize-none bg-white"
/>
<div className="flex items-center justify-between mt-2">
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => setNewPromptExpected("fail")}
className={`text-[10px] font-semibold px-2 py-0.5 rounded ${newPromptExpected === "fail" ? "bg-red-100 text-red-700" : "bg-gray-100 text-gray-500"}`}
className={`text-[10px] font-semibold px-2 py-0.5 rounded-sm ${newPromptExpected === "fail" ? "bg-red-100 text-red-700" : "bg-gray-100 text-gray-500"}`}
>
Should Fail
</button>
<button
type="button"
onClick={() => setNewPromptExpected("pass")}
className={`text-[10px] font-semibold px-2 py-0.5 rounded ${newPromptExpected === "pass" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-500"}`}
className={`text-[10px] font-semibold px-2 py-0.5 rounded-sm ${newPromptExpected === "pass" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-500"}`}
>
Should Pass
</button>
@ -942,7 +942,7 @@ export default function ComplianceUI({
type="button"
onClick={addCustomPrompt}
disabled={!newPromptText.trim()}
className={`text-[11px] font-medium px-2.5 py-1 rounded ${newPromptText.trim() ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-400"}`}
className={`text-[11px] font-medium px-2.5 py-1 rounded-sm ${newPromptText.trim() ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-400"}`}
>
Add
</button>
@ -964,17 +964,17 @@ export default function ComplianceUI({
</button>
</div>
<div className="mb-2 p-2 bg-white rounded border border-gray-200">
<div className="mb-2 p-2 bg-white rounded-sm border border-gray-200">
<p className="text-[10px] text-gray-500 leading-relaxed">
<span className="font-semibold text-gray-600">Required columns:</span>{" "}
<code className="bg-gray-100 px-1 rounded text-[10px]">prompt</code>,{" "}
<code className="bg-gray-100 px-1 rounded text-[10px]">expected_result</code>{" "}
<code className="bg-gray-100 px-1 rounded-sm text-[10px]">prompt</code>,{" "}
<code className="bg-gray-100 px-1 rounded-sm text-[10px]">expected_result</code>{" "}
<span className="text-gray-400">(fail or pass)</span>
</p>
<p className="text-[10px] text-gray-500 leading-relaxed mt-0.5">
<span className="font-semibold text-gray-600">Optional columns:</span>{" "}
<code className="bg-gray-100 px-1 rounded text-[10px]">framework</code>,{" "}
<code className="bg-gray-100 px-1 rounded text-[10px]">category</code>
<code className="bg-gray-100 px-1 rounded-sm text-[10px]">framework</code>,{" "}
<code className="bg-gray-100 px-1 rounded-sm text-[10px]">category</code>
</p>
</div>
@ -997,7 +997,7 @@ export default function ComplianceUI({
</button>
{csvError && (
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded text-[10px] text-red-600 whitespace-pre-line">
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded-sm text-[10px] text-red-600 whitespace-pre-line">
{csvError}
</div>
)}
@ -1033,11 +1033,11 @@ export default function ComplianceUI({
className="w-full flex items-center gap-2 px-3 py-2.5 text-left bg-gray-50 hover:bg-gray-100 transition-colors rounded-lg border border-gray-200"
>
{isExpanded ? (
<ChevronDown className="w-4 h-4 text-gray-400 flex-shrink-0" />
<ChevronDown className="w-4 h-4 text-gray-400 shrink-0" />
) : (
<ChevronRight className="w-4 h-4 text-gray-400 flex-shrink-0" />
<ChevronRight className="w-4 h-4 text-gray-400 shrink-0" />
)}
<CategoryIcon iconKey={fw.icon} className="w-4 h-4 text-gray-500 flex-shrink-0" />
<CategoryIcon iconKey={fw.icon} className="w-4 h-4 text-gray-500 shrink-0" />
<div className="flex-1 min-w-0">
<span className="text-xs font-semibold text-gray-900">{fw.name}</span>
<span className="text-[10px] text-gray-400 ml-1.5">{fwPromptCount} prompts</span>
@ -1053,7 +1053,7 @@ export default function ComplianceUI({
e.stopPropagation();
toggleFrameworkPrompts(fw);
}}
className="text-[10px] font-medium text-blue-600 hover:text-blue-700 px-1.5 py-0.5 rounded hover:bg-blue-50 flex-shrink-0"
className="text-[10px] font-medium text-blue-600 hover:text-blue-700 px-1.5 py-0.5 rounded-sm hover:bg-blue-50 shrink-0"
>
{fwSelectedCount === fwPromptCount ? "Clear" : "All"}
</button>
@ -1076,21 +1076,19 @@ export default function ComplianceUI({
className="w-full flex items-center gap-1.5 px-2.5 py-2 text-left hover:bg-gray-50 transition-colors"
>
{isCatExpanded ? (
<ChevronDown className="w-3.5 h-3.5 text-gray-400 flex-shrink-0" />
<ChevronDown className="w-3.5 h-3.5 text-gray-400 shrink-0" />
) : (
<ChevronRight className="w-3.5 h-3.5 text-gray-400 flex-shrink-0" />
<ChevronRight className="w-3.5 h-3.5 text-gray-400 shrink-0" />
)}
<span className="text-sm flex-shrink-0">
<span className="text-sm shrink-0">
<CategoryIcon iconKey={category.icon} className="w-3.5 h-3.5 text-gray-500" />
</span>
<span className="text-[11px] font-medium text-gray-700 flex-1 min-w-0 truncate">
{category.name}
</span>
<span className="text-[10px] text-gray-400 flex-shrink-0">
{category.prompts.length}
</span>
<span className="text-[10px] text-gray-400 shrink-0">{category.prompts.length}</span>
{selectedInCat > 0 && (
<span className="text-[9px] font-medium bg-blue-100 text-blue-700 px-1 py-0.5 rounded-full flex-shrink-0">
<span className="text-[9px] font-medium bg-blue-100 text-blue-700 px-1 py-0.5 rounded-full shrink-0">
{selectedInCat}
</span>
)}
@ -1105,7 +1103,7 @@ export default function ComplianceUI({
<button
type="button"
onClick={() => toggleCategoryPrompts(category)}
className="text-[10px] font-medium text-blue-600 hover:text-blue-700 flex-shrink-0 whitespace-nowrap"
className="text-[10px] font-medium text-blue-600 hover:text-blue-700 shrink-0 whitespace-nowrap"
>
{allCatSelected ? "Clear" : "Select all"}
</button>
@ -1119,12 +1117,12 @@ export default function ComplianceUI({
type="checkbox"
checked={selectedPromptIds.has(prompt.id)}
onChange={() => togglePrompt(prompt.id)}
className="mt-0.5 w-3.5 h-3.5 rounded border-gray-300 text-blue-600 focus:ring-blue-500/20 flex-shrink-0"
className="mt-0.5 w-3.5 h-3.5 rounded-sm border-gray-300 text-blue-600 focus:ring-blue-500/20 shrink-0"
/>
<div className="flex-1 min-w-0">
<p className="text-[11px] text-gray-700 leading-relaxed">{prompt.prompt}</p>
<span
className={`inline-block mt-0.5 text-[9px] font-semibold px-1 py-0.5 rounded ${prompt.expectedResult === "fail" ? "bg-red-50 text-red-600" : "bg-green-50 text-green-600"}`}
className={`inline-block mt-0.5 text-[9px] font-semibold px-1 py-0.5 rounded-sm ${prompt.expectedResult === "fail" ? "bg-red-50 text-red-600" : "bg-green-50 text-green-600"}`}
>
{prompt.expectedResult === "fail" ? "Should Fail" : "Should Pass"}
</span>
@ -1137,7 +1135,7 @@ export default function ComplianceUI({
e.stopPropagation();
deleteCustomPrompt(prompt.id);
}}
className="opacity-0 group-hover:opacity-100 p-0.5 text-gray-400 hover:text-red-500 transition-all flex-shrink-0"
className="opacity-0 group-hover:opacity-100 p-0.5 text-gray-400 hover:text-red-500 transition-all shrink-0"
aria-label="Delete"
>
<Trash2 className="w-3 h-3" />
@ -1161,7 +1159,7 @@ export default function ComplianceUI({
{/* Right panel */}
<div className="flex-1 flex flex-col bg-gray-50 overflow-hidden min-w-0">
<div className="flex-shrink-0 bg-white border-b border-gray-200 px-4">
<div className="shrink-0 bg-white border-b border-gray-200 px-4">
<div className="flex items-center gap-0">
<button
type="button"
@ -1193,12 +1191,15 @@ export default function ComplianceUI({
{rightTab === "quick-test" && (
<div className="flex-1 flex flex-col overflow-hidden min-h-0">
<div className="px-5 pt-4 pb-2 flex-shrink-0">
<div className="px-5 pt-4 pb-2 shrink-0">
{hasAnyConfig ? (
<div className="flex items-center gap-2 flex-wrap">
<span className="text-[11px] font-medium text-gray-500">Testing against:</span>
{selectedPolicies.map((id) => (
<span key={id} className="text-[11px] bg-blue-50 text-blue-700 px-2 py-0.5 rounded font-medium">
<span
key={id}
className="text-[11px] bg-blue-50 text-blue-700 px-2 py-0.5 rounded-sm font-medium"
>
{policyValueToLabel.get(id) ?? id}
</span>
))}
@ -1207,7 +1208,7 @@ export default function ComplianceUI({
return (
<span
key={id}
className="text-[11px] bg-indigo-50 text-indigo-700 px-2 py-0.5 rounded font-medium"
className="text-[11px] bg-indigo-50 text-indigo-700 px-2 py-0.5 rounded-sm font-medium"
>
{g?.name}
</span>
@ -1272,7 +1273,7 @@ export default function ComplianceUI({
<div ref={messagesEndRef} />
</div>
<div className="flex-shrink-0 px-5 pb-4">
<div className="shrink-0 px-5 pb-4">
<div className="border border-gray-200 rounded-lg bg-white overflow-hidden focus-within:ring-2 focus-within:ring-blue-500/20 focus-within:border-blue-400">
<textarea
ref={textareaRef}
@ -1281,14 +1282,14 @@ export default function ComplianceUI({
onKeyDown={handleQuickTestKeyDown}
placeholder="Enter text to test..."
rows={3}
className="w-full px-3 pt-3 pb-1 text-sm text-gray-700 placeholder:text-gray-400 focus:outline-none resize-none"
className="w-full px-3 pt-3 pb-1 text-sm text-gray-700 placeholder:text-gray-400 focus:outline-hidden resize-none"
/>
<div className="flex items-center justify-between px-3 pb-2">
<span className="text-[10px] text-gray-400">
Press <kbd className="px-1 py-0.5 bg-gray-100 rounded text-[10px] font-mono">Enter</kbd> to
Press <kbd className="px-1 py-0.5 bg-gray-100 rounded-sm text-[10px] font-mono">Enter</kbd> to
submit ·{" "}
<kbd className="px-1 py-0.5 bg-gray-100 rounded text-[10px] font-mono">Shift+Enter</kbd> for new
line
<kbd className="px-1 py-0.5 bg-gray-100 rounded-sm text-[10px] font-mono">Shift+Enter</kbd> for
new line
</span>
<span className="text-[10px] text-gray-400 tabular-nums">{quickTestInput.length}</span>
</div>
@ -1308,7 +1309,7 @@ export default function ComplianceUI({
{rightTab === "batch-results" && (
<div className="flex-1 flex flex-col overflow-hidden bg-white min-h-0">
<div className="px-5 py-3 border-b border-gray-200 flex-shrink-0">
<div className="px-5 py-3 border-b border-gray-200 shrink-0">
<div className="flex items-center justify-between mb-2">
<h2 className="text-sm font-semibold text-gray-900">Results</h2>
{testResults.length > 0 && (
@ -1317,7 +1318,7 @@ export default function ComplianceUI({
type="button"
onClick={exportBatchResults}
disabled={filteredResults.length === 0}
className="flex items-center gap-1 text-[11px] font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-100 px-2 py-1 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent"
className="flex items-center gap-1 text-[11px] font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-100 px-2 py-1 rounded-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent"
>
<Download className="w-3 h-3" /> Export CSV
</button>
@ -1437,7 +1438,7 @@ export default function ComplianceUI({
>
<div className="p-2.5">
<div className="flex items-start gap-2">
<div className="flex-shrink-0 mt-0.5">
<div className="shrink-0 mt-0.5">
{result.status !== "complete" ? (
<Loader2 className="w-3.5 h-3.5 text-gray-400 animate-spin" />
) : result.isMatch ? (
@ -1454,13 +1455,13 @@ export default function ComplianceUI({
{result.category}
</span>
<span
className={`text-[9px] font-semibold px-1 py-0.5 rounded ${result.expectedResult === "fail" ? "bg-red-50 text-red-600" : "bg-green-50 text-green-600"}`}
className={`text-[9px] font-semibold px-1 py-0.5 rounded-sm ${result.expectedResult === "fail" ? "bg-red-50 text-red-600" : "bg-green-50 text-green-600"}`}
>
{result.expectedResult === "fail" ? "Expect Block" : "Expect Allow"}
</span>
{result.status === "complete" && (
<span
className={`text-[9px] font-bold px-1 py-0.5 rounded ${result.isMatch ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"}`}
className={`text-[9px] font-bold px-1 py-0.5 rounded-sm ${result.isMatch ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"}`}
>
{result.isMatch ? "✓ Match" : "✗ Gap"}
</span>
@ -1478,7 +1479,7 @@ export default function ComplianceUI({
return next;
});
}}
className="flex-shrink-0 p-0.5 text-gray-400 hover:text-gray-600"
className="shrink-0 p-0.5 text-gray-400 hover:text-gray-600"
aria-label={isExpanded ? "Collapse" : "Expand"}
>
{isExpanded ? (
@ -1494,7 +1495,7 @@ export default function ComplianceUI({
{result.triggeredBy && (
<div>
<span className="text-gray-400">Triggered by:</span>{" "}
<span className="font-medium text-gray-700 bg-gray-100 px-1.5 py-0.5 rounded">
<span className="font-medium text-gray-700 bg-gray-100 px-1.5 py-0.5 rounded-sm">
{result.triggeredBy}
</span>
</div>
@ -1512,7 +1513,7 @@ export default function ComplianceUI({
{result.returnedText != null && result.returnedText !== "" && (
<div className="mt-1.5">
<span className="text-gray-400 block mb-0.5">LLM response:</span>
<div className="text-gray-700 bg-gray-50 rounded px-2 py-1.5 border border-gray-100 max-h-32 overflow-y-auto whitespace-pre-wrap break-words">
<div className="text-gray-700 bg-gray-50 rounded-sm px-2 py-1.5 border border-gray-100 max-h-32 overflow-y-auto whitespace-pre-wrap wrap-break-word">
{result.returnedText}
</div>
</div>

View file

@ -40,7 +40,7 @@ const ModelConfigCard: React.FC<ModelConfigCardProps> = ({
</button>
{showConfig && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-30">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30">
<div className="bg-white rounded-lg shadow-xl p-6 w-96">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">Model Parameters</h3>

View file

@ -53,7 +53,7 @@ const PromptEditorHeader: React.FC<PromptEditorHeaderProps> = ({
style={{ width: "200px" }}
/>
{version && (
<span className="px-2 py-0.5 text-xs bg-blue-100 text-blue-700 rounded font-medium">{version}</span>
<span className="px-2 py-0.5 text-xs bg-blue-100 text-blue-700 rounded-sm font-medium">{version}</span>
)}
<Select
value={environment}
@ -66,7 +66,7 @@ const PromptEditorHeader: React.FC<PromptEditorHeaderProps> = ({
{ label: "Production", value: "production" },
]}
/>
<span className="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">Draft</span>
<span className="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded-sm">Draft</span>
<span className="text-xs text-gray-400">Unsaved changes</span>
</div>
<div className="flex items-center space-x-2">

View file

@ -53,7 +53,8 @@ const PromptMessagesCard: React.FC<PromptMessagesCardProps> = ({
<div className="mb-2">
<Text className="text-sm font-medium">Prompt messages</Text>
<Text className="text-gray-500 text-xs mt-1">
Use <code className="bg-gray-100 px-1 rounded text-xs">{"{{variable}}"}</code> syntax for template variables
Use <code className="bg-gray-100 px-1 rounded-sm text-xs">{"{{variable}}"}</code> syntax for template
variables
</Text>
</div>
<div className="space-y-2">

View file

@ -27,7 +27,7 @@ const ToolsCard: React.FC<ToolsCardProps> = ({ tools, onAddTool, onEditTool, onR
{tools.map((tool, index) => (
<div
key={index}
className="flex items-center justify-between p-2 bg-gray-50 border border-gray-200 rounded"
className="flex items-center justify-between p-2 bg-gray-50 border border-gray-200 rounded-sm"
>
<div className="flex-1 min-w-0">
<div className="font-medium text-xs truncate">{tool.name}</div>

View file

@ -14,7 +14,7 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
return (
<div className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}>
<div
className="max-w-[85%] rounded-lg shadow-sm p-3.5 px-4"
className="max-w-[85%] rounded-lg shadow-xs p-3.5 px-4"
style={{
backgroundColor: message.role === "user" ? "#f0f8ff" : "#ffffff",
border: message.role === "user" ? "1px solid #e6f0fa" : "1px solid #f0f0f0",
@ -35,12 +35,14 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
</div>
<strong className="text-sm capitalize">{message.role}</strong>
{message.role === "assistant" && message.model && (
<span className="text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-normal">{message.model}</span>
<span className="text-xs px-2 py-0.5 rounded-sm bg-gray-100 text-gray-600 font-normal">
{message.model}
</span>
)}
</div>
<div
className="whitespace-pre-wrap break-words max-w-full message-content"
className="whitespace-pre-wrap wrap-break-word max-w-full message-content"
style={{
wordWrap: "break-word",
overflowWrap: "break-word",
@ -76,7 +78,7 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
</SyntaxHighlighter>
) : (
<code
className={`${className} px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono`}
className={`${className} px-1.5 py-0.5 rounded-sm bg-gray-100 text-sm font-mono`}
style={{ wordBreak: "break-word" }}
{...props}
>

View file

@ -49,7 +49,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
<TremorButton
onClick={onSend}
disabled={isDisabled}
className="flex-shrink-0 ml-2 !w-8 !h-8 !min-w-8 !p-0 !rounded-full !bg-blue-600 hover:!bg-blue-700 disabled:!bg-gray-300 !border-none !text-white disabled:!text-gray-500 !flex !items-center !justify-center"
className="shrink-0 ml-2 w-8! h-8! min-w-8! p-0! rounded-full! bg-blue-600! hover:bg-blue-700! disabled:bg-gray-300! border-none! text-white! disabled:text-gray-500! flex! items-center! justify-center!"
>
<ArrowUpOutlined style={{ fontSize: "14px" }} />
</TremorButton>

View file

@ -292,7 +292,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
/>
<div className="flex-1 flex overflow-hidden">
<div className="w-1/2 overflow-y-auto bg-white border-r border-gray-200 flex-shrink-0">
<div className="w-1/2 overflow-y-auto bg-white border-r border-gray-200 shrink-0">
<div className="border-b border-gray-200 bg-white px-6 py-4 flex items-center gap-3">
<ModelConfigCard
model={prompt.model}
@ -317,7 +317,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
<div className="ml-auto inline-flex items-center bg-gray-200 rounded-full p-0.5">
<button
className={`px-3 py-1 text-xs font-medium rounded-full transition-colors ${
viewMode === "pretty" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600"
viewMode === "pretty" ? "bg-white text-gray-900 shadow-xs" : "text-gray-600"
}`}
onClick={() => setViewMode("pretty")}
>
@ -325,7 +325,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
</button>
<button
className={`px-3 py-1 text-xs font-medium rounded-full transition-colors ${
viewMode === "dotprompt" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600"
viewMode === "dotprompt" ? "bg-white text-gray-900 shadow-xs" : "text-gray-600"
}`}
onClick={() => setViewMode("dotprompt")}
>
@ -361,7 +361,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
)}
</div>
<div className="w-1/2 flex-shrink-0">
<div className="w-1/2 shrink-0">
<ConversationPanel prompt={prompt} accessToken={accessToken} />
</div>
</div>

View file

@ -444,7 +444,9 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
<div className="space-y-4">
<div>
<Text className="font-medium">Template ID</Text>
<div className="font-mono text-sm bg-gray-50 p-2 rounded">{promptTemplate.litellm_prompt_id}</div>
<div className="font-mono text-sm bg-gray-50 p-2 rounded-sm">
{promptTemplate.litellm_prompt_id}
</div>
</div>
<div>

View file

@ -123,7 +123,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
<Tooltip title={model}>
<div className="flex items-center space-x-2">
{/* Provider Icon */}
<div className="flex-shrink-0">
<div className="shrink-0">
{provider && logo ? (
<img
src={logo}
@ -195,7 +195,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
development: "text-green-600 bg-green-50",
};
return (
<span className={`text-xs px-2 py-0.5 rounded ${colorMap[env] || "text-gray-600 bg-gray-50"}`}>{env}</span>
<span className={`text-xs px-2 py-0.5 rounded-sm ${colorMap[env] || "text-gray-600 bg-gray-50"}`}>{env}</span>
);
},
},

View file

@ -69,11 +69,11 @@ const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onC
]}
>
<div className="space-y-3">
{error && <div className="p-3 bg-red-50 border border-red-200 rounded text-red-600 text-sm">{error}</div>}
{error && <div className="p-3 bg-red-50 border border-red-200 rounded-sm text-red-600 text-sm">{error}</div>}
<textarea
value={json}
onChange={(e) => setJson(e.target.value)}
className="w-full min-h-[400px] px-4 py-3 border border-gray-300 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
className="w-full min-h-[400px] px-4 py-3 border border-gray-300 rounded-lg text-sm font-mono focus:outline-hidden focus:ring-2 focus:ring-blue-500 resize-none"
placeholder="Paste your tool JSON here..."
/>
</div>

View file

@ -104,7 +104,7 @@ const VariableTextArea: React.FC<VariableTextAreaProps> = ({ value, onChange, pl
<div className="flex gap-2 mt-2">
<button
onClick={handleVariableEdit}
className="text-xs px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
className="text-xs px-2 py-1 bg-blue-500 text-white rounded-sm hover:bg-blue-600"
>
Save
</button>
@ -113,7 +113,7 @@ const VariableTextArea: React.FC<VariableTextAreaProps> = ({ value, onChange, pl
setEditingVariable(null);
setNewVariableName("");
}}
className="text-xs px-2 py-1 bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
className="text-xs px-2 py-1 bg-gray-200 text-gray-700 rounded-sm hover:bg-gray-300"
>
Cancel
</button>

View file

@ -241,7 +241,7 @@ export const SearchToolTester: React.FC<SearchToolTesterProps> = ({ searchToolNa
<Button
type="text"
size="small"
className="flex-shrink-0"
className="shrink-0"
icon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path

View file

@ -116,7 +116,7 @@ const UIThemeSettings: React.FC<UIThemeSettingsProps> = ({ userID, userRole, acc
<Title className="text-2xl font-bold mb-2">UI Theme Customization</Title>
<Text className="text-gray-600">Customize your LiteLLM admin dashboard with a custom logo and favicon.</Text>
</div>
<Card className="shadow-sm p-6">
<Card className="shadow-xs p-6">
<div className="space-y-6">
<div>
<Text className="text-sm font-medium text-gray-700 mb-2 block">Custom Logo URL</Text>

View file

@ -375,7 +375,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
return (
<div className="flex flex-wrap gap-2 mt-1">
{value.map((model, index) => (
<span key={index} className="px-2 py-1 bg-blue-100 rounded text-xs">
<span key={index} className="px-2 py-1 bg-blue-100 rounded-sm text-xs">
{getModelDisplayName(model)}
</span>
))}
@ -390,7 +390,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
return (
<div className="flex flex-wrap gap-2 mt-1">
{value.map((item, index) => (
<span key={index} className="px-2 py-1 bg-blue-100 rounded text-xs">
<span key={index} className="px-2 py-1 bg-blue-100 rounded-sm text-xs">
{typeof item === "object" ? JSON.stringify(item) : String(item)}
</span>
))}
@ -398,7 +398,9 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
);
}
return <pre className="bg-gray-100 p-2 rounded text-xs overflow-auto mt-1">{JSON.stringify(value, null, 2)}</pre>;
return (
<pre className="bg-gray-100 p-2 rounded-sm text-xs overflow-auto mt-1">{JSON.stringify(value, null, 2)}</pre>
);
}
return <span>{String(value)}</span>;
@ -442,7 +444,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
{isEditing ? (
<div className="mt-2">{renderEditableField(key, property, value)}</div>
) : (
<div className="mt-1 p-2 bg-gray-50 rounded">{renderValue(key, value)}</div>
<div className="mt-1 p-2 bg-gray-50 rounded-sm">{renderValue(key, value)}</div>
)}
</div>
);

View file

@ -212,7 +212,7 @@ export function UserDataTable({
}
return (
<div className="bg-white rounded-lg shadow">
<div className="bg-white rounded-lg shadow-sm">
{/* Filter Section */}
<div className="border-b px-6 py-4">
<div className="flex flex-col space-y-4">

View file

@ -583,7 +583,7 @@ export default function UserInfoView({
<div className="flex flex-wrap gap-2 mt-1">
{userData.models?.length && userData.models?.length > 0 ? (
userData.models?.map((model, index) => (
<span key={index} className="px-2 py-1 bg-blue-100 rounded text-xs">
<span key={index} className="px-2 py-1 bg-blue-100 rounded-sm text-xs">
{model}
</span>
))
@ -609,7 +609,7 @@ export default function UserInfoView({
<div>
<Text className="font-medium">Metadata</Text>
<pre className="bg-gray-100 p-2 rounded text-xs overflow-auto mt-1">
<pre className="bg-gray-100 p-2 rounded-sm text-xs overflow-auto mt-1">
{JSON.stringify(userData.metadata || {}, null, 2)}
</pre>
</div>

View file

@ -1,36 +1,215 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer theme, base, antd, components, utilities;
@import "tailwindcss";
@import "tw-animate-css";
@import "./tremor-v3-compat.css" layer(utilities);
@source '../../node_modules/@tremor/react';
@plugin '@headlessui/tailwindcss';
@plugin '@tailwindcss/forms';
@custom-variant dark (&:where(.dark, .dark *));
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 255, 255, 255;
--background-end-rgb: 255, 255, 255;
--radius: 0.5rem;
--background: oklch(1 0 0);
--foreground: oklch(0.13 0.028 261.692);
--card: oklch(1 0 0);
--card-foreground: oklch(0.13 0.028 261.692);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.13 0.028 261.692);
--primary: oklch(0.21 0.034 264.665);
--primary-foreground: oklch(0.985 0.002 247.839);
--secondary: oklch(0.967 0.003 264.542);
--secondary-foreground: oklch(0.21 0.034 264.665);
--muted: oklch(0.967 0.003 264.542);
--muted-foreground: oklch(0.551 0.027 264.364);
--accent: oklch(0.967 0.003 264.542);
--accent-foreground: oklch(0.21 0.034 264.665);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.928 0.006 264.531);
--input: oklch(0.928 0.006 264.531);
--ring: oklch(0.707 0.022 261.325);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0.002 247.839);
--sidebar-foreground: oklch(0.13 0.028 261.692);
--sidebar-primary: oklch(0.21 0.034 264.665);
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
--sidebar-accent: oklch(0.967 0.003 264.542);
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
--sidebar-border: oklch(0.928 0.006 264.531);
--sidebar-ring: oklch(0.707 0.022 261.325);
--neutral-border: #dcddeb;
}
/* @media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
} */
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
.dark {
--background: oklch(0.13 0.028 261.692);
--foreground: oklch(0.985 0.002 247.839);
--card: oklch(0.21 0.034 264.665);
--card-foreground: oklch(0.985 0.002 247.839);
--popover: oklch(0.21 0.034 264.665);
--popover-foreground: oklch(0.985 0.002 247.839);
--primary: oklch(0.928 0.006 264.531);
--primary-foreground: oklch(0.21 0.034 264.665);
--secondary: oklch(0.278 0.033 256.848);
--secondary-foreground: oklch(0.985 0.002 247.839);
--muted: oklch(0.278 0.033 256.848);
--muted-foreground: oklch(0.707 0.022 261.325);
--accent: oklch(0.278 0.033 256.848);
--accent-foreground: oklch(0.985 0.002 247.839);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.034 264.665);
--sidebar-foreground: oklch(0.985 0.002 247.839);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
--sidebar-accent: oklch(0.278 0.033 256.848);
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);
}
@layer utilities {
.text-balance {
text-wrap: balance;
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@theme {
--color-tremor-brand-muted: #8688ef;
--color-tremor-brand-subtle: #8e91eb;
--color-tremor-brand: #6366f1;
--color-tremor-brand-emphasis: #4338ca;
--color-tremor-brand-inverted: #ffffff;
--color-tremor-background-muted: #f9fafb;
--color-tremor-background-subtle: #f3f4f6;
--color-tremor-background: #ffffff;
--color-tremor-background-emphasis: #374151;
--color-tremor-border: #e5e7eb;
--color-tremor-ring: #e5e7eb;
--color-tremor-content-subtle: #9ca3af;
--color-tremor-content: #6b7280;
--color-tremor-content-emphasis: #374151;
--color-tremor-content-strong: #111827;
--color-tremor-content-inverted: #ffffff;
--color-dark-tremor-brand-faint: #0b1229;
--color-dark-tremor-brand-muted: #1e1b4b;
--color-dark-tremor-brand-subtle: #3730a3;
--color-dark-tremor-brand: #6366f1;
--color-dark-tremor-brand-emphasis: #818cf8;
--color-dark-tremor-brand-inverted: #1e1b4b;
--color-dark-tremor-background-muted: #131a2b;
--color-dark-tremor-background-subtle: #1f2937;
--color-dark-tremor-background: #111827;
--color-dark-tremor-background-emphasis: #d1d5db;
--color-dark-tremor-border: #374151;
--color-dark-tremor-ring: #1f2937;
--color-dark-tremor-content-subtle: #4b5563;
--color-dark-tremor-content: #6b7280;
--color-dark-tremor-content-emphasis: #e5e7eb;
--color-dark-tremor-content-strong: #f9fafb;
--color-dark-tremor-content-inverted: #030712;
--shadow-tremor-input: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-tremor-card: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-tremor-dropdown: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-dark-tremor-input: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-dark-tremor-card: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-dark-tremor-dropdown: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--radius-tremor-small: 0.375rem;
--radius-tremor-default: 0.5rem;
--radius-tremor-full: 9999px;
--text-tremor-label: 0.75rem;
--text-tremor-label--line-height: 0.3rem;
--text-tremor-default: 0.775rem;
--text-tremor-default--line-height: 1.15rem;
--text-tremor-title: 1.025rem;
--text-tremor-title--line-height: 1.65rem;
--text-tremor-metric: 1.675rem;
--text-tremor-metric--line-height: 2.15rem;
}
@source inline("{,hover:,ui-selected:}{bg,text,border}-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}");
@source inline("{ring,stroke,fill}-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}");
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-border);
}
* {
outline-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
}
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
input::placeholder,
textarea::placeholder {
color: var(--color-gray-400);
}
body {
background-color: var(--color-background);
color: var(--color-foreground);
}
}
.table-wrapper {
overflow-x: scroll;
/* max-width: 80%; */
margin: 0px 24px;
}

View file

@ -162,7 +162,7 @@ function LoginPageContent() {
environment variable:
</Paragraph>
<Paragraph className="text-sm">
<code className="bg-gray-100 px-1 py-0.5 rounded text-xs">DISABLE_ADMIN_UI=False</code>
<code className="bg-gray-100 px-1 py-0.5 rounded-sm text-xs">DISABLE_ADMIN_UI=False</code>
</Paragraph>
</>
}
@ -194,9 +194,9 @@ function LoginPageContent() {
description={
<>
<Paragraph className="text-sm">
By default, Username is <code className="bg-gray-100 px-1 py-0.5 rounded text-xs">admin</code> and
Password is your set LiteLLM Proxy
<code className="bg-gray-100 px-1 py-0.5 rounded text-xs">MASTER_KEY</code>.
By default, Username is <code className="bg-gray-100 px-1 py-0.5 rounded-sm text-xs">admin</code>{" "}
and Password is your set LiteLLM Proxy
<code className="bg-gray-100 px-1 py-0.5 rounded-sm text-xs">MASTER_KEY</code>.
</Paragraph>
<Paragraph className="text-sm">
Need to set UI credentials or SSO?{" "}

View file

@ -0,0 +1,615 @@
.bg-slate-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-slate-500) 10%, transparent);
}
.bg-slate-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-slate-500) 20%, transparent);
}
.bg-slate-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-slate-500) 40%, transparent);
}
.hover\:bg-slate-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-slate-500) 20%, transparent);
}
.group:hover .bg-slate-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-slate-500) 30%, transparent);
}
.ring-slate-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-slate-500) 20%, transparent);
}
.ring-slate-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-slate-300) 40%, transparent);
}
.bg-gray-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-gray-500) 10%, transparent);
}
.bg-gray-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-gray-500) 20%, transparent);
}
.bg-gray-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-gray-500) 40%, transparent);
}
.hover\:bg-gray-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-gray-500) 20%, transparent);
}
.group:hover .bg-gray-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-gray-500) 30%, transparent);
}
.ring-gray-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-gray-500) 20%, transparent);
}
.ring-gray-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-gray-300) 40%, transparent);
}
.bg-zinc-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-zinc-500) 10%, transparent);
}
.bg-zinc-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-zinc-500) 20%, transparent);
}
.bg-zinc-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-zinc-500) 40%, transparent);
}
.hover\:bg-zinc-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-zinc-500) 20%, transparent);
}
.group:hover .bg-zinc-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
}
.ring-zinc-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-zinc-500) 20%, transparent);
}
.ring-zinc-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-zinc-300) 40%, transparent);
}
.bg-neutral-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-neutral-500) 10%, transparent);
}
.bg-neutral-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-neutral-500) 20%, transparent);
}
.bg-neutral-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-neutral-500) 40%, transparent);
}
.hover\:bg-neutral-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-neutral-500) 20%, transparent);
}
.group:hover .bg-neutral-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-neutral-500) 30%, transparent);
}
.ring-neutral-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-neutral-500) 20%, transparent);
}
.ring-neutral-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-neutral-300) 40%, transparent);
}
.bg-stone-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-stone-500) 10%, transparent);
}
.bg-stone-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-stone-500) 20%, transparent);
}
.bg-stone-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-stone-500) 40%, transparent);
}
.hover\:bg-stone-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-stone-500) 20%, transparent);
}
.group:hover .bg-stone-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-stone-500) 30%, transparent);
}
.ring-stone-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-stone-500) 20%, transparent);
}
.ring-stone-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-stone-300) 40%, transparent);
}
.bg-red-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-red-500) 10%, transparent);
}
.bg-red-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
}
.bg-red-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-red-500) 40%, transparent);
}
.hover\:bg-red-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
}
.group:hover .bg-red-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-red-500) 30%, transparent);
}
.ring-red-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
}
.ring-red-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-red-300) 40%, transparent);
}
.bg-orange-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-orange-500) 10%, transparent);
}
.bg-orange-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-orange-500) 20%, transparent);
}
.bg-orange-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-orange-500) 40%, transparent);
}
.hover\:bg-orange-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-orange-500) 20%, transparent);
}
.group:hover .bg-orange-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-orange-500) 30%, transparent);
}
.ring-orange-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-orange-500) 20%, transparent);
}
.ring-orange-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-orange-300) 40%, transparent);
}
.bg-amber-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-amber-500) 10%, transparent);
}
.bg-amber-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-amber-500) 20%, transparent);
}
.bg-amber-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-amber-500) 40%, transparent);
}
.hover\:bg-amber-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-amber-500) 20%, transparent);
}
.group:hover .bg-amber-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-amber-500) 30%, transparent);
}
.ring-amber-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-amber-500) 20%, transparent);
}
.ring-amber-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-amber-300) 40%, transparent);
}
.bg-yellow-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-yellow-500) 10%, transparent);
}
.bg-yellow-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-yellow-500) 20%, transparent);
}
.bg-yellow-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-yellow-500) 40%, transparent);
}
.hover\:bg-yellow-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-yellow-500) 20%, transparent);
}
.group:hover .bg-yellow-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-yellow-500) 30%, transparent);
}
.ring-yellow-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-yellow-500) 20%, transparent);
}
.ring-yellow-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-yellow-300) 40%, transparent);
}
.bg-lime-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-lime-500) 10%, transparent);
}
.bg-lime-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-lime-500) 20%, transparent);
}
.bg-lime-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-lime-500) 40%, transparent);
}
.hover\:bg-lime-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-lime-500) 20%, transparent);
}
.group:hover .bg-lime-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-lime-500) 30%, transparent);
}
.ring-lime-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-lime-500) 20%, transparent);
}
.ring-lime-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-lime-300) 40%, transparent);
}
.bg-green-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-green-500) 10%, transparent);
}
.bg-green-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-green-500) 20%, transparent);
}
.bg-green-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-green-500) 40%, transparent);
}
.hover\:bg-green-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-green-500) 20%, transparent);
}
.group:hover .bg-green-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-green-500) 30%, transparent);
}
.ring-green-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-green-500) 20%, transparent);
}
.ring-green-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-green-300) 40%, transparent);
}
.bg-emerald-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-emerald-500) 10%, transparent);
}
.bg-emerald-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-emerald-500) 20%, transparent);
}
.bg-emerald-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-emerald-500) 40%, transparent);
}
.hover\:bg-emerald-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-emerald-500) 20%, transparent);
}
.group:hover .bg-emerald-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-emerald-500) 30%, transparent);
}
.ring-emerald-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-emerald-500) 20%, transparent);
}
.ring-emerald-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-emerald-300) 40%, transparent);
}
.bg-teal-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-teal-500) 10%, transparent);
}
.bg-teal-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-teal-500) 20%, transparent);
}
.bg-teal-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-teal-500) 40%, transparent);
}
.hover\:bg-teal-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-teal-500) 20%, transparent);
}
.group:hover .bg-teal-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-teal-500) 30%, transparent);
}
.ring-teal-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-teal-500) 20%, transparent);
}
.ring-teal-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-teal-300) 40%, transparent);
}
.bg-cyan-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-cyan-500) 10%, transparent);
}
.bg-cyan-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-cyan-500) 20%, transparent);
}
.bg-cyan-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-cyan-500) 40%, transparent);
}
.hover\:bg-cyan-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-cyan-500) 20%, transparent);
}
.group:hover .bg-cyan-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-cyan-500) 30%, transparent);
}
.ring-cyan-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-cyan-500) 20%, transparent);
}
.ring-cyan-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-cyan-300) 40%, transparent);
}
.bg-sky-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-sky-500) 10%, transparent);
}
.bg-sky-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-sky-500) 20%, transparent);
}
.bg-sky-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-sky-500) 40%, transparent);
}
.hover\:bg-sky-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-sky-500) 20%, transparent);
}
.group:hover .bg-sky-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-sky-500) 30%, transparent);
}
.ring-sky-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-sky-500) 20%, transparent);
}
.ring-sky-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-sky-300) 40%, transparent);
}
.bg-blue-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-blue-500) 10%, transparent);
}
.bg-blue-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-blue-500) 20%, transparent);
}
.bg-blue-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-blue-500) 40%, transparent);
}
.hover\:bg-blue-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-blue-500) 20%, transparent);
}
.group:hover .bg-blue-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-blue-500) 30%, transparent);
}
.ring-blue-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-blue-500) 20%, transparent);
}
.ring-blue-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-blue-300) 40%, transparent);
}
.bg-indigo-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-indigo-500) 10%, transparent);
}
.bg-indigo-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-indigo-500) 20%, transparent);
}
.bg-indigo-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-indigo-500) 40%, transparent);
}
.hover\:bg-indigo-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-indigo-500) 20%, transparent);
}
.group:hover .bg-indigo-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-indigo-500) 30%, transparent);
}
.ring-indigo-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-indigo-500) 20%, transparent);
}
.ring-indigo-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-indigo-300) 40%, transparent);
}
.bg-violet-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-violet-500) 10%, transparent);
}
.bg-violet-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-violet-500) 20%, transparent);
}
.bg-violet-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-violet-500) 40%, transparent);
}
.hover\:bg-violet-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-violet-500) 20%, transparent);
}
.group:hover .bg-violet-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-violet-500) 30%, transparent);
}
.ring-violet-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-violet-500) 20%, transparent);
}
.ring-violet-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-violet-300) 40%, transparent);
}
.bg-purple-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-purple-500) 10%, transparent);
}
.bg-purple-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-purple-500) 20%, transparent);
}
.bg-purple-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-purple-500) 40%, transparent);
}
.hover\:bg-purple-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-purple-500) 20%, transparent);
}
.group:hover .bg-purple-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-purple-500) 30%, transparent);
}
.ring-purple-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-purple-500) 20%, transparent);
}
.ring-purple-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-purple-300) 40%, transparent);
}
.bg-fuchsia-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-fuchsia-500) 10%, transparent);
}
.bg-fuchsia-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-fuchsia-500) 20%, transparent);
}
.bg-fuchsia-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-fuchsia-500) 40%, transparent);
}
.hover\:bg-fuchsia-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-fuchsia-500) 20%, transparent);
}
.group:hover .bg-fuchsia-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-fuchsia-500) 30%, transparent);
}
.ring-fuchsia-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-fuchsia-500) 20%, transparent);
}
.ring-fuchsia-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-fuchsia-300) 40%, transparent);
}
.bg-pink-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-pink-500) 10%, transparent);
}
.bg-pink-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-pink-500) 20%, transparent);
}
.bg-pink-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-pink-500) 40%, transparent);
}
.hover\:bg-pink-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-pink-500) 20%, transparent);
}
.group:hover .bg-pink-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-pink-500) 30%, transparent);
}
.ring-pink-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-pink-500) 20%, transparent);
}
.ring-pink-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-pink-300) 40%, transparent);
}
.bg-rose-500.bg-opacity-10 {
background-color: color-mix(in oklab, var(--color-rose-500) 10%, transparent);
}
.bg-rose-500.bg-opacity-20 {
background-color: color-mix(in oklab, var(--color-rose-500) 20%, transparent);
}
.bg-rose-500.bg-opacity-40 {
background-color: color-mix(in oklab, var(--color-rose-500) 40%, transparent);
}
.hover\:bg-rose-500.hover\:bg-opacity-20:hover {
background-color: color-mix(in oklab, var(--color-rose-500) 20%, transparent);
}
.group:hover .bg-rose-500.group-hover\:bg-opacity-30 {
background-color: color-mix(in oklab, var(--color-rose-500) 30%, transparent);
}
.ring-rose-500.ring-opacity-20 {
--tw-ring-color: color-mix(in oklab, var(--color-rose-500) 20%, transparent);
}
.ring-rose-300.ring-opacity-40 {
--tw-ring-color: color-mix(in oklab, var(--color-rose-300) 40%, transparent);
}

View file

@ -410,11 +410,11 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({ accessToken, publicPage,
</div>
<div className="flex items-center space-x-4">
<Text>Model Hub URL:</Text>
<div className="flex items-center bg-gray-200 px-2 py-1 rounded">
<div className="flex items-center bg-gray-200 px-2 py-1 rounded-sm">
<Text className="mr-2">{`${getProxyBaseUrl()}/ui/model_hub_table`}</Text>
<button
onClick={() => copyToClipboard(`${getProxyBaseUrl()}/ui/model_hub_table`)}
className="p-1 hover:bg-gray-300 rounded transition-colors"
className="p-1 hover:bg-gray-300 rounded-sm transition-colors"
title="Copy URL"
>
<Copy size={16} className="text-gray-600" />
@ -563,7 +563,7 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({ accessToken, publicPage,
<div className="pt-5 pb-5">
<div className="flex justify-between mb-4">
<Text className="text-base mr-2">Shareable Link:</Text>
<Text className="max-w-sm ml-2 bg-gray-200 pr-2 pl-2 pt-1 pb-1 text-center rounded">
<Text className="max-w-sm ml-2 bg-gray-200 pr-2 pl-2 pt-1 pb-1 text-center rounded-sm">
{`${getProxyBaseUrl()}/ui/model_hub_table`}
</Text>
</div>
@ -817,7 +817,7 @@ print(response.choices[0].message.content)`}
<Text className="text-lg font-semibold mb-4">Skills</Text>
<div className="space-y-4">
{selectedAgent.skills.map((skill) => (
<div key={skill.id} className="border border-gray-200 rounded p-4">
<div key={skill.id} className="border border-gray-200 rounded-sm p-4">
<div className="flex justify-between items-start mb-2">
<div>
<Text className="font-medium text-base">{skill.name}</Text>
@ -938,7 +938,9 @@ print(response.choices[0].message.content)`}
{selectedMcpServer.command && (
<div>
<Text className="font-medium">Command:</Text>
<Text className="text-sm bg-gray-100 p-2 rounded mt-1 font-mono">{selectedMcpServer.command}</Text>
<Text className="text-sm bg-gray-100 p-2 rounded-sm mt-1 font-mono">
{selectedMcpServer.command}
</Text>
</div>
)}
</div>
@ -1014,7 +1016,7 @@ print(response.choices[0].message.content)`}
)}
</div>
{selectedMcpServer.health_check_error && (
<div className="mt-2 p-2 bg-red-50 rounded">
<div className="mt-2 p-2 bg-red-50 rounded-sm">
<Text className="font-medium text-red-700">Health Check Error:</Text>
<Text className="text-sm text-red-600 mt-1">{selectedMcpServer.health_check_error}</Text>
</div>

View file

@ -298,7 +298,7 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
href={`${getProxyBaseUrl()}/ui/model_hub_table`}
target="_blank"
rel="noopener noreferrer"
className="text-xs bg-blue-50 text-blue-600 px-3 py-1.5 rounded hover:bg-blue-100 flex items-center"
className="text-xs bg-blue-50 text-blue-600 px-3 py-1.5 rounded-sm hover:bg-blue-100 flex items-center"
title="Open Public Model Hub"
>
Public Model Hub
@ -307,7 +307,7 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
{!isRearranging ? (
<button
onClick={handleStartRearranging}
className="text-xs bg-purple-50 text-purple-600 px-3 py-1.5 rounded hover:bg-purple-100 flex items-center"
className="text-xs bg-purple-50 text-purple-600 px-3 py-1.5 rounded-sm hover:bg-purple-100 flex items-center"
>
Rearrange Order
</button>
@ -315,13 +315,13 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
<div className="flex space-x-2">
<button
onClick={handleSaveRearranging}
className="text-xs bg-green-600 text-white px-3 py-1.5 rounded hover:bg-green-700"
className="text-xs bg-green-600 text-white px-3 py-1.5 rounded-sm hover:bg-green-700"
>
Save Order
</button>
<button
onClick={handleCancelRearranging}
className="text-xs bg-gray-50 text-gray-600 px-3 py-1.5 rounded hover:bg-gray-100"
className="text-xs bg-gray-50 text-gray-600 px-3 py-1.5 rounded-sm hover:bg-gray-100"
>
Cancel
</button>
@ -374,13 +374,13 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
<div className="flex space-x-2">
<button
onClick={handleUpdateLink}
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100"
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded-sm hover:bg-blue-100"
>
Save
</button>
<button
onClick={handleCancelEdit}
className="text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100"
className="text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded-sm hover:bg-gray-100"
>
Cancel
</button>

View file

@ -204,7 +204,7 @@ const MakeAgentPublicForm: React.FC<MakeAgentPublicFormProps> = ({
{Array.from(selectedAgents).map((agentId) => {
const agent = agentHubData.find((a) => (a.agent_id || a.name) === agentId);
return (
<div key={agentId} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<div key={agentId} className="flex items-center justify-between p-2 bg-gray-50 rounded-sm">
<div className="flex-1">
<div className="flex items-center space-x-2">
<Text className="font-medium">{agent?.name || agentId}</Text>

View file

@ -225,7 +225,7 @@ const MakeMCPPublicForm: React.FC<MakeMCPPublicFormProps> = ({
{Array.from(selectedServers).map((serverId) => {
const server = mcpHubData.find((s) => s.server_id === serverId);
return (
<div key={serverId} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<div key={serverId} className="flex items-center justify-between p-2 bg-gray-50 rounded-sm">
<div className="flex-1">
<div className="flex items-center space-x-2">
<Text className="font-medium">{server?.server_name || serverId}</Text>

View file

@ -233,7 +233,7 @@ const MakeModelPublicForm: React.FC<MakeModelPublicFormProps> = ({
{Array.from(selectedModels).map((modelGroup) => {
const model = modelHubData.find((m) => m.model_group === modelGroup);
return (
<div key={modelGroup} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<div key={modelGroup} className="flex items-center justify-between p-2 bg-gray-50 rounded-sm">
<div>
<Text className="font-medium">{modelGroup}</Text>
{model && (

View file

@ -121,7 +121,7 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
</Button>
</div>
}
className="shadow-sm"
className="shadow-xs"
>
<Descriptions
bordered

View file

@ -172,11 +172,11 @@ export function LogViewer({
onClick={() => handleLogClick(log)}
className="w-full text-left px-4 py-3 hover:bg-gray-50 transition-colors flex items-start gap-3"
>
<ActionIcon className={`w-4 h-4 mt-0.5 flex-shrink-0 ${config.color}`} />
<ActionIcon className={`w-4 h-4 mt-0.5 shrink-0 ${config.color}`} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<span
className={`inline-flex items-center px-2 py-0.5 text-xs font-medium rounded border ${config.bg} ${config.color} ${config.border}`}
className={`inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-sm border ${config.bg} ${config.color} ${config.border}`}
>
{config.label}
</span>
@ -186,7 +186,7 @@ export function LogViewer({
</div>
<p className="text-sm text-gray-800 truncate">{log.input_snippet ?? log.input ?? "—"}</p>
</div>
<DownOutlined className="w-4 h-4 text-gray-400 flex-shrink-0 mt-1" />
<DownOutlined className="w-4 h-4 text-gray-400 shrink-0 mt-1" />
</button>
);
})}

View file

@ -47,13 +47,13 @@ export const HelpLink: React.FC<HelpLinkProps> = ({
className = "",
}) => {
const baseClasses =
"inline-flex items-center gap-1.5 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 rounded";
"inline-flex items-center gap-1.5 transition-colors focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 rounded-sm";
const variantClasses = {
inline: "text-blue-600 hover:text-blue-800 text-sm font-medium hover:underline",
subtle: "text-gray-500 hover:text-gray-700 text-xs",
button:
"text-blue-600 hover:text-blue-700 border border-gray-200 hover:border-gray-300 px-3 py-1.5 rounded-md bg-white hover:bg-gray-50 text-sm font-medium shadow-sm",
"text-blue-600 hover:text-blue-700 border border-gray-200 hover:border-gray-300 px-3 py-1.5 rounded-md bg-white hover:bg-gray-50 text-sm font-medium shadow-xs",
};
return (
@ -65,7 +65,7 @@ export const HelpLink: React.FC<HelpLinkProps> = ({
title="Open documentation in a new tab"
>
<span>{children}</span>
<ExternalLink className="h-3.5 w-3.5 flex-shrink-0" aria-hidden="true" />
<ExternalLink className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
<span className="sr-only">(opens in a new tab)</span>
</a>
);
@ -88,7 +88,7 @@ export const HelpIcon: React.FC<HelpIconProps> = ({ content, learnMoreHref, lear
<div className="relative inline-block ml-1.5">
<button
type="button"
className="inline-flex items-center justify-center w-4 h-4 text-gray-400 hover:text-gray-600 transition-colors cursor-help focus:outline-none focus:ring-2 focus:ring-blue-500 rounded-full"
className="inline-flex items-center justify-center w-4 h-4 text-gray-400 hover:text-gray-600 transition-colors cursor-help focus:outline-hidden focus:ring-2 focus:ring-blue-500 rounded-full"
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
onFocus={() => setShowTooltip(true)}
@ -169,7 +169,7 @@ export const DocsMenu: React.FC<DocsMenuProps> = ({ items, children = "Docs", cl
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="inline-flex items-center gap-1 text-gray-500 hover:text-gray-700 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 rounded px-2 py-1"
className="inline-flex items-center gap-1 text-gray-500 hover:text-gray-700 text-xs transition-colors focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 rounded-sm px-2 py-1"
aria-expanded={isOpen}
aria-haspopup="true"
>
@ -189,7 +189,7 @@ export const DocsMenu: React.FC<DocsMenuProps> = ({ items, children = "Docs", cl
onClick={() => setIsOpen(false)}
>
<span>{item.label}</span>
<ExternalLink className="h-3.5 w-3.5 text-gray-400 flex-shrink-0 ml-2" aria-hidden="true" />
<ExternalLink className="h-3.5 w-3.5 text-gray-400 shrink-0 ml-2" aria-hidden="true" />
</a>
))}
</div>

View file

@ -78,7 +78,7 @@ export const BlogDropdown: React.FC = () => {
// Blog opens a post list; Docs is a single outbound link — navbar adds a layout-only chevron there for alignment.
return (
<Dropdown menu={{ items }} trigger={["hover"]} placement="bottomRight">
<Button type="text" className={`${NAV_PRODUCT_LINK_CLASS} !border-0 !bg-transparent`}>
<Button type="text" className={`${NAV_PRODUCT_LINK_CLASS} border-0! bg-transparent!`}>
Blog
<DownOutlined className="text-[10px] text-gray-500" aria-hidden />
</Button>

View file

@ -24,10 +24,10 @@ export const NotificationsBell: React.FC = () => {
const content = (
<div className="max-w-[280px]">
<Typography.Title level={5} className="!mt-0 !mb-2">
<Typography.Title level={5} className="mt-0! mb-2!">
LiteLLM Agent Platform
</Typography.Title>
<Typography.Paragraph type="secondary" className="!mb-3 text-sm leading-snug">
<Typography.Paragraph type="secondary" className="mb-3! text-sm leading-snug">
Open-source agent infra sandboxes, durable sessions, and workers on AWS Fargate.
</Typography.Paragraph>
<div className="flex flex-wrap items-center gap-2">
@ -35,7 +35,7 @@ export const NotificationsBell: React.FC = () => {
GitHub
</Button>
{hasUnread ? (
<Button type="link" size="small" className="!px-1" onClick={markDismissed}>
<Button type="link" size="small" className="px-1!" onClick={markDismissed}>
Mark as read
</Button>
) : null}
@ -47,7 +47,7 @@ export const NotificationsBell: React.FC = () => {
<Popover content={content} trigger="click" open={open} onOpenChange={setOpen} placement="bottomRight">
<Button
type="text"
className="!flex !h-9 !w-9 items-center justify-center !rounded-md text-gray-600 transition-colors hover:!bg-gray-100 hover:!text-gray-900"
className="flex! h-9! w-9! items-center justify-center rounded-md! text-gray-600 transition-colors hover:bg-gray-100! hover:text-gray-900!"
aria-label="Notifications"
>
<Badge dot={hasUnread} color="#1677ff" size="small" offset={[8, 2]}>

View file

@ -232,7 +232,7 @@ const UserDropdown: React.FC<UserDropdownProps> = ({ onLogout }) => {
>
<Button
type="text"
className="!flex max-w-[min(200px,34vw)] items-center gap-2 !rounded-md !py-0.5 !pl-1 !pr-2 transition-colors hover:!bg-gray-100"
className="flex! max-w-[min(200px,34vw)] items-center gap-2 rounded-md! py-0.5! pl-1! pr-2! transition-colors hover:bg-gray-100!"
aria-label={`Account menu — ${userRole ?? "Unknown role"} — signed in as ${userEmail || userId || "unknown"}`}
aria-haspopup="menu"
>

View file

@ -96,7 +96,7 @@ const SCIMConfig: React.FC<SCIMConfigProps> = ({ accessToken, userID, proxySetti
Use this URL in your identity provider SCIM integration settings.
</Text>
<div className="flex items-center">
<TextInput value={scimBaseUrl} disabled={true} className="flex-grow" />
<TextInput value={scimBaseUrl} disabled={true} className="grow" />
<CopyToClipboard
text={scimBaseUrl}
onCopy={() => NotificationsManager.success("URL copied to clipboard")}
@ -159,12 +159,7 @@ const SCIMConfig: React.FC<SCIMConfigProps> = ({ accessToken, userID, proxySetti
Make sure to copy this token now. You will not be able to see it again.
</Text>
<div className="flex items-center">
<TextInput
value={tokenData.key}
className="flex-grow mr-2 bg-white"
type="password"
disabled={true}
/>
<TextInput value={tokenData.key} className="grow mr-2 bg-white" type="password" disabled={true} />
<CopyToClipboard
text={tokenData.key}
onCopy={() => NotificationsManager.success("Token copied to clipboard")}

View file

@ -76,7 +76,7 @@ export function FallbackGroupConfig({ group, onChange, availableModels, maxFallb
options={availableModels.map((m) => ({ label: m, value: m }))}
/>
{!group.primaryModel && (
<div className="mt-2 flex items-center gap-2 text-amber-600 text-xs bg-amber-50 p-2 rounded">
<div className="mt-2 flex items-center gap-2 text-amber-600 text-xs bg-amber-50 p-2 rounded-sm">
<AlertCircle className="w-4 h-4" />
<span>Select a model to begin configuring fallbacks</span>
</div>
@ -85,7 +85,7 @@ export function FallbackGroupConfig({ group, onChange, availableModels, maxFallb
{/* Visual Connection */}
<div className="flex items-center justify-center -my-4 z-10">
<div className="bg-indigo-50 text-indigo-500 px-4 py-1 rounded-full text-xs font-bold border border-indigo-100 flex items-center gap-2 shadow-sm">
<div className="bg-indigo-50 text-indigo-500 px-4 py-1 rounded-full text-xs font-bold border border-indigo-100 flex items-center gap-2 shadow-xs">
<ArrowDown className="w-4 h-4" />
IF FAILS, TRY...
</div>
@ -124,7 +124,7 @@ export function FallbackGroupConfig({ group, onChange, availableModels, maxFallb
return (
<div className="flex items-center gap-2">
{isSelected && orderIndex !== null && (
<span className="flex items-center justify-center w-5 h-5 rounded bg-indigo-100 text-indigo-600 text-xs font-bold">
<span className="flex items-center justify-center w-5 h-5 rounded-sm bg-indigo-100 text-indigo-600 text-xs font-bold">
{orderIndex}
</span>
)}
@ -163,10 +163,10 @@ export function FallbackGroupConfig({ group, onChange, availableModels, maxFallb
return (
<div
key={`${modelValue}-${index}`}
className="group flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200 hover:border-indigo-300 hover:shadow-sm transition-all"
className="group flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200 hover:border-indigo-300 hover:shadow-xs transition-all"
>
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-6 h-6 rounded bg-gray-100 text-gray-400 group-hover:text-indigo-500 group-hover:bg-indigo-50">
<div className="flex items-center justify-center w-6 h-6 rounded-sm bg-gray-100 text-gray-400 group-hover:text-indigo-500 group-hover:bg-indigo-50">
<span className="text-xs font-bold">{index + 1}</span>
</div>
<div>

View file

@ -291,7 +291,7 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
<div className="space-y-6">
{/* Two-panel policy layout */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-xs">
<h2 className="text-sm font-semibold text-gray-700 mb-1">Input Policy</h2>
<p className="text-xs text-gray-500 mb-3">
{inputDesc ?? "Controls what data this tool is allowed to accept."}
@ -308,7 +308,7 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
/>
</section>
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-xs">
<h2 className="text-sm font-semibold text-gray-700 mb-1">Output Policy</h2>
<p className="text-xs text-gray-500 mb-3">
{outputDesc ?? "Controls how this tool's output is trusted by downstream tools."}
@ -327,7 +327,7 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
</div>
{overrides.length > 0 && (
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-xs">
<h2 className="text-sm font-semibold text-gray-700 mb-3">Blocked for team or key</h2>
<ul className="border rounded-md divide-y divide-gray-100 bg-red-50/30">
{overrides.map((ov) => (
@ -353,7 +353,7 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
</section>
)}
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-xs">
<h2 className="text-sm font-semibold text-gray-700 mb-3">Block for team or key</h2>
<div className="flex flex-col gap-4 max-w-md">
<div>
@ -417,7 +417,7 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
</div>
</section>
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
<section className="bg-white rounded-lg border border-gray-200 p-5 shadow-xs">
<h2 className="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
<HistoryOutlined />
Recent logs

View file

@ -309,7 +309,7 @@ export const ToolPolicies: React.FC<ToolPoliciesProps> = ({ accessToken, onSelec
</div>
)}
<div className="bg-white rounded-lg shadow w-full max-w-full box-border">
<div className="bg-white rounded-lg shadow-sm w-full max-w-full box-border">
<div className="border-b px-6 py-4 w-full max-w-full box-border">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0 w-full max-w-full box-border">
<div className="flex flex-wrap items-center gap-3">
@ -317,7 +317,7 @@ export const ToolPolicies: React.FC<ToolPoliciesProps> = ({ accessToken, onSelec
<input
type="text"
placeholder="Search by Tool Name"
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
@ -413,7 +413,7 @@ export const ToolPolicies: React.FC<ToolPoliciesProps> = ({ accessToken, onSelec
)}
{error && (
<div className="mx-6 mt-4 p-3 bg-red-50 border border-red-200 rounded text-sm text-red-700">{error}</div>
<div className="mx-6 mt-4 p-3 bg-red-50 border border-red-200 rounded-sm text-sm text-red-700">{error}</div>
)}
<Table className="[&_td]:py-0.5 [&_th]:py-1 w-full">
@ -467,7 +467,7 @@ export const ToolPolicies: React.FC<ToolPoliciesProps> = ({ accessToken, onSelec
<button
type="button"
onClick={() => onSelectTool?.(tool.tool_name)}
className="text-left w-full font-mono text-xs max-w-[20ch] truncate block font-medium text-blue-600 hover:text-blue-800 hover:underline focus:outline-none focus:ring-0"
className="text-left w-full font-mono text-xs max-w-[20ch] truncate block font-medium text-blue-600 hover:text-blue-800 hover:underline focus:outline-hidden focus:ring-0"
>
<Tooltip title={onSelectTool ? "Click to view details and block for team/key" : tool.tool_name}>
<span>{tool.tool_name}</span>

View file

@ -14,10 +14,7 @@ import {
import { useEffect, useState } from "react";
import { getRemainingUsers, getLicenseInfo, LicenseInfo } from "./networking";
// Simple utility function to combine class names
const cn = (...classes: (string | boolean | undefined)[]) => {
return classes.filter(Boolean).join(" ");
};
import { cn } from "@/lib/cva.config";
interface UsageIndicatorProps {
accessToken: string | null;
@ -173,29 +170,29 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
<button
onClick={() => setIsMinimized(false)}
className={cn(
"flex items-center gap-2 text-xs text-gray-400 hover:text-gray-600 transition-colors p-1 rounded w-full",
"flex items-center gap-2 text-xs text-gray-400 hover:text-gray-600 transition-colors p-1 rounded-sm w-full",
hasError && "text-red-400 hover:text-red-600",
hasWarning && "text-yellow-500 hover:text-yellow-700",
)}
title="Show usage details"
>
<Users className="h-3 w-3 flex-shrink-0" />
{hasAnyIssue && <span className="flex-shrink-0">{getStatusIcon()}</span>}
<Users className="h-3 w-3 shrink-0" />
{hasAnyIssue && <span className="shrink-0">{getStatusIcon()}</span>}
<div className="flex items-center gap-1 truncate">
{data && data.total_users !== null && (
<span className="flex-shrink-0">
<span className="shrink-0">
U:{data.total_users_used}/{data.total_users}
</span>
)}
{data && data.total_teams !== null && (
<span className="flex-shrink-0">
<span className="shrink-0">
T:{data.total_teams_used}/{data.total_teams}
</span>
)}
{licenseInfo?.expiration_date && daysUntilExpiration !== null && (
<span
className={cn(
"flex-shrink-0",
"shrink-0",
isLicenseExpired && "text-red-500",
isLicenseExpiringSoon && "text-yellow-500",
)}
@ -222,7 +219,7 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
if (isLoading) {
return (
<div className="flex items-center gap-3 px-3 py-2 text-gray-500" style={{ maxWidth: `${width}px` }}>
<Loader2 className="h-4 w-4 animate-spin flex-shrink-0" />
<Loader2 className="h-4 w-4 animate-spin shrink-0" />
<span className="text-sm truncate">Loading...</span>
</div>
);
@ -235,12 +232,12 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
style={{ maxWidth: `${width}px` }}
>
<div className="flex items-center gap-3 min-w-0 flex-1">
<Users className="h-4 w-4 flex-shrink-0" />
<Users className="h-4 w-4 shrink-0" />
<span className="text-sm truncate">{error || "No data"}</span>
</div>
<button
onClick={() => setIsMinimized(true)}
className="opacity-0 group-hover:opacity-100 p-0.5 hover:bg-gray-100 rounded transition-all flex-shrink-0"
className="opacity-0 group-hover:opacity-100 p-0.5 hover:bg-gray-100 rounded-sm transition-all shrink-0"
title="Minimize"
>
<Minus className="h-3 w-3" />
@ -261,24 +258,24 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
hasWarning && "text-yellow-600",
)}
>
<Users className="h-4 w-4 flex-shrink-0" />
<Users className="h-4 w-4 shrink-0" />
<span className="text-sm font-medium truncate">Usage Status</span>
{hasAnyIssue && (
<Badge color={getStatusColor()} className="text-xs px-1.5 py-0.5 flex-shrink-0">
<Badge color={getStatusColor()} className="text-xs px-1.5 py-0.5 shrink-0">
{getStatusIcon()}
</Badge>
)}
{isExpanded ? (
<ChevronUp className="h-3 w-3 text-gray-400 ml-auto flex-shrink-0" />
<ChevronUp className="h-3 w-3 text-gray-400 ml-auto shrink-0" />
) : (
<ChevronDown className="h-3 w-3 text-gray-400 ml-auto flex-shrink-0" />
<ChevronDown className="h-3 w-3 text-gray-400 ml-auto shrink-0" />
)}
</button>
{/* Minimize button */}
<button
onClick={() => setIsMinimized(true)}
className="opacity-0 group-hover:opacity-100 p-0.5 hover:bg-gray-100 rounded transition-all ml-1 flex-shrink-0"
className="opacity-0 group-hover:opacity-100 p-0.5 hover:bg-gray-100 rounded-sm transition-all ml-1 shrink-0"
title="Minimize"
>
<Minus className="h-3 w-3 text-gray-400" />
@ -410,18 +407,18 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
<button
onClick={() => setIsMinimized(false)}
className={cn(
"bg-white border border-gray-200 rounded-lg shadow-sm p-3 hover:shadow-md transition-all w-full",
"bg-white border border-gray-200 rounded-lg shadow-xs p-3 hover:shadow-md transition-all w-full",
)}
title="Show usage details"
>
<div className="flex items-center gap-2">
<Users className="h-4 w-4 flex-shrink-0" />
{hasAnyIssue && <span className="flex-shrink-0">{getStatusIcon()}</span>}
<Users className="h-4 w-4 shrink-0" />
{hasAnyIssue && <span className="shrink-0">{getStatusIcon()}</span>}
<div className="flex items-center gap-2 text-sm font-medium truncate">
{data && data.total_users !== null && (
<span
className={cn(
"flex-shrink-0 px-1.5 py-0.5 rounded text-xs border",
"shrink-0 px-1.5 py-0.5 rounded-sm text-xs border",
userMetrics.isOverLimit && "bg-red-50 text-red-700 border-red-200",
userMetrics.isNearLimit && "bg-yellow-50 text-yellow-700 border-yellow-200",
!userMetrics.isOverLimit && !userMetrics.isNearLimit && "bg-gray-50 text-gray-700 border-gray-200",
@ -433,7 +430,7 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
{data && data.total_teams !== null && (
<span
className={cn(
"flex-shrink-0 px-1.5 py-0.5 rounded text-xs border",
"shrink-0 px-1.5 py-0.5 rounded-sm text-xs border",
teamMetrics.isOverLimit && "bg-red-50 text-red-700 border-red-200",
teamMetrics.isNearLimit && "bg-yellow-50 text-yellow-700 border-yellow-200",
!teamMetrics.isOverLimit && !teamMetrics.isNearLimit && "bg-gray-50 text-gray-700 border-gray-200",
@ -445,7 +442,7 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
{licenseInfo?.expiration_date && daysUntilExpiration !== null && (
<span
className={cn(
"flex-shrink-0 px-1.5 py-0.5 rounded text-xs border",
"shrink-0 px-1.5 py-0.5 rounded-sm text-xs border",
isLicenseExpired && "bg-red-50 text-red-700 border-red-200",
isLicenseExpiringSoon && "bg-yellow-50 text-yellow-700 border-yellow-200",
!isLicenseExpired && !isLicenseExpiringSoon && "bg-gray-50 text-gray-700 border-gray-200",
@ -466,7 +463,7 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
if (isLoading) {
return (
<div className="bg-white border border-gray-200 rounded-lg shadow-sm p-4 w-full">
<div className="bg-white border border-gray-200 rounded-lg shadow-xs p-4 w-full">
<div className="flex items-center justify-center gap-2 py-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="text-sm text-gray-500 truncate">Loading...</span>
@ -477,14 +474,14 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
if (error || !data) {
return (
<div className="bg-white border border-gray-200 rounded-lg shadow-sm p-4 group w-full">
<div className="bg-white border border-gray-200 rounded-lg shadow-xs p-4 group w-full">
<div className="flex items-center justify-between gap-2">
<div className="flex-1 min-w-0">
<span className="text-sm text-gray-500 truncate block">{error || "No data"}</span>
</div>
<button
onClick={() => setIsMinimized(true)}
className="opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded transition-all flex-shrink-0"
className="opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded-sm transition-all shrink-0"
title="Minimize"
>
<Minus className="h-3 w-3 text-gray-400" />
@ -495,15 +492,15 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
}
return (
<div className={cn("bg-white border rounded-lg shadow-sm p-3 transition-all duration-200 group w-full")}>
<div className={cn("bg-white border rounded-lg shadow-xs p-3 transition-all duration-200 group w-full")}>
<div className="flex items-center justify-between gap-2 mb-3">
<div className="flex items-center gap-2 min-w-0 flex-1">
<Users className="h-4 w-4 flex-shrink-0" />
<Users className="h-4 w-4 shrink-0" />
<span className="font-medium text-sm truncate">Usage</span>
</div>
<button
onClick={() => setIsMinimized(true)}
className="opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded transition-all flex-shrink-0"
className="opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded-sm transition-all shrink-0"
title="Minimize"
>
<Minus className="h-3 w-3 text-gray-400" />
@ -526,7 +523,7 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
<span className="font-medium">License</span>
<span
className={cn(
"ml-1 px-1.5 py-0.5 rounded border",
"ml-1 px-1.5 py-0.5 rounded-sm border",
isLicenseExpired && "bg-red-50 text-red-700 border-red-200",
isLicenseExpiringSoon && "bg-yellow-50 text-yellow-700 border-yellow-200",
!isLicenseExpired && !isLicenseExpiringSoon && "bg-gray-50 text-gray-600 border-gray-200",
@ -570,7 +567,7 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
<span className="font-medium">Users</span>
<span
className={cn(
"ml-1 px-1.5 py-0.5 rounded border",
"ml-1 px-1.5 py-0.5 rounded-sm border",
userMetrics.isOverLimit && "bg-red-50 text-red-700 border-red-200",
userMetrics.isNearLimit && "bg-yellow-50 text-yellow-700 border-yellow-200",
!userMetrics.isOverLimit && !userMetrics.isNearLimit && "bg-gray-50 text-gray-600 border-gray-200",
@ -631,7 +628,7 @@ export default function UsageIndicator({ accessToken, width = 220 }: UsageIndica
<span className="font-medium">Teams</span>
<span
className={cn(
"ml-1 px-1.5 py-0.5 rounded border",
"ml-1 px-1.5 py-0.5 rounded-sm border",
teamMetrics.isOverLimit && "bg-red-50 text-red-700 border-red-200",
teamMetrics.isNearLimit && "bg-yellow-50 text-yellow-700 border-yellow-200",
!teamMetrics.isOverLimit && !teamMetrics.isNearLimit && "bg-gray-50 text-gray-600 border-gray-200",

View file

@ -262,15 +262,12 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
keyData &&
(console.log("Rendering modal with:", { isModalOpen, selectedKey, keyData }),
(
<div
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
onClick={handleOutsideClick}
>
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={handleOutsideClick}>
<div className="bg-white rounded-lg shadow-xl relative w-11/12 max-w-6xl max-h-[90vh] overflow-y-auto min-h-[750px]">
{/* Close button */}
<button
onClick={handleClose}
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700 focus:outline-none"
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700 focus:outline-hidden"
aria-label="Close"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">

View file

@ -39,7 +39,7 @@ const ToolCallDisplay: React.FC<{ step: ToolCallStep }> = ({ step }) => {
return (
<div className="flex items-start gap-2 px-3 py-2 rounded-lg bg-gray-100 border border-gray-200 text-xs">
<span className="flex-shrink-0 mt-0.5">
<span className="shrink-0 mt-0.5">
{step.status === "running" ? (
<Spin size="small" />
) : step.status === "error" ? (
@ -74,11 +74,11 @@ const MarkdownContent: React.FC<{ content: string }> = ({ content }) => (
code: ({ children, className }) => {
const isBlock = className?.includes("language-");
return isBlock ? (
<pre className="bg-gray-100 rounded p-2 my-1 overflow-x-auto text-xs">
<pre className="bg-gray-100 rounded-sm p-2 my-1 overflow-x-auto text-xs">
<code>{children}</code>
</pre>
) : (
<code className="px-1 py-0.5 rounded bg-gray-100 text-xs font-mono">{children}</code>
<code className="px-1 py-0.5 rounded-sm bg-gray-100 text-xs font-mono">{children}</code>
);
},
table: ({ children }) => (
@ -237,7 +237,7 @@ const UsageAIChatPanel: React.FC<UsageAIChatPanelProps> = ({ open, onClose, acce
style={{ width: 420 }}
>
{/* Header */}
<div className="px-5 pt-5 pb-3 border-b border-gray-100 flex-shrink-0">
<div className="px-5 pt-5 pb-3 border-b border-gray-100 shrink-0">
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<svg className="w-5 h-5 text-blue-600" viewBox="0 0 16 16" fill="currentColor">
@ -258,7 +258,7 @@ const UsageAIChatPanel: React.FC<UsageAIChatPanelProps> = ({ open, onClose, acce
</div>
{/* Model selector */}
<div className="px-5 py-3 border-b border-gray-100 flex-shrink-0">
<div className="px-5 py-3 border-b border-gray-100 shrink-0">
<Select
placeholder="Select a model (optional, defaults to gpt-4o-mini)"
value={selectedModel}
@ -345,7 +345,7 @@ const UsageAIChatPanel: React.FC<UsageAIChatPanelProps> = ({ open, onClose, acce
</div>
{/* Input area */}
<div className="px-4 py-3 border-t border-gray-200 bg-white flex-shrink-0">
<div className="px-4 py-3 border-t border-gray-200 bg-white shrink-0">
<div className="flex gap-2">
<TextArea
value={inputText}

View file

@ -747,7 +747,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
<button
className={`px-3 py-1 text-sm rounded-md transition-colors ${
modelViewType === "groups"
? "bg-white shadow-sm text-gray-900"
? "bg-white shadow-xs text-gray-900"
: "text-gray-600 hover:text-gray-900"
}`}
onClick={() => setModelViewType("groups")}
@ -757,7 +757,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
<button
className={`px-3 py-1 text-sm rounded-md transition-colors ${
modelViewType === "individual"
? "bg-white shadow-sm text-gray-900"
? "bg-white shadow-xs text-gray-900"
: "text-gray-600 hover:text-gray-900"
}`}
onClick={() => setModelViewType("individual")}

View file

@ -153,7 +153,7 @@ export const UsageViewSelect: React.FC<UsageViewSelectProps> = ({
<div className="w-full" data-id={dataId}>
<div className="flex flex-wrap items-center justify-start gap-4">
<div className="flex items-stretch gap-2 min-w-0">
<div className="flex-shrink-0 flex items-center">
<div className="shrink-0 flex items-center">
<BarChartOutlined style={{ fontSize: "32px" }} />
</div>
<div className="flex-1 min-w-0">
@ -161,7 +161,7 @@ export const UsageViewSelect: React.FC<UsageViewSelectProps> = ({
<p className="text-xs text-gray-600 leading-tight">{description}</p>
</div>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
<Select
value={value}
onChange={onChange}
@ -176,7 +176,7 @@ export const UsageViewSelect: React.FC<UsageViewSelectProps> = ({
if (!opt) return option.label;
return (
<div className="flex items-center gap-2 py-1">
<div className="flex-shrink-0 mt-0.5">{opt.icon}</div>
<div className="shrink-0 mt-0.5">{opt.icon}</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-gray-900">{opt.label}</div>
<div className="text-xs text-gray-600 mt-0.5">{opt.description}</div>

View file

@ -266,9 +266,9 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
return (
<>
<div className="flex items-center my-4">
<div className="flex-grow border-t border-gray-200"></div>
<div className="grow border-t border-gray-200"></div>
<span className="px-4 text-gray-500 text-sm">OR</span>
<div className="flex-grow border-t border-gray-200"></div>
<div className="grow border-t border-gray-200"></div>
</div>
<ProviderSpecificFields selectedProvider={selectedProvider} uploadProps={uploadProps} />
</>
@ -278,9 +278,9 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
}}
</Form.Item>
<div className="flex items-center my-4">
<div className="flex-grow border-t border-gray-200"></div>
<div className="grow border-t border-gray-200"></div>
<span className="px-4 text-gray-500 text-sm">Additional Model Info Settings</span>
<div className="flex-grow border-t border-gray-200"></div>
<div className="grow border-t border-gray-200"></div>
</div>
{/* Team-only Model Switch - Only show for proxy admins, not team admins */}
{(isAdmin || !isTeamAdmin) && (

View file

@ -364,9 +364,9 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
)}
<div className="flex items-center my-4">
<div className="flex-grow border-t border-gray-200"></div>
<div className="grow border-t border-gray-200"></div>
<span className="px-4 text-gray-500 text-sm">Additional Settings</span>
<div className="flex-grow border-t border-gray-200"></div>
<div className="grow border-t border-gray-200"></div>
</div>
{/* Model Access Groups - Admin only */}

View file

@ -136,7 +136,7 @@ const CacheControlSettings: React.FC<CacheControlSettingsProps> = ({
<Form.Item>
<button
type="button"
className="flex items-center justify-center w-full border border-dashed border-gray-300 py-2 px-4 text-gray-600 hover:text-blue-600 hover:border-blue-300 transition-all rounded"
className="flex items-center justify-center w-full border border-dashed border-gray-300 py-2 px-4 text-gray-600 hover:text-blue-600 hover:border-blue-300 transition-all rounded-sm"
onClick={() => add()}
>
<PlusOutlined className="mr-2" />

View file

@ -98,16 +98,16 @@ const ConditionalPublicModelName: React.FC = () => {
<div className="mb-2 font-normal">The name you specify in your API calls to LiteLLM Proxy</div>
<div className="mb-2 font-normal">
<strong>Example:</strong> If you name your public model{" "}
<code className="bg-gray-700 px-1 py-0.5 rounded text-xs">example-name</code>, and choose{" "}
<code className="bg-gray-700 px-1 py-0.5 rounded text-xs">openai/qwen-plus-latest</code> as the LiteLLM model
<code className="bg-gray-700 px-1 py-0.5 rounded-sm text-xs">example-name</code>, and choose{" "}
<code className="bg-gray-700 px-1 py-0.5 rounded-sm text-xs">openai/qwen-plus-latest</code> as the LiteLLM model
</div>
<div className="mb-2 font-normal">
<strong>Usage:</strong> You make an API call to the LiteLLM proxy with{" "}
<code className="bg-gray-700 px-1 py-0.5 rounded text-xs">model = &quot;example-name&quot;</code>
<code className="bg-gray-700 px-1 py-0.5 rounded-sm text-xs">model = &quot;example-name&quot;</code>
</div>
<div className="font-normal">
<strong>Result:</strong> LiteLLM sends{" "}
<code className="bg-gray-700 px-1 py-0.5 rounded text-xs">qwen-plus-latest</code> to the provider
<code className="bg-gray-700 px-1 py-0.5 rounded-sm text-xs">qwen-plus-latest</code> to the provider
</div>
</>
);

View file

@ -1010,7 +1010,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
<button
type="button"
onClick={handleBack}
className="text-sm text-gray-600 border border-gray-300 rounded px-4 py-2 hover:bg-gray-50"
className="text-sm text-gray-600 border border-gray-300 rounded-sm px-4 py-2 hover:bg-gray-50"
>
Back
</button>

View file

@ -249,7 +249,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
<Paragraph className="text-xs text-gray-500 mb-2">
Using the connection details you entered above. We&apos;ll fetch:
</Paragraph>
<div className="bg-white border border-gray-200 rounded px-3 py-2 mb-3 font-mono text-xs text-gray-700 break-all">
<div className="bg-white border border-gray-200 rounded-sm px-3 py-2 mb-3 font-mono text-xs text-gray-700 break-all">
{discoveryRequest!.display_url || effectiveUrl || (
<span className="text-gray-400 italic">Fill in the fields above first</span>
)}
@ -409,7 +409,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
return (
<div
key={key}
className="flex items-center justify-between p-2 border border-gray-200 rounded bg-white"
className="flex items-center justify-between p-2 border border-gray-200 rounded-sm bg-white"
>
<div>
<Text strong className="capitalize">

View file

@ -22,7 +22,7 @@ const AgentVirtualKeys: React.FC<AgentVirtualKeysProps> = ({ keys, isLoading, on
) : (
<div className="mt-3 flex flex-col gap-2">
{keys.map((key) => (
<div key={key.token} className="flex items-center gap-3 border border-gray-100 rounded px-3 py-2">
<div key={key.token} className="flex items-center gap-3 border border-gray-100 rounded-sm px-3 py-2">
<KeyOutlined className="text-gray-400" />
<span className="font-medium">{key.key_alias || "Unnamed key"}</span>
{key.key_name && <span className="font-mono text-xs text-gray-500">{key.key_name}</span>}

View file

@ -125,9 +125,9 @@ const DynamicForm: React.FC<DynamicFormProps> = ({
In DB
</Badge>
) : value.stored_in_db == false ? (
<Badge className="text-gray bg-white outline">In Config</Badge>
<Badge className="text-gray bg-white outline-solid">In Config</Badge>
) : (
<Badge className="text-gray bg-white outline">Not Set</Badge>
<Badge className="text-gray bg-white outline-solid">Not Set</Badge>
)}
</TableCell>
<TableCell>

View file

@ -576,14 +576,14 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
<h4 className="font-medium mb-2">Template Column Names</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="flex items-start">
<div className="w-3 h-3 rounded-full bg-red-500 mt-1.5 mr-2 flex-shrink-0"></div>
<div className="w-3 h-3 rounded-full bg-red-500 mt-1.5 mr-2 shrink-0"></div>
<div>
<p className="font-medium">user_email</p>
<p className="text-sm text-gray-600">User&apos;s email address (required)</p>
</div>
</div>
<div className="flex items-start">
<div className="w-3 h-3 rounded-full bg-red-500 mt-1.5 mr-2 flex-shrink-0"></div>
<div className="w-3 h-3 rounded-full bg-red-500 mt-1.5 mr-2 shrink-0"></div>
<div>
<p className="font-medium">user_role</p>
<p className="text-sm text-gray-600">
@ -593,7 +593,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
</div>
</div>
<div className="flex items-start">
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"></div>
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 shrink-0"></div>
<div>
<p className="font-medium">teams</p>
<p className="text-sm text-gray-600">
@ -602,14 +602,14 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
</div>
</div>
<div className="flex items-start">
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"></div>
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 shrink-0"></div>
<div>
<p className="font-medium">max_budget</p>
<p className="text-sm text-gray-600">Maximum budget as a number (e.g., &quot;100&quot;)</p>
</div>
</div>
<div className="flex items-start">
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"></div>
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 shrink-0"></div>
<div>
<p className="font-medium">budget_duration</p>
<p className="text-sm text-gray-600">
@ -618,7 +618,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
</div>
</div>
<div className="flex items-start">
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"></div>
<div className="w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 shrink-0"></div>
<div>
<p className="font-medium">models</p>
<p className="text-sm text-gray-600">
@ -760,11 +760,11 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
{parsedData.some((user) => user.status === "success" || user.status === "failed") ? (
<div className="flex items-center">
<Text className="text-lg font-medium mr-3">Creation Summary</Text>
<Text className="text-sm bg-green-100 text-green-800 px-2 py-1 rounded mr-2">
<Text className="text-sm bg-green-100 text-green-800 px-2 py-1 rounded-sm mr-2">
{parsedData.filter((d) => d.status === "success").length} Successful
</Text>
{parsedData.some((d) => d.status === "failed") && (
<Text className="text-sm bg-red-100 text-red-800 px-2 py-1 rounded">
<Text className="text-sm bg-red-100 text-red-800 px-2 py-1 rounded-sm">
{parsedData.filter((d) => d.status === "failed").length} Failed
</Text>
)}
@ -772,7 +772,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
) : (
<div className="flex items-center">
<Text className="text-lg font-medium mr-3">User Preview</Text>
<Text className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
<Text className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded-sm">
{parsedData.filter((d) => d.isValid).length} of {parsedData.length} users valid
</Text>
</div>

View file

@ -52,7 +52,7 @@ const ReasoningContent: React.FC<ReasoningContentProps> = ({ reasoningContent })
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code className={`${className} px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono`} {...props}>
<code className={`${className} px-1.5 py-0.5 rounded-sm bg-gray-100 text-sm font-mono`} {...props}>
{children}
</code>
);

View file

@ -82,7 +82,7 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({ accessT
<h1 className="text-2xl font-bold">Skills</h1>
<p className="text-sm text-gray-600">
Register Claude Code skills. Published skills appear in the Skill Hub for all users and are served via{" "}
<code className="bg-gray-100 px-1 rounded">/claude-code/marketplace.json</code>.
<code className="bg-gray-100 px-1 rounded-sm">/claude-code/marketplace.json</code>.
</p>
<div className="mt-2 flex gap-2">
<Button onClick={() => setIsAddModalVisible(true)} disabled={!accessToken || !isAdmin}>

View file

@ -186,7 +186,7 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
{Array.from(selectedSkills).map((name) => {
const skill = skillsList.find((s) => s.name === name);
return (
<div key={name} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<div key={name} className="flex items-center justify-between p-2 bg-gray-50 rounded-sm">
<Text className="font-mono text-sm">{name}</Text>
{skill?.domain && (
<Badge color="blue" size="xs">

View file

@ -233,13 +233,13 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
<div className="flex space-x-2">
<button
onClick={handleUpdateAlias}
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100"
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded-sm hover:bg-blue-100"
>
Save
</button>
<button
onClick={handleCancelEdit}
className="text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100"
className="text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded-sm hover:bg-gray-100"
>
Cancel
</button>
@ -254,13 +254,13 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
<div className="flex space-x-2">
<button
onClick={() => handleEditAlias(alias)}
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100"
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded-sm hover:bg-blue-100"
>
<PencilIcon className="w-3 h-3" />
</button>
<button
onClick={() => deleteAlias(alias.id)}
className="text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100"
className="text-xs bg-red-50 text-red-600 px-2 py-1 rounded-sm hover:bg-red-100"
>
<TrashIcon className="w-3 h-3" />
</button>

View file

@ -99,13 +99,13 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
<div className="text-xs space-y-1 mt-2">
<div className="font-medium">Common Examples:</div>
<div>
<code className="bg-gray-100 px-1 rounded">query</code> - Single field
<code className="bg-gray-100 px-1 rounded-sm">query</code> - Single field
</div>
<div>
<code className="bg-gray-100 px-1 rounded">documents[*].text</code> - All text in documents array
<code className="bg-gray-100 px-1 rounded-sm">documents[*].text</code> - All text in documents array
</div>
<div>
<code className="bg-gray-100 px-1 rounded">messages[*].content</code> - All message contents
<code className="bg-gray-100 px-1 rounded-sm">messages[*].content</code> - All message contents
</div>
</div>
</div>
@ -170,7 +170,7 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
const current = guardrailSettings[guardrailName]?.request_fields || [];
handleFieldChange(guardrailName, "request_fields", [...current, "query"]);
}}
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50"
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded-sm hover:bg-gray-50"
disabled={disabled}
>
+ query
@ -181,7 +181,7 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
const current = guardrailSettings[guardrailName]?.request_fields || [];
handleFieldChange(guardrailName, "request_fields", [...current, "documents[*]"]);
}}
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50"
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded-sm hover:bg-gray-50"
disabled={disabled}
>
+ documents[*]
@ -224,7 +224,7 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
const current = guardrailSettings[guardrailName]?.response_fields || [];
handleFieldChange(guardrailName, "response_fields", [...current, "results[*]"]);
}}
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50"
className="text-xs px-2 py-1 bg-white border border-gray-300 rounded-sm hover:bg-gray-50"
disabled={disabled}
>
+ results[*]

View file

@ -169,9 +169,9 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken, user
In DB
</Badge>
) : value.stored_in_db == false ? (
<Badge className="text-gray bg-white outline">In Config</Badge>
<Badge className="text-gray bg-white outline-solid">In Config</Badge>
) : (
<Badge className="text-gray bg-white outline">Not Set</Badge>
<Badge className="text-gray bg-white outline-solid">Not Set</Badge>
)}
</TableCell>
<TableCell>

View file

@ -133,9 +133,9 @@ export function GuardrailTestPanel({
/>
<div className="flex justify-between items-center mt-1">
<Text className="text-xs text-gray-500">
Press <kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs">Enter</kbd> to
Press <kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded-sm text-xs">Enter</kbd> to
submit {" "}
<kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs">Shift+Enter</kbd> for
<kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded-sm text-xs">Shift+Enter</kbd> for
new line
</Text>
<Text className="text-xs text-gray-500">Characters: {inputText.length}</Text>

View file

@ -118,9 +118,9 @@ export function GuardrailTestResults({ results, errors }: GuardrailTestResultsPr
</div>
{!isCollapsed && (
<>
<div className="bg-white border border-green-200 rounded p-3">
<div className="bg-white border border-green-200 rounded-sm p-3">
<label className="text-xs font-medium text-gray-600 mb-2 block">Output Text</label>
<div className="font-mono text-sm text-gray-900 whitespace-pre-wrap break-words">
<div className="font-mono text-sm text-gray-900 whitespace-pre-wrap wrap-break-word">
{result.response_text}
</div>
</div>

View file

@ -193,7 +193,7 @@ function Toggle({ enabled, onToggle }: { enabled: boolean; onToggle: () => void
onClick={onToggle}
role="switch"
aria-checked={enabled}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 ${
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 ${
enabled ? "bg-blue-500" : "bg-gray-200"
}`}
>
@ -249,7 +249,7 @@ function GuardrailCard({
<h3 className="text-sm font-semibold text-gray-900 mb-1">{g.name}</h3>
<p className="text-xs text-gray-500 mb-2 line-clamp-1">{g.description}</p>
<div className="flex items-center gap-1.5 mb-2">
<ServerIcon className="h-3.5 w-3.5 text-gray-400 flex-shrink-0" />
<ServerIcon className="h-3.5 w-3.5 text-gray-400 shrink-0" />
<code className="text-xs text-gray-500 font-mono truncate">{g.endpoint}</code>
</div>
<div className="flex items-center gap-4 text-xs text-gray-500">
@ -261,7 +261,7 @@ function GuardrailCard({
</span>
</div>
</div>
<div className="flex flex-col items-end gap-2 flex-shrink-0">
<div className="flex flex-col items-end gap-2 shrink-0">
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 whitespace-nowrap">Forward API Key</span>
<Toggle enabled={g.forwardKey} onToggle={onToggleForwardKey} />
@ -317,9 +317,11 @@ function GuardrailCard({
<div className="space-y-1">
{g.customHeaders.map((h, i) => (
<div key={`${h.key}-${i}`} className="flex items-center gap-2 text-xs font-mono">
<span className="text-gray-500 bg-gray-50 border border-gray-200 rounded px-2 py-0.5">{h.key}</span>
<span className="text-gray-500 bg-gray-50 border border-gray-200 rounded-sm px-2 py-0.5">
{h.key}
</span>
<span className="text-gray-400">:</span>
<span className="text-gray-700 bg-gray-50 border border-gray-200 rounded px-2 py-0.5">
<span className="text-gray-700 bg-gray-50 border border-gray-200 rounded-sm px-2 py-0.5">
{h.value}
</span>
</div>
@ -368,7 +370,7 @@ function DetailPanel({
const status = STATUS_CONFIG[g.status];
const teamColor = TEAM_COLORS[g.team] ?? "bg-gray-100 text-gray-700";
return (
<div className="w-96 flex-shrink-0 bg-white overflow-auto">
<div className="w-96 shrink-0 bg-white overflow-auto">
<div className="p-5">
<div className="flex items-start justify-between mb-4">
<div>
@ -404,14 +406,14 @@ function DetailPanel({
href={g.endpoint}
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-blue-500 flex-shrink-0"
className="text-gray-400 hover:text-blue-500 shrink-0"
>
<ExternalLinkIcon className="h-3.5 w-3.5" />
</a>
</div>
</ConfigRow>
<ConfigRow label="Method">
<span className="text-xs font-mono font-medium text-gray-700 bg-gray-100 px-2 py-0.5 rounded">
<span className="text-xs font-mono font-medium text-gray-700 bg-gray-100 px-2 py-0.5 rounded-sm">
{g.method}
</span>
</ConfigRow>
@ -425,7 +427,7 @@ function DetailPanel({
</div>
<p className="text-xs text-blue-700 leading-relaxed">
When enabled, the caller&apos;s LiteLLM API key is forwarded as an{" "}
<code className="font-mono bg-blue-100 px-1 rounded">Authorization</code> header to your guardrail
<code className="font-mono bg-blue-100 px-1 rounded-sm">Authorization</code> header to your guardrail
endpoint. This allows your guardrail to authenticate model calls using the original caller&apos;s
credentials.
</p>
@ -447,7 +449,7 @@ function DetailPanel({
{g.customHeaders.map((h, i) => (
<li
key={`${h.key}-${i}`}
className="flex items-center justify-between gap-2 text-xs font-mono bg-gray-50 border border-gray-200 rounded px-2 py-1.5"
className="flex items-center justify-between gap-2 text-xs font-mono bg-gray-50 border border-gray-200 rounded-sm px-2 py-1.5"
>
<span className="text-gray-700 truncate">
{h.key}: {h.value}
@ -455,7 +457,7 @@ function DetailPanel({
<button
type="button"
onClick={() => onUpdateCustomHeaders(g.customHeaders.filter((_, idx) => idx !== i))}
className="text-gray-400 hover:text-red-600 flex-shrink-0"
className="text-gray-400 hover:text-red-600 shrink-0"
aria-label={`Remove ${h.key}`}
>
<XIcon className="h-3.5 w-3.5" />
@ -470,7 +472,7 @@ function DetailPanel({
value={newStaticHeaderKey}
onChange={(e) => setNewStaticHeaderKey(e.target.value)}
placeholder="Header name (e.g. X-API-Key)"
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500"
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded-sm px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@ -489,7 +491,7 @@ function DetailPanel({
value={newStaticHeaderValue}
onChange={(e) => setNewStaticHeaderValue(e.target.value)}
placeholder="Value"
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500"
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded-sm px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@ -514,7 +516,7 @@ function DetailPanel({
setNewStaticHeaderValue("");
}
}}
className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded transition-colors flex-shrink-0"
className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded-sm transition-colors shrink-0"
>
Add
</button>
@ -539,13 +541,13 @@ function DetailPanel({
{g.extraHeaders.map((name, i) => (
<li
key={`${name}-${i}`}
className="flex items-center justify-between gap-2 text-xs font-mono bg-gray-50 border border-gray-200 rounded px-2 py-1.5"
className="flex items-center justify-between gap-2 text-xs font-mono bg-gray-50 border border-gray-200 rounded-sm px-2 py-1.5"
>
<span className="text-gray-700 truncate">{name}</span>
<button
type="button"
onClick={() => onUpdateExtraHeaders(g.extraHeaders.filter((_, idx) => idx !== i))}
className="text-gray-400 hover:text-red-600 flex-shrink-0"
className="text-gray-400 hover:text-red-600 shrink-0"
aria-label={`Remove ${name}`}
>
<XIcon className="h-3.5 w-3.5" />
@ -560,7 +562,7 @@ function DetailPanel({
value={newExtraHeader}
onChange={(e) => setNewExtraHeader(e.target.value)}
placeholder="e.g. x-request-id"
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500"
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded-sm px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-hidden focus:ring-1 focus:ring-blue-500"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@ -581,7 +583,7 @@ function DetailPanel({
setNewExtraHeader("");
}
}}
className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded transition-colors"
className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded-sm transition-colors"
>
Add
</button>
@ -607,7 +609,7 @@ function DetailPanel({
)}
</div>
<div className="flex items-start gap-2 bg-gray-50 border border-gray-200 rounded-lg p-3">
<InfoIcon className="h-3.5 w-3.5 text-gray-400 flex-shrink-0 mt-0.5" />
<InfoIcon className="h-3.5 w-3.5 text-gray-400 shrink-0 mt-0.5" />
<p className="text-xs text-gray-500 leading-relaxed">
This guardrail runs on a separate instance. It receives the user request and forwards the result to the
next step in the pipeline. See{" "}
@ -887,13 +889,13 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
placeholder="Search guardrails..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full pl-9 pr-4 py-2 border border-gray-200 rounded-md text-sm text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
className="w-full pl-9 pr-4 py-2 border border-gray-200 rounded-md text-sm text-gray-700 placeholder-gray-400 focus:outline-hidden focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as typeof statusFilter)}
className="border border-gray-200 rounded-md px-3 py-2 text-sm text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 bg-white"
className="border border-gray-200 rounded-md px-3 py-2 text-sm text-gray-700 focus:outline-hidden focus:ring-1 focus:ring-blue-500 focus:border-blue-500 bg-white"
>
<option value="all">All Status</option>
<option value="pending">Pending Review</option>

View file

@ -1062,7 +1062,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
onChange={(e) =>
setEndSessionAfterNFails(e.target.value ? parseInt(e.target.value, 10) : undefined)
}
className="border border-gray-300 rounded px-3 py-1.5 text-sm w-32"
className="border border-gray-300 rounded-sm px-3 py-1.5 text-sm w-32"
/>
</div>
@ -1105,7 +1105,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
placeholder="e.g. I'm not able to continue this conversation. Please contact us at 1-800-774-2678."
value={realtimeViolationMessage}
onChange={(e) => setRealtimeViolationMessage(e.target.value)}
className="border border-gray-300 rounded px-3 py-2 text-sm w-full resize-none"
className="border border-gray-300 rounded-sm px-3 py-2 text-sm w-full resize-none"
/>
</div>
</div>
@ -1185,9 +1185,9 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
return (
<div key={index} className="relative flex gap-4" style={{ paddingBottom: isLast ? 0 : 8 }}>
{/* Vertical line + step indicator */}
<div className="flex flex-col items-center flex-shrink-0" style={{ width: 24 }}>
<div className="flex flex-col items-center shrink-0" style={{ width: 24 }}>
<div
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium flex-shrink-0"
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium shrink-0"
style={{
background: isDone ? "#4f46e5" : isCurrent ? "#fff" : "#f8fafc",
color: isDone ? "#fff" : isCurrent ? "#4f46e5" : "#94a3b8",

View file

@ -565,13 +565,13 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
{/* Main Content */}
<div className="flex flex-1 overflow-hidden mt-4 gap-6">
{/* Code Editor */}
<div className="flex-[2] flex flex-col min-w-0 overflow-y-auto">
<div className="flex items-center justify-between mb-2 flex-shrink-0">
<div className="flex-2 flex flex-col min-w-0 overflow-y-auto">
<div className="flex items-center justify-between mb-2 shrink-0">
<span className="text-xs font-semibold text-gray-500 uppercase tracking-wide">Python Logic</span>
<span className="text-xs text-gray-400">Restricted environment (no imports)</span>
</div>
<div
className="relative rounded-lg overflow-hidden border border-gray-700 bg-[#1e1e1e] flex-shrink-0"
className="relative rounded-lg overflow-hidden border border-gray-700 bg-[#1e1e1e] shrink-0"
style={{ minHeight: "300px", maxHeight: "400px" }}
>
{/* Line numbers */}
@ -596,7 +596,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
onChange={(e) => setCode(e.target.value)}
onKeyDown={handleKeyDown}
spellCheck={false}
className="w-full h-full pl-14 pr-4 pt-3 pb-3 resize-none focus:outline-none bg-transparent text-gray-200"
className="w-full h-full pl-14 pr-4 pt-3 pb-3 resize-none focus:outline-hidden bg-transparent text-gray-200"
style={{
fontFamily: "'Fira Code', 'Monaco', 'Consolas', monospace",
fontSize: "14px",
@ -610,7 +610,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
<Collapse
activeKey={testExpanded ? ["test"] : []}
onChange={(keys) => setTestExpanded(keys.includes("test"))}
className="mt-3 bg-white border border-gray-200 rounded-lg flex-shrink-0"
className="mt-3 bg-white border border-gray-200 rounded-lg shrink-0"
expandIcon={({ isActive }) => <CaretRightOutlined rotate={isActive ? 90 : 0} />}
>
<Panel
@ -631,27 +631,27 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.pre_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100 transition-colors"
className="px-2 py-1 text-xs rounded-sm border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100 transition-colors"
>
Pre-call
</button>
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.pre_mcp_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-purple-200 bg-purple-50 text-purple-700 hover:bg-purple-100 transition-colors"
className="px-2 py-1 text-xs rounded-sm border border-purple-200 bg-purple-50 text-purple-700 hover:bg-purple-100 transition-colors"
>
Pre MCP
</button>
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.post_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-green-200 bg-green-50 text-green-700 hover:bg-green-100 transition-colors"
className="px-2 py-1 text-xs rounded-sm border border-green-200 bg-green-50 text-green-700 hover:bg-green-100 transition-colors"
>
Post-call
</button>
</div>
</div>
<div className="mb-2 p-2 bg-gray-50 rounded text-xs text-gray-600 border border-gray-200">
<div className="mb-2 p-2 bg-gray-50 rounded-sm text-xs text-gray-600 border border-gray-200">
<div className="grid grid-cols-2 gap-x-4 gap-y-1">
<div>
<strong>texts</strong>: Message content (always)
@ -738,7 +738,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
</Panel>
</Collapse>
{/* Contribution CTA Banner */}
<div className="mt-3 p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-lg flex items-center justify-between flex-shrink-0">
<div className="mt-3 p-4 bg-linear-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-lg flex items-center justify-between shrink-0">
<div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-full p-2">
<UsergroupAddOutlined className="text-blue-600 text-lg" />
@ -760,7 +760,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
</div>
{/* Primitives Panel */}
<div className="w-[300px] flex-shrink-0 overflow-auto border-l border-gray-200 pl-6">
<div className="w-[300px] shrink-0 overflow-auto border-l border-gray-200 pl-6">
<div className="flex items-center gap-2 mb-3">
<CodeOutlined className="text-blue-500" />
<span className="font-semibold text-gray-700">Available Primitives</span>

View file

@ -580,7 +580,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
Object.keys(guardrailData.litellm_params.pii_entities_config).length > 0 && (
<Card className="mt-6">
<Text className="mb-4 text-lg font-semibold">PII Entity Configuration</Text>
<div className="border rounded-lg overflow-hidden shadow-sm">
<div className="border rounded-lg overflow-hidden shadow-xs">
<div className="bg-gray-50 px-5 py-3 border-b flex">
<Text className="flex-1 font-semibold text-gray-700">Entity Type</Text>
<Text className="flex-1 font-semibold text-gray-700">Configuration</Text>

View file

@ -147,7 +147,7 @@ const GuardrailOptionalParams: React.FC<GuardrailOptionalParamsProps> = ({
}
return (
<div key={fullFieldKey} className="mb-8 p-6 bg-white rounded-lg border border-gray-200 shadow-sm">
<div key={fullFieldKey} className="mb-8 p-6 bg-white rounded-lg border border-gray-200 shadow-xs">
<Form.Item
name={[parentFieldKey, fieldKey]}
label={

View file

@ -71,7 +71,7 @@ export interface QuickActionsProps {
export const QuickActions: React.FC<QuickActionsProps> = ({ onSelectAll, onUnselectAll, hasSelectedEntities }) => {
return (
<div className="bg-gray-50 p-5 rounded-lg mb-6 border border-gray-200 shadow-sm">
<div className="bg-gray-50 p-5 rounded-lg mb-6 border border-gray-200 shadow-xs">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<Text strong className="text-gray-700 text-base">
@ -138,7 +138,7 @@ export const PiiEntityList: React.FC<PiiEntityListProps> = ({
entityToCategoryMap,
}) => {
return (
<div className="border rounded-lg overflow-hidden shadow-sm">
<div className="border rounded-lg overflow-hidden shadow-xs">
<div className="bg-gray-50 px-5 py-3 border-b flex">
<Text strong className="flex-1 text-gray-700">
PII Type

View file

@ -57,7 +57,7 @@ const PiiConfiguration: React.FC<PiiConfigurationProps> = ({
<div className="pii-configuration">
<div className="flex justify-between items-center mb-5">
<div className="flex items-center">
<Title level={4} className="!m-0 font-semibold text-gray-800">
<Title level={4} className="m-0! font-semibold text-gray-800">
Configure PII Protection
</Title>
</div>

View file

@ -186,7 +186,7 @@ const ToolPermissionRulesEditor: React.FC<ToolPermissionRulesEditorProps> = ({ v
icon={<PlusOutlined />}
type="primary"
onClick={addRule}
className="!bg-blue-600 !text-white hover:!bg-blue-500"
className="bg-blue-600! text-white! hover:bg-blue-500!"
>
Add Rule
</Button>

View file

@ -102,11 +102,11 @@ export const ByokCredentialModal: React.FC<ByokCredentialModalProps> = ({
<div className="text-center">
{/* Logos */}
<div className="flex items-center justify-center gap-3 mb-6">
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-teal-400 to-cyan-600 flex items-center justify-center text-white font-bold text-xl shadow">
<div className="w-14 h-14 rounded-xl bg-linear-to-br from-teal-400 to-cyan-600 flex items-center justify-center text-white font-bold text-xl shadow-sm">
L
</div>
<ArrowRightOutlined className="text-gray-400 text-lg" />
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-blue-600 to-indigo-800 flex items-center justify-center text-white font-bold text-xl shadow">
<div className="w-14 h-14 rounded-xl bg-linear-to-br from-blue-600 to-indigo-800 flex items-center justify-center text-white font-bold text-xl shadow-sm">
{firstLetter}
</div>
</div>
@ -146,7 +146,7 @@ export const ByokCredentialModal: React.FC<ByokCredentialModalProps> = ({
<ul className="space-y-2">
{server.byok_description.map((item, i) => (
<li key={i} className="flex items-center gap-2 text-sm text-gray-700">
<CheckOutlined className="text-green-500 flex-shrink-0" />
<CheckOutlined className="text-green-500 shrink-0" />
{item}
</li>
))}
@ -211,7 +211,7 @@ export const ByokCredentialModal: React.FC<ByokCredentialModalProps> = ({
{/* Security note */}
<div className="bg-blue-50 rounded-xl p-4 flex items-start gap-3 mb-6">
<LockOutlined className="text-blue-400 mt-0.5 flex-shrink-0" />
<LockOutlined className="text-blue-400 mt-0.5 shrink-0" />
<p className="text-sm text-blue-700">
Your key is stored securely and transmitted over HTTPS. It is never shared with third parties.
</p>

Some files were not shown because too many files have changed in this diff Show more