From e69678a9b3f1f8399a94ff69f0564e429b1d3c68 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 23 Nov 2024 16:25:00 -0800 Subject: [PATCH 01/16] update doc title --- docs/my-website/sidebars.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 0821ac257..cdcf3ba1b 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -266,10 +266,10 @@ const sidebars = { }, { type: "category", - label: "Load Balancing & Routing", + label: "Routing, Loadbalancing & Fallbacks", link: { type: "generated-index", - title: "Load Balancing & Routing", + title: "Routing, Loadbalancing & Fallbacks", description: "Learn how to load balance, route, and set fallbacks for your LLM requests", slug: "/routing-load-balancing", }, From 34bfebe47033c2db5cab24edba65553f41d63209 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 23 Nov 2024 16:59:46 -0800 Subject: [PATCH 02/16] (QOL improvement) Provider budget routing - allow using 1s, 1d, 1mo, 2mo etc (#6885) * use 1 file for duration_in_seconds * add to readme.md * re use duration_in_seconds * fix importing _extract_from_regex, get_last_day_of_month * fix import * update provider budget routing * fix - remove dup test --- .../docs/proxy/provider_budget_routing.md | 24 ++++- litellm/litellm_core_utils/README.md | 1 + litellm/litellm_core_utils/duration_parser.py | 92 +++++++++++++++++++ .../internal_user_endpoints.py | 8 +- .../key_management_endpoints.py | 16 ++-- .../team_callback_endpoints.py | 2 +- .../management_endpoints/team_endpoints.py | 22 ++--- litellm/proxy/proxy_server.py | 2 +- litellm/proxy/utils.py | 91 ++---------------- litellm/router_strategy/provider_budgets.py | 12 +-- .../test_router_provider_budgets.py | 17 ---- tests/local_testing/test_utils.py | 4 +- 12 files changed, 149 insertions(+), 142 deletions(-) create mode 100644 litellm/litellm_core_utils/duration_parser.py diff --git a/docs/my-website/docs/proxy/provider_budget_routing.md b/docs/my-website/docs/proxy/provider_budget_routing.md index fea3f483c..293f9e9d8 100644 --- a/docs/my-website/docs/proxy/provider_budget_routing.md +++ b/docs/my-website/docs/proxy/provider_budget_routing.md @@ -22,14 +22,14 @@ router_settings: provider_budget_config: openai: budget_limit: 0.000000000001 # float of $ value budget for time period - time_period: 1d # can be 1d, 2d, 30d + time_period: 1d # can be 1d, 2d, 30d, 1mo, 2mo azure: budget_limit: 100 time_period: 1d anthropic: budget_limit: 100 time_period: 10d - vertexai: + vertex_ai: budget_limit: 100 time_period: 12d gemini: @@ -112,8 +112,11 @@ Expected response on failure - If all providers exceed budget, raises an error 3. **Supported Time Periods**: - - Format: "Xd" where X is number of days - - Examples: "1d" (1 day), "30d" (30 days) + - Seconds: "Xs" (e.g., "30s") + - Minutes: "Xm" (e.g., "10m") + - Hours: "Xh" (e.g., "24h") + - Days: "Xd" (e.g., "1d", "30d") + - Months: "Xmo" (e.g., "1mo", "2mo") 4. **Requirements**: - Redis required for tracking spend across instances @@ -136,7 +139,12 @@ The `provider_budget_config` is a dictionary where: - **Key**: Provider name (string) - Must be a valid [LiteLLM provider name](https://docs.litellm.ai/docs/providers) - **Value**: Budget configuration object with the following parameters: - `budget_limit`: Float value representing the budget in USD - - `time_period`: String in the format "Xd" where X is the number of days (e.g., "1d", "30d") + - `time_period`: Duration string in one of the following formats: + - Seconds: `"Xs"` (e.g., "30s") + - Minutes: `"Xm"` (e.g., "10m") + - Hours: `"Xh"` (e.g., "24h") + - Days: `"Xd"` (e.g., "1d", "30d") + - Months: `"Xmo"` (e.g., "1mo", "2mo") Example structure: ```yaml @@ -147,4 +155,10 @@ provider_budget_config: azure: budget_limit: 500.0 # $500 USD time_period: "30d" # 30 day period + anthropic: + budget_limit: 200.0 # $200 USD + time_period: "1mo" # 1 month period + gemini: + budget_limit: 50.0 # $50 USD + time_period: "24h" # 24 hour period ``` \ No newline at end of file diff --git a/litellm/litellm_core_utils/README.md b/litellm/litellm_core_utils/README.md index 9cd351453..649404129 100644 --- a/litellm/litellm_core_utils/README.md +++ b/litellm/litellm_core_utils/README.md @@ -8,4 +8,5 @@ Core files: - `exception_mapping_utils.py`: utils for mapping exceptions to openai-compatible error types. - `default_encoding.py`: code for loading the default encoding (tiktoken) - `get_llm_provider_logic.py`: code for inferring the LLM provider from a given model name. +- `duration_parser.py`: code for parsing durations - e.g. "1d", "1mo", "10s" diff --git a/litellm/litellm_core_utils/duration_parser.py b/litellm/litellm_core_utils/duration_parser.py new file mode 100644 index 000000000..c8c6bea83 --- /dev/null +++ b/litellm/litellm_core_utils/duration_parser.py @@ -0,0 +1,92 @@ +""" +Helper utilities for parsing durations - 1s, 1d, 10d, 30d, 1mo, 2mo + +duration_in_seconds is used in diff parts of the code base, example +- Router - Provider budget routing +- Proxy - Key, Team Generation +""" + +import re +import time +from datetime import datetime, timedelta +from typing import Tuple + + +def _extract_from_regex(duration: str) -> Tuple[int, str]: + match = re.match(r"(\d+)(mo|[smhd]?)", duration) + + if not match: + raise ValueError("Invalid duration format") + + value, unit = match.groups() + value = int(value) + + return value, unit + + +def get_last_day_of_month(year, month): + # Handle December case + if month == 12: + return 31 + # Next month is January, so subtract a day from March 1st + next_month = datetime(year=year, month=month + 1, day=1) + last_day_of_month = (next_month - timedelta(days=1)).day + return last_day_of_month + + +def duration_in_seconds(duration: str) -> int: + """ + Parameters: + - duration: + - "s" - seconds + - "m" - minutes + - "h" - hours + - "d" - days + - "mo" - months + + Returns time in seconds till when budget needs to be reset + """ + value, unit = _extract_from_regex(duration=duration) + + if unit == "s": + return value + elif unit == "m": + return value * 60 + elif unit == "h": + return value * 3600 + elif unit == "d": + return value * 86400 + elif unit == "mo": + now = time.time() + current_time = datetime.fromtimestamp(now) + + if current_time.month == 12: + target_year = current_time.year + 1 + target_month = 1 + else: + target_year = current_time.year + target_month = current_time.month + value + + # Determine the day to set for next month + target_day = current_time.day + last_day_of_target_month = get_last_day_of_month(target_year, target_month) + + if target_day > last_day_of_target_month: + target_day = last_day_of_target_month + + next_month = datetime( + year=target_year, + month=target_month, + day=target_day, + hour=current_time.hour, + minute=current_time.minute, + second=current_time.second, + microsecond=current_time.microsecond, + ) + + # Calculate the duration until the first day of the next month + duration_until_next_month = next_month - current_time + return int(duration_until_next_month.total_seconds()) + + else: + raise ValueError(f"Unsupported duration unit, passed duration: {duration}") diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index c69e255f2..c41975f50 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -30,7 +30,7 @@ from litellm._logging import verbose_proxy_logger from litellm.proxy._types import * from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.management_endpoints.key_management_endpoints import ( - _duration_in_seconds, + duration_in_seconds, generate_key_helper_fn, ) from litellm.proxy.management_helpers.utils import ( @@ -516,7 +516,7 @@ async def user_update( is_internal_user = True if "budget_duration" in non_default_values: - duration_s = _duration_in_seconds( + duration_s = duration_in_seconds( duration=non_default_values["budget_duration"] ) user_reset_at = datetime.now(timezone.utc) + timedelta(seconds=duration_s) @@ -535,7 +535,7 @@ async def user_update( non_default_values["budget_duration"] = ( litellm.internal_user_budget_duration ) - duration_s = _duration_in_seconds( + duration_s = duration_in_seconds( duration=non_default_values["budget_duration"] ) user_reset_at = datetime.now(timezone.utc) + timedelta( @@ -725,8 +725,8 @@ async def delete_user( - user_ids: List[str] - The list of user id's to be deleted. """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, user_api_key_cache, diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 511e5a940..8353b5d91 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -34,8 +34,8 @@ from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.hooks.key_management_event_hooks import KeyManagementEventHooks from litellm.proxy.management_helpers.utils import management_endpoint_wrapper from litellm.proxy.utils import ( - _duration_in_seconds, _hash_token_if_needed, + duration_in_seconds, handle_exception_on_proxy, ) from litellm.secret_managers.main import get_secret @@ -321,10 +321,10 @@ async def generate_key_fn( # noqa: PLR0915 ) # Compare durations elif key in ["budget_duration", "duration"]: - upperbound_duration = _duration_in_seconds( + upperbound_duration = duration_in_seconds( duration=upperbound_value ) - user_duration = _duration_in_seconds(duration=value) + user_duration = duration_in_seconds(duration=value) if user_duration > upperbound_duration: raise HTTPException( status_code=400, @@ -421,7 +421,7 @@ def prepare_key_update_data( if "duration" in non_default_values: duration = non_default_values.pop("duration") if duration and (isinstance(duration, str)) and len(duration) > 0: - duration_s = _duration_in_seconds(duration=duration) + duration_s = duration_in_seconds(duration=duration) expires = datetime.now(timezone.utc) + timedelta(seconds=duration_s) non_default_values["expires"] = expires @@ -432,7 +432,7 @@ def prepare_key_update_data( and (isinstance(budget_duration, str)) and len(budget_duration) > 0 ): - duration_s = _duration_in_seconds(duration=budget_duration) + duration_s = duration_in_seconds(duration=budget_duration) key_reset_at = datetime.now(timezone.utc) + timedelta(seconds=duration_s) non_default_values["budget_reset_at"] = key_reset_at @@ -932,19 +932,19 @@ async def generate_key_helper_fn( # noqa: PLR0915 if duration is None: # allow tokens that never expire expires = None else: - duration_s = _duration_in_seconds(duration=duration) + duration_s = duration_in_seconds(duration=duration) expires = datetime.now(timezone.utc) + timedelta(seconds=duration_s) if key_budget_duration is None: # one-time budget key_reset_at = None else: - duration_s = _duration_in_seconds(duration=key_budget_duration) + duration_s = duration_in_seconds(duration=key_budget_duration) key_reset_at = datetime.now(timezone.utc) + timedelta(seconds=duration_s) if budget_duration is None: # one-time budget reset_at = None else: - duration_s = _duration_in_seconds(duration=budget_duration) + duration_s = duration_in_seconds(duration=budget_duration) reset_at = datetime.now(timezone.utc) + timedelta(seconds=duration_s) aliases_json = json.dumps(aliases) diff --git a/litellm/proxy/management_endpoints/team_callback_endpoints.py b/litellm/proxy/management_endpoints/team_callback_endpoints.py index b60ea3acf..6c5fa80a2 100644 --- a/litellm/proxy/management_endpoints/team_callback_endpoints.py +++ b/litellm/proxy/management_endpoints/team_callback_endpoints.py @@ -90,8 +90,8 @@ async def add_team_callbacks( """ try: from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index dc1ec444d..575e71119 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -169,8 +169,8 @@ async def new_team( # noqa: PLR0915 ``` """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) @@ -289,7 +289,7 @@ async def new_team( # noqa: PLR0915 # If budget_duration is set, set `budget_reset_at` if complete_team_data.budget_duration is not None: - duration_s = _duration_in_seconds(duration=complete_team_data.budget_duration) + duration_s = duration_in_seconds(duration=complete_team_data.budget_duration) reset_at = datetime.now(timezone.utc) + timedelta(seconds=duration_s) complete_team_data.budget_reset_at = reset_at @@ -396,8 +396,8 @@ async def update_team( """ from litellm.proxy.auth.auth_checks import _cache_team_object from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, proxy_logging_obj, @@ -425,7 +425,7 @@ async def update_team( # Check budget_duration and budget_reset_at if data.budget_duration is not None: - duration_s = _duration_in_seconds(duration=data.budget_duration) + duration_s = duration_in_seconds(duration=data.budget_duration) reset_at = datetime.now(timezone.utc) + timedelta(seconds=duration_s) # set the budget_reset_at in DB @@ -709,8 +709,8 @@ async def team_member_delete( ``` """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) @@ -829,8 +829,8 @@ async def team_member_update( Update team member budgets """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) @@ -965,8 +965,8 @@ async def delete_team( ``` """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) @@ -1054,8 +1054,8 @@ async def team_info( ``` """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) @@ -1203,8 +1203,8 @@ async def block_team( """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) @@ -1251,8 +1251,8 @@ async def unblock_team( ``` """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) @@ -1294,8 +1294,8 @@ async def list_team( - user_id: str - Optional. If passed will only return teams that the user_id is a member of. """ from litellm.proxy.proxy_server import ( - _duration_in_seconds, create_audit_log_for_update, + duration_in_seconds, litellm_proxy_admin_name, prisma_client, ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 70bf5b523..4a867f46a 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -182,8 +182,8 @@ from litellm.proxy.management_endpoints.internal_user_endpoints import ( ) from litellm.proxy.management_endpoints.internal_user_endpoints import user_update from litellm.proxy.management_endpoints.key_management_endpoints import ( - _duration_in_seconds, delete_verification_token, + duration_in_seconds, generate_key_helper_fn, ) from litellm.proxy.management_endpoints.key_management_endpoints import ( diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 0f7d6c3e0..2a298af21 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -26,6 +26,11 @@ from typing import ( overload, ) +from litellm.litellm_core_utils.duration_parser import ( + _extract_from_regex, + duration_in_seconds, + get_last_day_of_month, +) from litellm.proxy._types import ProxyErrorTypes, ProxyException try: @@ -2429,86 +2434,6 @@ def _hash_token_if_needed(token: str) -> str: return token -def _extract_from_regex(duration: str) -> Tuple[int, str]: - match = re.match(r"(\d+)(mo|[smhd]?)", duration) - - if not match: - raise ValueError("Invalid duration format") - - value, unit = match.groups() - value = int(value) - - return value, unit - - -def get_last_day_of_month(year, month): - # Handle December case - if month == 12: - return 31 - # Next month is January, so subtract a day from March 1st - next_month = datetime(year=year, month=month + 1, day=1) - last_day_of_month = (next_month - timedelta(days=1)).day - return last_day_of_month - - -def _duration_in_seconds(duration: str) -> int: - """ - Parameters: - - duration: - - "s" - seconds - - "m" - minutes - - "h" - hours - - "d" - days - - "mo" - months - - Returns time in seconds till when budget needs to be reset - """ - value, unit = _extract_from_regex(duration=duration) - - if unit == "s": - return value - elif unit == "m": - return value * 60 - elif unit == "h": - return value * 3600 - elif unit == "d": - return value * 86400 - elif unit == "mo": - now = time.time() - current_time = datetime.fromtimestamp(now) - - if current_time.month == 12: - target_year = current_time.year + 1 - target_month = 1 - else: - target_year = current_time.year - target_month = current_time.month + value - - # Determine the day to set for next month - target_day = current_time.day - last_day_of_target_month = get_last_day_of_month(target_year, target_month) - - if target_day > last_day_of_target_month: - target_day = last_day_of_target_month - - next_month = datetime( - year=target_year, - month=target_month, - day=target_day, - hour=current_time.hour, - minute=current_time.minute, - second=current_time.second, - microsecond=current_time.microsecond, - ) - - # Calculate the duration until the first day of the next month - duration_until_next_month = next_month - current_time - return int(duration_until_next_month.total_seconds()) - - else: - raise ValueError("Unsupported duration unit") - - async def reset_budget(prisma_client: PrismaClient): """ Gets all the non-expired keys for a db, which need spend to be reset @@ -2527,7 +2452,7 @@ async def reset_budget(prisma_client: PrismaClient): if keys_to_reset is not None and len(keys_to_reset) > 0: for key in keys_to_reset: key.spend = 0.0 - duration_s = _duration_in_seconds(duration=key.budget_duration) + duration_s = duration_in_seconds(duration=key.budget_duration) key.budget_reset_at = now + timedelta(seconds=duration_s) await prisma_client.update_data( @@ -2543,7 +2468,7 @@ async def reset_budget(prisma_client: PrismaClient): if users_to_reset is not None and len(users_to_reset) > 0: for user in users_to_reset: user.spend = 0.0 - duration_s = _duration_in_seconds(duration=user.budget_duration) + duration_s = duration_in_seconds(duration=user.budget_duration) user.budget_reset_at = now + timedelta(seconds=duration_s) await prisma_client.update_data( @@ -2561,7 +2486,7 @@ async def reset_budget(prisma_client: PrismaClient): if teams_to_reset is not None and len(teams_to_reset) > 0: team_reset_requests = [] for team in teams_to_reset: - duration_s = _duration_in_seconds(duration=team.budget_duration) + duration_s = duration_in_seconds(duration=team.budget_duration) reset_team_budget_request = ResetTeamBudgetRequest( team_id=team.team_id, spend=0.0, diff --git a/litellm/router_strategy/provider_budgets.py b/litellm/router_strategy/provider_budgets.py index 23d8b6c39..ea26d2c0f 100644 --- a/litellm/router_strategy/provider_budgets.py +++ b/litellm/router_strategy/provider_budgets.py @@ -25,6 +25,7 @@ from litellm._logging import verbose_router_logger from litellm.caching.caching import DualCache from litellm.integrations.custom_logger import CustomLogger from litellm.litellm_core_utils.core_helpers import _get_parent_otel_span_from_kwargs +from litellm.litellm_core_utils.duration_parser import duration_in_seconds from litellm.router_utils.cooldown_callbacks import ( _get_prometheus_logger_from_callbacks, ) @@ -207,7 +208,7 @@ class ProviderBudgetLimiting(CustomLogger): ) spend_key = f"provider_spend:{custom_llm_provider}:{budget_config.time_period}" - ttl_seconds = self.get_ttl_seconds(budget_config.time_period) + ttl_seconds = duration_in_seconds(duration=budget_config.time_period) verbose_router_logger.debug( f"Incrementing spend for {spend_key} by {response_cost}, ttl: {ttl_seconds}" ) @@ -242,15 +243,6 @@ class ProviderBudgetLimiting(CustomLogger): return None return custom_llm_provider - def get_ttl_seconds(self, time_period: str) -> int: - """ - Convert time period (e.g., '1d', '30d') to seconds for Redis TTL - """ - if time_period.endswith("d"): - days = int(time_period[:-1]) - return days * 24 * 60 * 60 - raise ValueError(f"Unsupported time period format: {time_period}") - def _track_provider_remaining_budget_prometheus( self, provider: str, spend: float, budget_limit: float ): diff --git a/tests/local_testing/test_router_provider_budgets.py b/tests/local_testing/test_router_provider_budgets.py index 46b9ee29e..a6574ba4b 100644 --- a/tests/local_testing/test_router_provider_budgets.py +++ b/tests/local_testing/test_router_provider_budgets.py @@ -142,23 +142,6 @@ async def test_provider_budgets_e2e_test_expect_to_fail(): assert "Exceeded budget for provider" in str(exc_info.value) -def test_get_ttl_seconds(): - """ - Test the get_ttl_seconds helper method" - - """ - provider_budget = ProviderBudgetLimiting( - router_cache=DualCache(), provider_budget_config={} - ) - - assert provider_budget.get_ttl_seconds("1d") == 86400 # 1 day in seconds - assert provider_budget.get_ttl_seconds("7d") == 604800 # 7 days in seconds - assert provider_budget.get_ttl_seconds("30d") == 2592000 # 30 days in seconds - - with pytest.raises(ValueError, match="Unsupported time period format"): - provider_budget.get_ttl_seconds("1h") - - def test_get_llm_provider_for_deployment(): """ Test the _get_llm_provider_for_deployment helper method diff --git a/tests/local_testing/test_utils.py b/tests/local_testing/test_utils.py index cf1db27e8..7c349a658 100644 --- a/tests/local_testing/test_utils.py +++ b/tests/local_testing/test_utils.py @@ -17,7 +17,7 @@ import pytest import litellm from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, headers from litellm.proxy.utils import ( - _duration_in_seconds, + duration_in_seconds, _extract_from_regex, get_last_day_of_month, ) @@ -593,7 +593,7 @@ def test_duration_in_seconds(): duration_until_next_month = next_month - current_time expected_duration = int(duration_until_next_month.total_seconds()) - value = _duration_in_seconds(duration="1mo") + value = duration_in_seconds(duration="1mo") assert value - expected_duration < 2 From c73ce95c01d33eeebe8adb4df2b899053261e405 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sun, 24 Nov 2024 16:36:19 -0800 Subject: [PATCH 03/16] (feat) - provider budget improvements - ensure provider budgets work with multiple proxy instances + improve latency to ~90ms (#6886) * use 1 file for duration_in_seconds * add to readme.md * re use duration_in_seconds * fix importing _extract_from_regex, get_last_day_of_month * fix import * update provider budget routing * fix - remove dup test * add support for using in multi instance environments * test_in_memory_redis_sync_e2e * test_in_memory_redis_sync_e2e * fix test_in_memory_redis_sync_e2e * fix code quality check * fix test provider budgets * working provider budget tests * add fixture for provider budget routing * fix router testing for provider budgets * add comments on provider budget routing * use RedisPipelineIncrementOperation * add redis async_increment_pipeline * use redis async_increment_pipeline * use lower value for testing * use redis async_increment_pipeline * use consistent key name for increment op * add handling for budget windows * fix typing async_increment_pipeline * fix set attr * add clear doc strings * unit testing for provider budgets * test_redis_increment_pipeline --- .../docs/proxy/provider_budget_routing.md | 63 +++-- litellm/caching/redis_cache.py | 90 +++++++ litellm/proxy/proxy_config.yaml | 23 +- litellm/router_strategy/provider_budgets.py | 236 ++++++++++++++++-- litellm/types/caching.py | 12 +- tests/local_testing/test_caching.py | 45 ++++ .../test_router_provider_budgets.py | 221 +++++++++++++++- 7 files changed, 638 insertions(+), 52 deletions(-) diff --git a/docs/my-website/docs/proxy/provider_budget_routing.md b/docs/my-website/docs/proxy/provider_budget_routing.md index 293f9e9d8..1cb75d667 100644 --- a/docs/my-website/docs/proxy/provider_budget_routing.md +++ b/docs/my-website/docs/proxy/provider_budget_routing.md @@ -16,25 +16,27 @@ model_list: api_key: os.environ/OPENAI_API_KEY router_settings: - redis_host: - redis_password: - redis_port: provider_budget_config: - openai: - budget_limit: 0.000000000001 # float of $ value budget for time period - time_period: 1d # can be 1d, 2d, 30d, 1mo, 2mo - azure: - budget_limit: 100 - time_period: 1d - anthropic: - budget_limit: 100 - time_period: 10d - vertex_ai: - budget_limit: 100 - time_period: 12d - gemini: - budget_limit: 100 - time_period: 12d + openai: + budget_limit: 0.000000000001 # float of $ value budget for time period + time_period: 1d # can be 1d, 2d, 30d, 1mo, 2mo + azure: + budget_limit: 100 + time_period: 1d + anthropic: + budget_limit: 100 + time_period: 10d + vertex_ai: + budget_limit: 100 + time_period: 12d + gemini: + budget_limit: 100 + time_period: 12d + + # OPTIONAL: Set Redis Host, Port, and Password if using multiple instance of LiteLLM + redis_host: os.environ/REDIS_HOST + redis_port: os.environ/REDIS_PORT + redis_password: os.environ/REDIS_PASSWORD general_settings: master_key: sk-1234 @@ -132,6 +134,31 @@ This metric indicates the remaining budget for a provider in dollars (USD) litellm_provider_remaining_budget_metric{api_provider="openai"} 10 ``` +## Multi-instance setup + +If you are using a multi-instance setup, you will need to set the Redis host, port, and password in the `proxy_config.yaml` file. Redis is used to sync the spend across LiteLLM instances. + +```yaml +model_list: + - model_name: gpt-3.5-turbo + litellm_params: + model: openai/gpt-3.5-turbo + api_key: os.environ/OPENAI_API_KEY + +router_settings: + provider_budget_config: + openai: + budget_limit: 0.000000000001 # float of $ value budget for time period + time_period: 1d # can be 1d, 2d, 30d, 1mo, 2mo + + # 👇 Add this: Set Redis Host, Port, and Password if using multiple instance of LiteLLM + redis_host: os.environ/REDIS_HOST + redis_port: os.environ/REDIS_PORT + redis_password: os.environ/REDIS_PASSWORD + +general_settings: + master_key: sk-1234 +``` ## Spec for provider_budget_config diff --git a/litellm/caching/redis_cache.py b/litellm/caching/redis_cache.py index e15a3f83d..ba5c3a695 100644 --- a/litellm/caching/redis_cache.py +++ b/litellm/caching/redis_cache.py @@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Any, List, Optional, Tuple import litellm from litellm._logging import print_verbose, verbose_logger from litellm.litellm_core_utils.core_helpers import _get_parent_otel_span_from_kwargs +from litellm.types.caching import RedisPipelineIncrementOperation from litellm.types.services import ServiceLoggerPayload, ServiceTypes from litellm.types.utils import all_litellm_params @@ -890,3 +891,92 @@ class RedisCache(BaseCache): def delete_cache(self, key): self.redis_client.delete(key) + + async def _pipeline_increment_helper( + self, + pipe: pipeline, + increment_list: List[RedisPipelineIncrementOperation], + ) -> Optional[List[float]]: + """Helper function for pipeline increment operations""" + # Iterate through each increment operation and add commands to pipeline + for increment_op in increment_list: + cache_key = self.check_and_fix_namespace(key=increment_op["key"]) + print_verbose( + f"Increment ASYNC Redis Cache PIPELINE: key: {cache_key}\nValue {increment_op['increment_value']}\nttl={increment_op['ttl']}" + ) + pipe.incrbyfloat(cache_key, increment_op["increment_value"]) + if increment_op["ttl"] is not None: + _td = timedelta(seconds=increment_op["ttl"]) + pipe.expire(cache_key, _td) + # Execute the pipeline and return results + results = await pipe.execute() + print_verbose(f"Increment ASYNC Redis Cache PIPELINE: results: {results}") + return results + + async def async_increment_pipeline( + self, increment_list: List[RedisPipelineIncrementOperation], **kwargs + ) -> Optional[List[float]]: + """ + Use Redis Pipelines for bulk increment operations + Args: + increment_list: List of RedisPipelineIncrementOperation dicts containing: + - key: str + - increment_value: float + - ttl_seconds: int + """ + # don't waste a network request if there's nothing to increment + if len(increment_list) == 0: + return None + + from redis.asyncio import Redis + + _redis_client: Redis = self.init_async_client() # type: ignore + start_time = time.time() + + print_verbose( + f"Increment Async Redis Cache Pipeline: increment list: {increment_list}" + ) + + try: + async with _redis_client as redis_client: + async with redis_client.pipeline(transaction=True) as pipe: + results = await self._pipeline_increment_helper( + pipe, increment_list + ) + + print_verbose(f"pipeline increment results: {results}") + + ## LOGGING ## + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_success_hook( + service=ServiceTypes.REDIS, + duration=_duration, + call_type="async_increment_pipeline", + start_time=start_time, + end_time=end_time, + parent_otel_span=_get_parent_otel_span_from_kwargs(kwargs), + ) + ) + return results + except Exception as e: + ## LOGGING ## + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_failure_hook( + service=ServiceTypes.REDIS, + duration=_duration, + error=e, + call_type="async_increment_pipeline", + start_time=start_time, + end_time=end_time, + parent_otel_span=_get_parent_otel_span_from_kwargs(kwargs), + ) + ) + verbose_logger.error( + "LiteLLM Redis Caching: async increment_pipeline() - Got exception from REDIS %s", + str(e), + ) + raise e diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 956a17a75..13fb1bcbe 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -2,8 +2,23 @@ model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o - api_key: os.environ/OPENAI_API_KEY + api_base: https://exampleopenaiendpoint-production.up.railway.app/ + - model_name: fake-anthropic-endpoint + litellm_params: + model: anthropic/fake + api_base: https://exampleanthropicendpoint-production.up.railway.app/ -default_vertex_config: - vertex_project: "adroit-crow-413218" - vertex_location: "us-central1" +router_settings: + provider_budget_config: + openai: + budget_limit: 0.3 # float of $ value budget for time period + time_period: 1d # can be 1d, 2d, 30d + anthropic: + budget_limit: 5 + time_period: 1d + redis_host: os.environ/REDIS_HOST + redis_port: os.environ/REDIS_PORT + redis_password: os.environ/REDIS_PASSWORD + +litellm_settings: + callbacks: ["prometheus"] \ No newline at end of file diff --git a/litellm/router_strategy/provider_budgets.py b/litellm/router_strategy/provider_budgets.py index ea26d2c0f..f4dc1ba94 100644 --- a/litellm/router_strategy/provider_budgets.py +++ b/litellm/router_strategy/provider_budgets.py @@ -18,11 +18,14 @@ anthropic: ``` """ +import asyncio +from datetime import datetime, timezone from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypedDict, Union import litellm from litellm._logging import verbose_router_logger from litellm.caching.caching import DualCache +from litellm.caching.redis_cache import RedisPipelineIncrementOperation from litellm.integrations.custom_logger import CustomLogger from litellm.litellm_core_utils.core_helpers import _get_parent_otel_span_from_kwargs from litellm.litellm_core_utils.duration_parser import duration_in_seconds @@ -44,10 +47,14 @@ if TYPE_CHECKING: else: Span = Any +DEFAULT_REDIS_SYNC_INTERVAL = 1 + class ProviderBudgetLimiting(CustomLogger): def __init__(self, router_cache: DualCache, provider_budget_config: dict): self.router_cache = router_cache + self.redis_increment_operation_queue: List[RedisPipelineIncrementOperation] = [] + asyncio.create_task(self.periodic_sync_in_memory_spend_with_redis()) # cast elements of provider_budget_config to ProviderBudgetInfo for provider, config in provider_budget_config.items(): @@ -173,19 +180,76 @@ class ProviderBudgetLimiting(CustomLogger): return potential_deployments + async def _get_or_set_budget_start_time( + self, start_time_key: str, current_time: float, ttl_seconds: int + ) -> float: + """ + Checks if the key = `provider_budget_start_time:{provider}` exists in cache. + + If it does, return the value. + If it does not, set the key to `current_time` and return the value. + """ + budget_start = await self.router_cache.async_get_cache(start_time_key) + if budget_start is None: + await self.router_cache.async_set_cache( + key=start_time_key, value=current_time, ttl=ttl_seconds + ) + return current_time + return float(budget_start) + + async def _handle_new_budget_window( + self, + spend_key: str, + start_time_key: str, + current_time: float, + response_cost: float, + ttl_seconds: int, + ) -> float: + """ + Handle start of new budget window by resetting spend and start time + + Enters this when: + - The budget does not exist in cache, so we need to set it + - The budget window has expired, so we need to reset everything + + Does 2 things: + - stores key: `provider_spend:{provider}:1d`, value: response_cost + - stores key: `provider_budget_start_time:{provider}`, value: current_time. + This stores the start time of the new budget window + """ + await self.router_cache.async_set_cache( + key=spend_key, value=response_cost, ttl=ttl_seconds + ) + await self.router_cache.async_set_cache( + key=start_time_key, value=current_time, ttl=ttl_seconds + ) + return current_time + + async def _increment_spend_in_current_window( + self, spend_key: str, response_cost: float, ttl: int + ): + """ + Increment spend within existing budget window + + Runs once the budget start time exists in Redis Cache (on the 2nd and subsequent requests to the same provider) + + - Increments the spend in memory cache (so spend instantly updated in memory) + - Queues the increment operation to Redis Pipeline (using batched pipeline to optimize performance. Using Redis for multi instance environment of LiteLLM) + """ + await self.router_cache.in_memory_cache.async_increment( + key=spend_key, + value=response_cost, + ttl=ttl, + ) + increment_op = RedisPipelineIncrementOperation( + key=spend_key, + increment_value=response_cost, + ttl=ttl, + ) + self.redis_increment_operation_queue.append(increment_op) + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): - """ - Increment provider spend in DualCache (InMemory + Redis) - - Handles saving current provider spend to Redis. - - Spend is stored as: - provider_spend:{provider}:{time_period} - ex. provider_spend:openai:1d - ex. provider_spend:anthropic:7d - - The time period is tracked for time_periods set in the provider budget config. - """ + """Original method now uses helper functions""" verbose_router_logger.debug("in ProviderBudgetLimiting.async_log_success_event") standard_logging_payload: Optional[StandardLoggingPayload] = kwargs.get( "standard_logging_object", None @@ -208,20 +272,146 @@ class ProviderBudgetLimiting(CustomLogger): ) spend_key = f"provider_spend:{custom_llm_provider}:{budget_config.time_period}" - ttl_seconds = duration_in_seconds(duration=budget_config.time_period) + start_time_key = f"provider_budget_start_time:{custom_llm_provider}" + + current_time = datetime.now(timezone.utc).timestamp() + ttl_seconds = duration_in_seconds(budget_config.time_period) + + budget_start = await self._get_or_set_budget_start_time( + start_time_key=start_time_key, + current_time=current_time, + ttl_seconds=ttl_seconds, + ) + + if budget_start is None: + # First spend for this provider + budget_start = await self._handle_new_budget_window( + spend_key=spend_key, + start_time_key=start_time_key, + current_time=current_time, + response_cost=response_cost, + ttl_seconds=ttl_seconds, + ) + elif (current_time - budget_start) > ttl_seconds: + # Budget window expired - reset everything + verbose_router_logger.debug("Budget window expired - resetting everything") + budget_start = await self._handle_new_budget_window( + spend_key=spend_key, + start_time_key=start_time_key, + current_time=current_time, + response_cost=response_cost, + ttl_seconds=ttl_seconds, + ) + else: + # Within existing window - increment spend + remaining_time = ttl_seconds - (current_time - budget_start) + ttl_for_increment = int(remaining_time) + + await self._increment_spend_in_current_window( + spend_key=spend_key, response_cost=response_cost, ttl=ttl_for_increment + ) + verbose_router_logger.debug( - f"Incrementing spend for {spend_key} by {response_cost}, ttl: {ttl_seconds}" - ) - # Increment the spend in Redis and set TTL - await self.router_cache.async_increment_cache( - key=spend_key, - value=response_cost, - ttl=ttl_seconds, - ) - verbose_router_logger.debug( - f"Incremented spend for {spend_key} by {response_cost}, ttl: {ttl_seconds}" + f"Incremented spend for {spend_key} by {response_cost}" ) + async def periodic_sync_in_memory_spend_with_redis(self): + """ + Handler that triggers sync_in_memory_spend_with_redis every DEFAULT_REDIS_SYNC_INTERVAL seconds + + Required for multi-instance environment usage of provider budgets + """ + while True: + try: + await self._sync_in_memory_spend_with_redis() + await asyncio.sleep( + DEFAULT_REDIS_SYNC_INTERVAL + ) # Wait for DEFAULT_REDIS_SYNC_INTERVAL seconds before next sync + except Exception as e: + verbose_router_logger.error(f"Error in periodic sync task: {str(e)}") + await asyncio.sleep( + DEFAULT_REDIS_SYNC_INTERVAL + ) # Still wait DEFAULT_REDIS_SYNC_INTERVAL seconds on error before retrying + + async def _push_in_memory_increments_to_redis(self): + """ + How this works: + - async_log_success_event collects all provider spend increments in `redis_increment_operation_queue` + - This function pushes all increments to Redis in a batched pipeline to optimize performance + + Only runs if Redis is initialized + """ + try: + if not self.router_cache.redis_cache: + return # Redis is not initialized + + verbose_router_logger.debug( + "Pushing Redis Increment Pipeline for queue: %s", + self.redis_increment_operation_queue, + ) + if len(self.redis_increment_operation_queue) > 0: + asyncio.create_task( + self.router_cache.redis_cache.async_increment_pipeline( + increment_list=self.redis_increment_operation_queue, + ) + ) + + self.redis_increment_operation_queue = [] + + except Exception as e: + verbose_router_logger.error( + f"Error syncing in-memory cache with Redis: {str(e)}" + ) + + async def _sync_in_memory_spend_with_redis(self): + """ + Ensures in-memory cache is updated with latest Redis values for all provider spends. + + Why Do we need this? + - Optimization to hit sub 100ms latency. Performance was impacted when redis was used for read/write per request + - Use provider budgets in multi-instance environment, we use Redis to sync spend across all instances + + What this does: + 1. Push all provider spend increments to Redis + 2. Fetch all current provider spend from Redis to update in-memory cache + """ + + try: + # No need to sync if Redis cache is not initialized + if self.router_cache.redis_cache is None: + return + + # 1. Push all provider spend increments to Redis + await self._push_in_memory_increments_to_redis() + + # 2. Fetch all current provider spend from Redis to update in-memory cache + cache_keys = [] + for provider, config in self.provider_budget_config.items(): + if config is None: + continue + cache_keys.append(f"provider_spend:{provider}:{config.time_period}") + + # Batch fetch current spend values from Redis + redis_values = await self.router_cache.redis_cache.async_batch_get_cache( + key_list=cache_keys + ) + + # Update in-memory cache with Redis values + if isinstance(redis_values, dict): # Check if redis_values is a dictionary + for key, value in redis_values.items(): + if value is not None: + await self.router_cache.in_memory_cache.async_set_cache( + key=key, value=float(value) + ) + verbose_router_logger.debug( + f"Updated in-memory cache for {key}: {value}" + ) + + except Exception as e: + verbose_router_logger.error( + f"Error syncing in-memory cache with Redis: {str(e)}" + ) + def _get_budget_config_for_provider( self, provider: str ) -> Optional[ProviderBudgetInfo]: diff --git a/litellm/types/caching.py b/litellm/types/caching.py index 7fca4c041..a6f9de308 100644 --- a/litellm/types/caching.py +++ b/litellm/types/caching.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Literal +from typing import Literal, Optional, TypedDict class LiteLLMCacheType(str, Enum): @@ -23,3 +23,13 @@ CachingSupportedCallTypes = Literal[ "arerank", "rerank", ] + + +class RedisPipelineIncrementOperation(TypedDict): + """ + TypeDict for 1 Redis Pipeline Increment Operation + """ + + key: str + increment_value: float + ttl: Optional[int] diff --git a/tests/local_testing/test_caching.py b/tests/local_testing/test_caching.py index 222013a86..08da89172 100644 --- a/tests/local_testing/test_caching.py +++ b/tests/local_testing/test_caching.py @@ -2433,3 +2433,48 @@ async def test_dual_cache_caching_batch_get_cache(): await dc.async_batch_get_cache(keys=["test_key1", "test_key2"]) assert mock_async_get_cache.call_count == 1 + + +@pytest.mark.asyncio +async def test_redis_increment_pipeline(): + """Test Redis increment pipeline functionality""" + try: + from litellm.caching.redis_cache import RedisCache + + litellm.set_verbose = True + redis_cache = RedisCache( + host=os.environ["REDIS_HOST"], + port=os.environ["REDIS_PORT"], + password=os.environ["REDIS_PASSWORD"], + ) + + # Create test increment operations + increment_list = [ + {"key": "test_key1", "increment_value": 1.5, "ttl": 60}, + {"key": "test_key1", "increment_value": 1.1, "ttl": 58}, + {"key": "test_key1", "increment_value": 0.4, "ttl": 55}, + {"key": "test_key2", "increment_value": 2.5, "ttl": 60}, + ] + + # Test pipeline increment + results = await redis_cache.async_increment_pipeline(increment_list) + + # Verify results + assert len(results) == 8 # 4 increment operations + 4 expire operations + + # Verify the values were actually set in Redis + value1 = await redis_cache.async_get_cache("test_key1") + print("result in cache for key=test_key1", value1) + value2 = await redis_cache.async_get_cache("test_key2") + print("result in cache for key=test_key2", value2) + + assert float(value1) == 3.0 + assert float(value2) == 2.5 + + # Clean up + await redis_cache.async_delete_cache("test_key1") + await redis_cache.async_delete_cache("test_key2") + + except Exception as e: + print(f"Error occurred: {str(e)}") + raise e diff --git a/tests/local_testing/test_router_provider_budgets.py b/tests/local_testing/test_router_provider_budgets.py index a6574ba4b..430550632 100644 --- a/tests/local_testing/test_router_provider_budgets.py +++ b/tests/local_testing/test_router_provider_budgets.py @@ -17,7 +17,7 @@ from litellm.types.router import ( ProviderBudgetConfigType, ProviderBudgetInfo, ) -from litellm.caching.caching import DualCache +from litellm.caching.caching import DualCache, RedisCache import logging from litellm._logging import verbose_router_logger import litellm @@ -25,6 +25,27 @@ import litellm verbose_router_logger.setLevel(logging.DEBUG) +def cleanup_redis(): + """Cleanup Redis cache before each test""" + try: + import redis + + print("cleaning up redis..") + + redis_client = redis.Redis( + host=os.getenv("REDIS_HOST"), + port=int(os.getenv("REDIS_PORT")), + password=os.getenv("REDIS_PASSWORD"), + ) + print("scan iter result", redis_client.scan_iter("provider_spend:*")) + # Delete all provider spend keys + for key in redis_client.scan_iter("provider_spend:*"): + print("deleting key", key) + redis_client.delete(key) + except Exception as e: + print(f"Error cleaning up Redis: {str(e)}") + + @pytest.mark.asyncio async def test_provider_budgets_e2e_test(): """ @@ -34,6 +55,8 @@ async def test_provider_budgets_e2e_test(): - Next 3 requests all go to Azure """ + cleanup_redis() + # Modify for test provider_budget_config: ProviderBudgetConfigType = { "openai": ProviderBudgetInfo(time_period="1d", budget_limit=0.000000000001), "azure": ProviderBudgetInfo(time_period="1d", budget_limit=100), @@ -71,7 +94,7 @@ async def test_provider_budgets_e2e_test(): ) print(response) - await asyncio.sleep(0.5) + await asyncio.sleep(2.5) for _ in range(3): response = await router.acompletion( @@ -94,6 +117,7 @@ async def test_provider_budgets_e2e_test_expect_to_fail(): - first request passes, all subsequent requests fail """ + cleanup_redis() # Note: We intentionally use a dictionary with string keys for budget_limit and time_period # we want to test that the router can handle type conversion, since the proxy config yaml passes these values as a dictionary @@ -125,7 +149,7 @@ async def test_provider_budgets_e2e_test_expect_to_fail(): ) print(response) - await asyncio.sleep(0.5) + await asyncio.sleep(2.5) for _ in range(3): with pytest.raises(Exception) as exc_info: @@ -142,11 +166,13 @@ async def test_provider_budgets_e2e_test_expect_to_fail(): assert "Exceeded budget for provider" in str(exc_info.value) -def test_get_llm_provider_for_deployment(): +@pytest.mark.asyncio +async def test_get_llm_provider_for_deployment(): """ Test the _get_llm_provider_for_deployment helper method """ + cleanup_redis() provider_budget = ProviderBudgetLimiting( router_cache=DualCache(), provider_budget_config={} ) @@ -172,11 +198,13 @@ def test_get_llm_provider_for_deployment(): assert provider_budget._get_llm_provider_for_deployment(unknown_deployment) is None -def test_get_budget_config_for_provider(): +@pytest.mark.asyncio +async def test_get_budget_config_for_provider(): """ Test the _get_budget_config_for_provider helper method """ + cleanup_redis() config = { "openai": ProviderBudgetInfo(time_period="1d", budget_limit=100), "anthropic": ProviderBudgetInfo(time_period="7d", budget_limit=500), @@ -206,6 +234,7 @@ async def test_prometheus_metric_tracking(): """ Test that the Prometheus metric for provider budget is tracked correctly """ + cleanup_redis() from unittest.mock import MagicMock from litellm.integrations.prometheus import PrometheusLogger @@ -263,7 +292,187 @@ async def test_prometheus_metric_tracking(): except Exception as e: print("error", e) - await asyncio.sleep(0.5) + await asyncio.sleep(2.5) # Verify the mock was called correctly mock_prometheus.track_provider_remaining_budget.assert_called_once() + + +@pytest.mark.asyncio +async def test_handle_new_budget_window(): + """ + Test _handle_new_budget_window helper method + + Current + """ + cleanup_redis() + provider_budget = ProviderBudgetLimiting( + router_cache=DualCache(), provider_budget_config={} + ) + + spend_key = "provider_spend:openai:7d" + start_time_key = "provider_budget_start_time:openai" + current_time = 1000.0 + response_cost = 0.5 + ttl_seconds = 86400 # 1 day + + # Test handling new budget window + new_start_time = await provider_budget._handle_new_budget_window( + spend_key=spend_key, + start_time_key=start_time_key, + current_time=current_time, + response_cost=response_cost, + ttl_seconds=ttl_seconds, + ) + + assert new_start_time == current_time + + # Verify the spend was set correctly + spend = await provider_budget.router_cache.async_get_cache(spend_key) + print("spend in cache for key", spend_key, "is", spend) + assert float(spend) == response_cost + + # Verify start time was set correctly + start_time = await provider_budget.router_cache.async_get_cache(start_time_key) + print("start time in cache for key", start_time_key, "is", start_time) + assert float(start_time) == current_time + + +@pytest.mark.asyncio +async def test_get_or_set_budget_start_time(): + """ + Test _get_or_set_budget_start_time helper method + + scenario 1: no existing start time in cache, should return current time + scenario 2: existing start time in cache, should return existing start time + """ + cleanup_redis() + provider_budget = ProviderBudgetLimiting( + router_cache=DualCache(), provider_budget_config={} + ) + + start_time_key = "test_start_time" + current_time = 1000.0 + ttl_seconds = 86400 # 1 day + + # When there is no existing start time, we should set it to the current time + start_time = await provider_budget._get_or_set_budget_start_time( + start_time_key=start_time_key, + current_time=current_time, + ttl_seconds=ttl_seconds, + ) + print("budget start time when no existing start time is in cache", start_time) + assert start_time == current_time + + # When there is an existing start time, we should return it even if the current time is later + new_current_time = 2000.0 + existing_start_time = await provider_budget._get_or_set_budget_start_time( + start_time_key=start_time_key, + current_time=new_current_time, + ttl_seconds=ttl_seconds, + ) + print( + "budget start time when existing start time is in cache, but current time is later", + existing_start_time, + ) + assert existing_start_time == current_time # Should return the original start time + + +@pytest.mark.asyncio +async def test_increment_spend_in_current_window(): + """ + Test _increment_spend_in_current_window helper method + + Expected behavior: + - Increment the spend in memory cache + - Queue the increment operation to Redis + """ + cleanup_redis() + provider_budget = ProviderBudgetLimiting( + router_cache=DualCache(), provider_budget_config={} + ) + + spend_key = "provider_spend:openai:1d" + response_cost = 0.5 + ttl = 86400 # 1 day + + # Set initial spend + await provider_budget.router_cache.async_set_cache( + key=spend_key, value=1.0, ttl=ttl + ) + + # Test incrementing spend + await provider_budget._increment_spend_in_current_window( + spend_key=spend_key, + response_cost=response_cost, + ttl=ttl, + ) + + # Verify the spend was incremented correctly in memory + spend = await provider_budget.router_cache.async_get_cache(spend_key) + assert float(spend) == 1.5 + + # Verify the increment operation was queued for Redis + print( + "redis_increment_operation_queue", + provider_budget.redis_increment_operation_queue, + ) + assert len(provider_budget.redis_increment_operation_queue) == 1 + queued_op = provider_budget.redis_increment_operation_queue[0] + assert queued_op["key"] == spend_key + assert queued_op["increment_value"] == response_cost + assert queued_op["ttl"] == ttl + + +@pytest.mark.asyncio +async def test_sync_in_memory_spend_with_redis(): + """ + Test _sync_in_memory_spend_with_redis helper method + + Expected behavior: + - Push all provider spend increments to Redis + - Fetch all current provider spend from Redis to update in-memory cache + """ + cleanup_redis() + provider_budget_config = { + "openai": ProviderBudgetInfo(time_period="1d", budget_limit=100), + "anthropic": ProviderBudgetInfo(time_period="1d", budget_limit=200), + } + + provider_budget = ProviderBudgetLimiting( + router_cache=DualCache( + redis_cache=RedisCache( + host=os.getenv("REDIS_HOST"), + port=int(os.getenv("REDIS_PORT")), + password=os.getenv("REDIS_PASSWORD"), + ) + ), + provider_budget_config=provider_budget_config, + ) + + # Set some values in Redis + spend_key_openai = "provider_spend:openai:1d" + spend_key_anthropic = "provider_spend:anthropic:1d" + + await provider_budget.router_cache.redis_cache.async_set_cache( + key=spend_key_openai, value=50.0 + ) + await provider_budget.router_cache.redis_cache.async_set_cache( + key=spend_key_anthropic, value=75.0 + ) + + # Test syncing with Redis + await provider_budget._sync_in_memory_spend_with_redis() + + # Verify in-memory cache was updated + openai_spend = await provider_budget.router_cache.in_memory_cache.async_get_cache( + spend_key_openai + ) + anthropic_spend = ( + await provider_budget.router_cache.in_memory_cache.async_get_cache( + spend_key_anthropic + ) + ) + + assert float(openai_spend) == 50.0 + assert float(anthropic_spend) == 75.0 From f77bf497721b0d01885eddd3dd896738e6e7f70d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 25 Nov 2024 12:12:09 -0800 Subject: [PATCH 04/16] feat - allow sending `tags` on vertex pass through requests (#6876) * feat - allow tagging vertex JS SDK request * add unit testing for passing headers for pass through endpoints * fix allow using vertex_ai as the primary way for pass through vertex endpoints * docs on vertex js pass tags * add e2e test for vertex pass through with spend tags * add e2e tests for streaming vertex JS with tags * fix vertex ai testing --- .../my-website/docs/pass_through/vertex_ai.md | 195 ++++++++++++++---- .../pass_through_endpoints.py | 15 ++ .../vertex_ai_endpoints/vertex_endpoints.py | 7 +- tests/pass_through_tests/test_local_vertex.js | 43 +--- tests/pass_through_tests/test_vertex_ai.py | 6 +- .../test_vertex_with_spend.test.js | 194 +++++++++++++++++ .../test_pass_through_unit_tests.py | 165 +++++++++++++++ 7 files changed, 548 insertions(+), 77 deletions(-) create mode 100644 tests/pass_through_tests/test_vertex_with_spend.test.js create mode 100644 tests/pass_through_unit_tests/test_pass_through_unit_tests.py diff --git a/docs/my-website/docs/pass_through/vertex_ai.md b/docs/my-website/docs/pass_through/vertex_ai.md index 03190c839..744c5e3ff 100644 --- a/docs/my-website/docs/pass_through/vertex_ai.md +++ b/docs/my-website/docs/pass_through/vertex_ai.md @@ -4,17 +4,9 @@ import TabItem from '@theme/TabItem'; # Vertex AI SDK -Use VertexAI SDK to call endpoints on LiteLLM Gateway (native provider format) - -:::tip - -Looking for the Unified API (OpenAI format) for VertexAI ? [Go here - using vertexAI with LiteLLM SDK or LiteLLM Proxy Server](../providers/vertex.md) - -::: - Pass-through endpoints for Vertex AI - call provider-specific endpoint, in native format (no translation). -Just replace `https://REGION-aiplatform.googleapis.com` with `LITELLM_PROXY_BASE_URL/vertex-ai` +Just replace `https://REGION-aiplatform.googleapis.com` with `LITELLM_PROXY_BASE_URL/vertex_ai` #### **Example Usage** @@ -23,9 +15,9 @@ Just replace `https://REGION-aiplatform.googleapis.com` with `LITELLM_PROXY_BASE ```bash -curl http://localhost:4000/vertex-ai/publishers/google/models/gemini-1.0-pro:generateContent \ +curl http://localhost:4000/vertex_ai/publishers/google/models/gemini-1.0-pro:generateContent \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-1234" \ + -H "x-litellm-api-key: Bearer sk-1234" \ -d '{ "contents":[{ "role": "user", @@ -43,7 +35,7 @@ const { VertexAI } = require('@google-cloud/vertexai'); const vertexAI = new VertexAI({ project: 'your-project-id', // enter your vertex project id location: 'us-central1', // enter your vertex region - apiEndpoint: "localhost:4000/vertex-ai" // /vertex-ai # note, do not include 'https://' in the url + apiEndpoint: "localhost:4000/vertex_ai" // /vertex_ai # note, do not include 'https://' in the url }); const model = vertexAI.getGenerativeModel({ @@ -87,7 +79,7 @@ generateContent(); - Tuning API - CountTokens API -## Authentication to Vertex AI +#### Authentication to Vertex AI LiteLLM Proxy Server supports two methods of authentication to Vertex AI: @@ -116,9 +108,9 @@ from vertexai.preview.generative_models import GenerativeModel LITE_LLM_ENDPOINT = "http://localhost:4000" vertexai.init( - project="", # enter your project id - location="", # enter your region - api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex-ai", # route on litellm + project="", # enter your project id + location="", # enter your region + api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex_ai", # route on litellm api_transport="rest", ) @@ -158,7 +150,7 @@ from google.auth.credentials import Credentials from vertexai.generative_models import GenerativeModel LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -219,7 +211,7 @@ import vertexai from vertexai.generative_models import GenerativeModel LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" vertexai.init( project="adroit-crow-413218", @@ -247,7 +239,7 @@ from google.auth.credentials import Credentials from vertexai.generative_models import GenerativeModel LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -297,9 +289,9 @@ print(response.text) ```shell -curl http://localhost:4000/vertex-ai/publishers/google/models/gemini-1.5-flash-001:generateContent \ +curl http://localhost:4000/vertex_ai/publishers/google/models/gemini-1.5-flash-001:generateContent \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-1234" \ + -H "x-litellm-api-key: Bearer sk-1234" \ -d '{"contents":[{"role": "user", "parts":[{"text": "hi"}]}]}' ``` @@ -320,7 +312,7 @@ import vertexai from vertexai.generative_models import GenerativeModel LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -358,7 +350,7 @@ from google.auth.credentials import Credentials from vertexai.generative_models import GenerativeModel LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -413,9 +405,9 @@ def embed_text( ```shell -curl http://localhost:4000/vertex-ai/publishers/google/models/textembedding-gecko@001:predict \ +curl http://localhost:4000/vertex_ai/publishers/google/models/textembedding-gecko@001:predict \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-1234" \ + -H "x-litellm-api-key: Bearer sk-1234" \ -d '{"instances":[{"content": "gm"}]}' ``` @@ -437,7 +429,7 @@ import vertexai from google.auth.credentials import Credentials LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -482,7 +474,7 @@ import vertexai from google.auth.credentials import Credentials LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -547,9 +539,9 @@ print(f"Created output image using {len(images[0]._image_bytes)} bytes") ```shell -curl http://localhost:4000/vertex-ai/publishers/google/models/imagen-3.0-generate-001:predict \ +curl http://localhost:4000/vertex_ai/publishers/google/models/imagen-3.0-generate-001:predict \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-1234" \ + -H "x-litellm-api-key: Bearer sk-1234" \ -d '{"instances":[{"prompt": "make an otter"}], "parameters": {"sampleCount": 1}}' ``` @@ -571,7 +563,7 @@ from vertexai.generative_models import GenerativeModel import vertexai LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -614,7 +606,7 @@ import vertexai from google.auth.credentials import Credentials LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -677,9 +669,9 @@ print(f"Total Token Count: {usage_metadata.total_token_count}") ```shell -curl http://localhost:4000/vertex-ai/publishers/google/models/gemini-1.5-flash-001:countTokens \ +curl http://localhost:4000/vertex_ai/publishers/google/models/gemini-1.5-flash-001:countTokens \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-1234" \ + -H "x-litellm-api-key: Bearer sk-1234" \ -d '{"contents":[{"role": "user", "parts":[{"text": "hi"}]}]}' ``` @@ -700,7 +692,7 @@ from vertexai.preview.tuning import sft import vertexai LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" vertexai.init( @@ -741,7 +733,7 @@ import vertexai from google.auth.credentials import Credentials LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex_ai" import datetime @@ -801,9 +793,9 @@ print(sft_tuning_job.experiment) ```shell -curl http://localhost:4000/vertex-ai/tuningJobs \ +curl http://localhost:4000/vertex_ai/tuningJobs \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-1234" \ + -H "x-litellm-api-key: Bearer sk-1234" \ -d '{ "baseModel": "gemini-1.0-pro-002", "supervisedTuningSpec" : { @@ -872,8 +864,8 @@ httpx_client = httpx.Client(timeout=30) print("Creating cached content") create_cache = httpx_client.post( - url=f"{LITELLM_BASE_URL}/vertex-ai/cachedContents", - headers={"Authorization": f"Bearer {LITELLM_PROXY_API_KEY}"}, + url=f"{LITELLM_BASE_URL}/vertex_ai/cachedContents", + headers={"x-litellm-api-key": f"Bearer {LITELLM_PROXY_API_KEY}"}, json={ "model": "gemini-1.5-pro-001", "contents": [ @@ -920,5 +912,130 @@ response = client.chat.completions.create( print("Response from proxy:", response) ``` + + + + +## Advanced + +Pre-requisites +- [Setup proxy with DB](../proxy/virtual_keys.md#setup) + +Use this, to avoid giving developers the raw Anthropic API key, but still letting them use Anthropic endpoints. + +### Use with Virtual Keys + +1. Setup environment + +```bash +export DATABASE_URL="" +export LITELLM_MASTER_KEY="" +``` + +```bash +litellm + +# RUNNING on http://0.0.0.0:4000 +``` + +2. Generate virtual key + +```bash +curl -X POST 'http://0.0.0.0:4000/key/generate' \ +-H 'x-litellm-api-key: Bearer sk-1234' \ +-H 'Content-Type: application/json' \ +-d '{}' +``` + +Expected Response + +```bash +{ + ... + "key": "sk-1234ewknldferwedojwojw" +} +``` + +3. Test it! + + +```bash +curl http://localhost:4000/vertex_ai/publishers/google/models/gemini-1.0-pro:generateContent \ + -H "Content-Type: application/json" \ + -H "x-litellm-api-key: Bearer sk-1234" \ + -d '{ + "contents":[{ + "role": "user", + "parts":[{"text": "How are you doing today?"}] + }] + }' +``` + +### Send `tags` in request headers + +Use this if you wants `tags` to be tracked in the LiteLLM DB and on logging callbacks + +Pass `tags` in request headers as a comma separated list. In the example below the following tags will be tracked + +``` +tags: ["vertex-js-sdk", "pass-through-endpoint"] +``` + + + + +```bash +curl http://localhost:4000/vertex-ai/publishers/google/models/gemini-1.0-pro:generateContent \ + -H "Content-Type: application/json" \ + -H "x-litellm-api-key: Bearer sk-1234" \ + -H "tags: vertex-js-sdk,pass-through-endpoint" \ + -d '{ + "contents":[{ + "role": "user", + "parts":[{"text": "How are you doing today?"}] + }] + }' +``` + + + + +```javascript +const { VertexAI } = require('@google-cloud/vertexai'); + +const vertexAI = new VertexAI({ + project: 'your-project-id', // enter your vertex project id + location: 'us-central1', // enter your vertex region + apiEndpoint: "localhost:4000/vertex_ai" // /vertex_ai # note, do not include 'https://' in the url +}); + +const model = vertexAI.getGenerativeModel({ + model: 'gemini-1.0-pro' +}, { + customHeaders: { + "x-litellm-api-key": "sk-1234", // Your litellm Virtual Key + "tags": "vertex-js-sdk,pass-through-endpoint" + } +}); + +async function generateContent() { + try { + const prompt = { + contents: [{ + role: 'user', + parts: [{ text: 'How are you doing today?' }] + }] + }; + + const response = await model.generateContent(prompt); + console.log('Response:', response); + } catch (error) { + console.error('Error:', error); + } +} + +generateContent(); +``` + \ No newline at end of file diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 0fd174440..130be6303 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -393,6 +393,7 @@ async def pass_through_request( # noqa: PLR0915 _parsed_body=_parsed_body, passthrough_logging_payload=passthrough_logging_payload, litellm_call_id=litellm_call_id, + request=request, ) # done for supporting 'parallel_request_limiter.py' with pass-through endpoints logging_obj.update_environment_variables( @@ -572,6 +573,7 @@ async def pass_through_request( # noqa: PLR0915 def _init_kwargs_for_pass_through_endpoint( + request: Request, user_api_key_dict: UserAPIKeyAuth, passthrough_logging_payload: PassthroughStandardLoggingPayload, _parsed_body: Optional[dict] = None, @@ -587,6 +589,12 @@ def _init_kwargs_for_pass_through_endpoint( } if _litellm_metadata: _metadata.update(_litellm_metadata) + + _metadata = _update_metadata_with_tags_in_header( + request=request, + metadata=_metadata, + ) + kwargs = { "litellm_params": { "metadata": _metadata, @@ -598,6 +606,13 @@ def _init_kwargs_for_pass_through_endpoint( return kwargs +def _update_metadata_with_tags_in_header(request: Request, metadata: dict) -> dict: + _tags = request.headers.get("tags") + if _tags: + metadata["tags"] = _tags.split(",") + return metadata + + def create_pass_through_route( endpoint, target: str, diff --git a/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py b/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py index fbf37ce8d..1a0d09a88 100644 --- a/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/vertex_endpoints.py @@ -113,7 +113,12 @@ def construct_target_url( @router.api_route( - "/vertex-ai/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"] + "/vertex-ai/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE"], + include_in_schema=False, +) +@router.api_route( + "/vertex_ai/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"] ) async def vertex_proxy_route( endpoint: str, diff --git a/tests/pass_through_tests/test_local_vertex.js b/tests/pass_through_tests/test_local_vertex.js index 7ae9b942a..9ee603e7a 100644 --- a/tests/pass_through_tests/test_local_vertex.js +++ b/tests/pass_through_tests/test_local_vertex.js @@ -1,31 +1,22 @@ const { VertexAI, RequestOptions } = require('@google-cloud/vertexai'); -// Import fetch if the SDK uses it -const originalFetch = global.fetch || require('node-fetch'); - -// Monkey-patch the fetch used internally -global.fetch = async function patchedFetch(url, options) { - // Modify the URL to use HTTP instead of HTTPS - if (url.startsWith('https://localhost:4000')) { - url = url.replace('https://', 'http://'); - } - console.log('Patched fetch sending request to:', url); - return originalFetch(url, options); -}; const vertexAI = new VertexAI({ project: 'adroit-crow-413218', location: 'us-central1', - apiEndpoint: "localhost:4000/vertex-ai" + apiEndpoint: "127.0.0.1:4000/vertex-ai" }); +// Create customHeaders using Headers +const customHeaders = new Headers({ + "X-Litellm-Api-Key": "sk-1234", + tags: "vertexjs,test-2" +}); // Use customHeaders in RequestOptions const requestOptions = { - customHeaders: new Headers({ - "x-litellm-api-key": "sk-1234" - }) + customHeaders: customHeaders, }; const generativeModel = vertexAI.getGenerativeModel( @@ -33,7 +24,7 @@ const generativeModel = vertexAI.getGenerativeModel( requestOptions ); -async function streamingResponse() { +async function testModel() { try { const request = { contents: [{role: 'user', parts: [{text: 'How are you doing today tell me your name?'}]}], @@ -49,20 +40,4 @@ async function streamingResponse() { } } - -async function nonStreamingResponse() { - try { - const request = { - contents: [{role: 'user', parts: [{text: 'How are you doing today tell me your name?'}]}], - }; - const response = await generativeModel.generateContent(request); - console.log('non streaming response: ', JSON.stringify(response)); - } catch (error) { - console.error('Error:', error); - } -} - - - -streamingResponse(); -nonStreamingResponse(); \ No newline at end of file +testModel(); \ No newline at end of file diff --git a/tests/pass_through_tests/test_vertex_ai.py b/tests/pass_through_tests/test_vertex_ai.py index dee0d59eb..99b513e82 100644 --- a/tests/pass_through_tests/test_vertex_ai.py +++ b/tests/pass_through_tests/test_vertex_ai.py @@ -99,7 +99,7 @@ async def test_basic_vertex_ai_pass_through_with_spendlog(): vertexai.init( project="adroit-crow-413218", location="us-central1", - api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex-ai", + api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex_ai", api_transport="rest", ) @@ -131,7 +131,7 @@ async def test_basic_vertex_ai_pass_through_streaming_with_spendlog(): vertexai.init( project="adroit-crow-413218", location="us-central1", - api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex-ai", + api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex_ai", api_transport="rest", ) @@ -170,7 +170,7 @@ async def test_vertex_ai_pass_through_endpoint_context_caching(): vertexai.init( project="adroit-crow-413218", location="us-central1", - api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex-ai", + api_endpoint=f"{LITE_LLM_ENDPOINT}/vertex_ai", api_transport="rest", ) diff --git a/tests/pass_through_tests/test_vertex_with_spend.test.js b/tests/pass_through_tests/test_vertex_with_spend.test.js new file mode 100644 index 000000000..8a5b91557 --- /dev/null +++ b/tests/pass_through_tests/test_vertex_with_spend.test.js @@ -0,0 +1,194 @@ +const { VertexAI, RequestOptions } = require('@google-cloud/vertexai'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const { writeFileSync } = require('fs'); + + +// Import fetch if the SDK uses it +const originalFetch = global.fetch || require('node-fetch'); + +let lastCallId; + +// Monkey-patch the fetch used internally +global.fetch = async function patchedFetch(url, options) { + // Modify the URL to use HTTP instead of HTTPS + if (url.startsWith('https://127.0.0.1:4000')) { + url = url.replace('https://', 'http://'); + } + console.log('Patched fetch sending request to:', url); + + const response = await originalFetch(url, options); + + // Store the call ID if it exists + lastCallId = response.headers.get('x-litellm-call-id'); + + return response; +}; + +function loadVertexAiCredentials() { + console.log("loading vertex ai credentials"); + const filepath = path.dirname(__filename); + const vertexKeyPath = path.join(filepath, "vertex_key.json"); + + // Initialize default empty service account data + let serviceAccountKeyData = {}; + + // Try to read existing vertex_key.json + try { + const content = fs.readFileSync(vertexKeyPath, 'utf8'); + if (content && content.trim()) { + serviceAccountKeyData = JSON.parse(content); + } + } catch (error) { + // File doesn't exist or is invalid, continue with empty object + } + + // Update with environment variables + const privateKeyId = process.env.VERTEX_AI_PRIVATE_KEY_ID || ""; + const privateKey = (process.env.VERTEX_AI_PRIVATE_KEY || "").replace(/\\n/g, "\n"); + + serviceAccountKeyData.private_key_id = privateKeyId; + serviceAccountKeyData.private_key = privateKey; + + // Create temporary file + const tempFilePath = path.join(os.tmpdir(), `vertex-credentials-${Date.now()}.json`); + writeFileSync(tempFilePath, JSON.stringify(serviceAccountKeyData, null, 2)); + + // Set environment variable + process.env.GOOGLE_APPLICATION_CREDENTIALS = tempFilePath; +} + +// Run credential loading before tests +// beforeAll(() => { +// loadVertexAiCredentials(); +// }); + + + +describe('Vertex AI Tests', () => { + test('should successfully generate non-streaming content with tags', async () => { + const vertexAI = new VertexAI({ + project: 'adroit-crow-413218', + location: 'us-central1', + apiEndpoint: "127.0.0.1:4000/vertex_ai" + }); + + const customHeaders = new Headers({ + "x-litellm-api-key": "sk-1234", + "tags": "vertex-js-sdk,pass-through-endpoint" + }); + + const requestOptions = { + customHeaders: customHeaders + }; + + const generativeModel = vertexAI.getGenerativeModel( + { model: 'gemini-1.0-pro' }, + requestOptions + ); + + const request = { + contents: [{role: 'user', parts: [{text: 'Say "hello test" and nothing else'}]}] + }; + + const result = await generativeModel.generateContent(request); + expect(result).toBeDefined(); + + // Use the captured callId + const callId = lastCallId; + console.log("Captured Call ID:", callId); + + // Wait for spend to be logged + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check spend logs + const spendResponse = await fetch( + `http://127.0.0.1:4000/spend/logs?request_id=${callId}`, + { + headers: { + 'Authorization': 'Bearer sk-1234' + } + } + ); + + const spendData = await spendResponse.json(); + console.log("spendData", spendData) + expect(spendData).toBeDefined(); + expect(spendData[0].request_id).toBe(callId); + expect(spendData[0].call_type).toBe('pass_through_endpoint'); + expect(spendData[0].request_tags).toEqual(['vertex-js-sdk', 'pass-through-endpoint']); + expect(spendData[0].metadata).toHaveProperty('user_api_key'); + expect(spendData[0].model).toContain('gemini'); + expect(spendData[0].spend).toBeGreaterThan(0); + }, 25000); + + test('should successfully generate streaming content with tags', async () => { + const vertexAI = new VertexAI({ + project: 'adroit-crow-413218', + location: 'us-central1', + apiEndpoint: "127.0.0.1:4000/vertex_ai" + }); + + const customHeaders = new Headers({ + "x-litellm-api-key": "sk-1234", + "tags": "vertex-js-sdk,pass-through-endpoint" + }); + + const requestOptions = { + customHeaders: customHeaders + }; + + const generativeModel = vertexAI.getGenerativeModel( + { model: 'gemini-1.0-pro' }, + requestOptions + ); + + const request = { + contents: [{role: 'user', parts: [{text: 'Say "hello test" and nothing else'}]}] + }; + + const streamingResult = await generativeModel.generateContentStream(request); + expect(streamingResult).toBeDefined(); + + + // Add some assertions + expect(streamingResult).toBeDefined(); + + for await (const item of streamingResult.stream) { + console.log('stream chunk:', JSON.stringify(item)); + expect(item).toBeDefined(); + } + + const aggregatedResponse = await streamingResult.response; + console.log('aggregated response:', JSON.stringify(aggregatedResponse)); + expect(aggregatedResponse).toBeDefined(); + + // Use the captured callId + const callId = lastCallId; + console.log("Captured Call ID:", callId); + + // Wait for spend to be logged + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check spend logs + const spendResponse = await fetch( + `http://127.0.0.1:4000/spend/logs?request_id=${callId}`, + { + headers: { + 'Authorization': 'Bearer sk-1234' + } + } + ); + + const spendData = await spendResponse.json(); + console.log("spendData", spendData) + expect(spendData).toBeDefined(); + expect(spendData[0].request_id).toBe(callId); + expect(spendData[0].call_type).toBe('pass_through_endpoint'); + expect(spendData[0].request_tags).toEqual(['vertex-js-sdk', 'pass-through-endpoint']); + expect(spendData[0].metadata).toHaveProperty('user_api_key'); + expect(spendData[0].model).toContain('gemini'); + expect(spendData[0].spend).toBeGreaterThan(0); + }, 25000); +}); \ No newline at end of file diff --git a/tests/pass_through_unit_tests/test_pass_through_unit_tests.py b/tests/pass_through_unit_tests/test_pass_through_unit_tests.py new file mode 100644 index 000000000..c55bdc7a8 --- /dev/null +++ b/tests/pass_through_unit_tests/test_pass_through_unit_tests.py @@ -0,0 +1,165 @@ +import json +import os +import sys +from datetime import datetime +from unittest.mock import AsyncMock, Mock, patch, MagicMock + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +import httpx +import pytest +import litellm +from typing import AsyncGenerator +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.proxy.pass_through_endpoints.types import EndpointType +from litellm.proxy.pass_through_endpoints.success_handler import ( + PassThroughEndpointLogging, +) +from litellm.proxy.pass_through_endpoints.streaming_handler import ( + PassThroughStreamingHandler, +) + +from fastapi import Request +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.pass_through_endpoints.pass_through_endpoints import ( + _init_kwargs_for_pass_through_endpoint, + _update_metadata_with_tags_in_header, +) +from litellm.proxy.pass_through_endpoints.types import PassthroughStandardLoggingPayload + + +@pytest.fixture +def mock_request(): + # Create a mock request with headers + class MockRequest: + def __init__(self, headers=None): + self.headers = headers or {} + + return MockRequest + + +@pytest.fixture +def mock_user_api_key_dict(): + return UserAPIKeyAuth( + api_key="test-key", + user_id="test-user", + team_id="test-team", + ) + + +def test_update_metadata_with_tags_in_header_no_tags(mock_request): + """ + No tags should be added to metadata if they do not exist in headers + """ + # Test when no tags are present in headers + request = mock_request(headers={}) + metadata = {"existing": "value"} + + result = _update_metadata_with_tags_in_header(request=request, metadata=metadata) + + assert result == {"existing": "value"} + assert "tags" not in result + + +def test_update_metadata_with_tags_in_header_with_tags(mock_request): + """ + Tags should be added to metadata if they exist in headers + """ + # Test when tags are present in headers + request = mock_request(headers={"tags": "tag1,tag2,tag3"}) + metadata = {"existing": "value"} + + result = _update_metadata_with_tags_in_header(request=request, metadata=metadata) + + assert result == {"existing": "value", "tags": ["tag1", "tag2", "tag3"]} + + +def test_init_kwargs_for_pass_through_endpoint_basic( + mock_request, mock_user_api_key_dict +): + """ + Basic test for init_kwargs_for_pass_through_endpoint + + - metadata should contain user_api_key, user_api_key_user_id, user_api_key_team_id, user_api_key_end_user_id from `mock_user_api_key_dict` + """ + request = mock_request() + passthrough_payload = PassthroughStandardLoggingPayload( + url="https://test.com", + request_body={}, + ) + + result = _init_kwargs_for_pass_through_endpoint( + request=request, + user_api_key_dict=mock_user_api_key_dict, + passthrough_logging_payload=passthrough_payload, + litellm_call_id="test-call-id", + ) + + assert result["call_type"] == "pass_through_endpoint" + assert result["litellm_call_id"] == "test-call-id" + assert result["passthrough_logging_payload"] == passthrough_payload + + # Check metadata + expected_metadata = { + "user_api_key": "test-key", + "user_api_key_user_id": "test-user", + "user_api_key_team_id": "test-team", + "user_api_key_end_user_id": "test-user", + } + assert result["litellm_params"]["metadata"] == expected_metadata + + +def test_init_kwargs_with_litellm_metadata(mock_request, mock_user_api_key_dict): + """ + Expected behavior: litellm_metadata should be merged with default metadata + + see usage example here: https://docs.litellm.ai/docs/pass_through/anthropic_completion#send-litellm_metadata-tags + """ + request = mock_request() + parsed_body = { + "litellm_metadata": {"custom_field": "custom_value", "tags": ["tag1", "tag2"]} + } + passthrough_payload = PassthroughStandardLoggingPayload( + url="https://test.com", + request_body={}, + ) + + result = _init_kwargs_for_pass_through_endpoint( + request=request, + user_api_key_dict=mock_user_api_key_dict, + passthrough_logging_payload=passthrough_payload, + _parsed_body=parsed_body, + litellm_call_id="test-call-id", + ) + + # Check that litellm_metadata was merged with default metadata + metadata = result["litellm_params"]["metadata"] + print("metadata", metadata) + assert metadata["custom_field"] == "custom_value" + assert metadata["tags"] == ["tag1", "tag2"] + assert metadata["user_api_key"] == "test-key" + + +def test_init_kwargs_with_tags_in_header(mock_request, mock_user_api_key_dict): + """ + Tags should be added to metadata if they exist in headers + """ + request = mock_request(headers={"tags": "tag1,tag2"}) + passthrough_payload = PassthroughStandardLoggingPayload( + url="https://test.com", + request_body={}, + ) + + result = _init_kwargs_for_pass_through_endpoint( + request=request, + user_api_key_dict=mock_user_api_key_dict, + passthrough_logging_payload=passthrough_payload, + litellm_call_id="test-call-id", + ) + + # Check that tags were added to metadata + metadata = result["litellm_params"]["metadata"] + print("metadata", metadata) + assert metadata["tags"] == ["tag1", "tag2"] From c60261c3bc30f99bb01a1f8776de9d254c7efa1a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 25 Nov 2024 13:13:03 -0800 Subject: [PATCH 05/16] (feat) Add support for using @google/generative-ai JS with LiteLLM Proxy (#6899) * feat - allow using gemini js SDK with LiteLLM * add auth for gemini_proxy_route * basic local test for js * test cost tagging gemini js requests * add js sdk test for gemini with litellm * add docs on gemini JS SDK * run node.js tests * fix google ai studio tests * fix vertex js spend test --- .circleci/config.yml | 6 +- .../docs/pass_through/google_ai_studio.md | 128 +++++++++++++++++- litellm/proxy/_types.py | 1 + litellm/proxy/auth/user_api_key_auth.py | 10 ++ .../llm_passthrough_endpoints.py | 6 +- .../test_gemini_with_spend.test.js | 123 +++++++++++++++++ tests/pass_through_tests/test_local_gemini.js | 55 ++++++++ .../test_vertex_with_spend.test.js | 6 +- 8 files changed, 323 insertions(+), 12 deletions(-) create mode 100644 tests/pass_through_tests/test_gemini_with_spend.test.js create mode 100644 tests/pass_through_tests/test_local_gemini.js diff --git a/.circleci/config.yml b/.circleci/config.yml index d33f62cf3..56fb1fee1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1191,6 +1191,7 @@ jobs: -e DATABASE_URL=$PROXY_DATABASE_URL \ -e LITELLM_MASTER_KEY="sk-1234" \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ + -e GEMINI_API_KEY=$GEMINI_API_KEY \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ -e LITELLM_LICENSE=$LITELLM_LICENSE \ --name my-app \ @@ -1228,12 +1229,13 @@ jobs: name: Install Node.js dependencies command: | npm install @google-cloud/vertexai + npm install @google/generative-ai npm install --save-dev jest - run: - name: Run Vertex AI tests + name: Run Vertex AI, Google AI Studio Node.js tests command: | - npx jest tests/pass_through_tests/test_vertex.test.js --verbose + npx jest tests/pass_through_tests --verbose no_output_timeout: 30m - run: name: Run tests diff --git a/docs/my-website/docs/pass_through/google_ai_studio.md b/docs/my-website/docs/pass_through/google_ai_studio.md index cc7f9ce71..ee5eecc19 100644 --- a/docs/my-website/docs/pass_through/google_ai_studio.md +++ b/docs/my-website/docs/pass_through/google_ai_studio.md @@ -1,12 +1,21 @@ +import Image from '@theme/IdealImage'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + # Google AI Studio SDK Pass-through endpoints for Google AI Studio - call provider-specific endpoint, in native format (no translation). -Just replace `https://generativelanguage.googleapis.com` with `LITELLM_PROXY_BASE_URL/gemini` 🚀 +Just replace `https://generativelanguage.googleapis.com` with `LITELLM_PROXY_BASE_URL/gemini` #### **Example Usage** + + + + ```bash -http://0.0.0.0:4000/gemini/v1beta/models/gemini-1.5-flash:countTokens?key=sk-anything' \ +curl 'http://0.0.0.0:4000/gemini/v1beta/models/gemini-1.5-flash:countTokens?key=sk-anything' \ -H 'Content-Type: application/json' \ -d '{ "contents": [{ @@ -17,6 +26,53 @@ http://0.0.0.0:4000/gemini/v1beta/models/gemini-1.5-flash:countTokens?key=sk-any }' ``` + + + +```javascript +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +const modelParams = { + model: 'gemini-pro', +}; + +const requestOptions = { + baseUrl: 'http://localhost:4000/gemini', // http:///gemini +}; + +const genAI = new GoogleGenerativeAI("sk-1234"); // litellm proxy API key +const model = genAI.getGenerativeModel(modelParams, requestOptions); + +async function main() { + try { + const result = await model.generateContent("Explain how AI works"); + console.log(result.response.text()); + } catch (error) { + console.error('Error:', error); + } +} + +// For streaming responses +async function main_streaming() { + try { + const streamingResult = await model.generateContentStream("Explain how AI works"); + for await (const chunk of streamingResult.stream) { + console.log('Stream chunk:', JSON.stringify(chunk)); + } + const aggregatedResponse = await streamingResult.response; + console.log('Aggregated response:', JSON.stringify(aggregatedResponse)); + } catch (error) { + console.error('Error:', error); + } +} + +main(); +// main_streaming(); +``` + + + + Supports **ALL** Google AI Studio Endpoints (including streaming). [**See All Google AI Studio Endpoints**](https://ai.google.dev/api) @@ -166,14 +222,14 @@ curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5 ``` -## Advanced - Use with Virtual Keys +## Advanced Pre-requisites - [Setup proxy with DB](../proxy/virtual_keys.md#setup) Use this, to avoid giving developers the raw Google AI Studio key, but still letting them use Google AI Studio endpoints. -### Usage +### Use with Virtual Keys 1. Setup environment @@ -220,4 +276,66 @@ http://0.0.0.0:4000/gemini/v1beta/models/gemini-1.5-flash:countTokens?key=sk-123 }] }] }' -``` \ No newline at end of file +``` + + +### Send `tags` in request headers + +Use this if you want `tags` to be tracked in the LiteLLM DB and on logging callbacks. + +Pass tags in request headers as a comma separated list. In the example below the following tags will be tracked + +``` +tags: ["gemini-js-sdk", "pass-through-endpoint"] +``` + + + + +```bash +curl 'http://0.0.0.0:4000/gemini/v1beta/models/gemini-1.5-flash:generateContent?key=sk-anything' \ +-H 'Content-Type: application/json' \ +-H 'tags: gemini-js-sdk,pass-through-endpoint' \ +-d '{ + "contents": [{ + "parts":[{ + "text": "The quick brown fox jumps over the lazy dog." + }] + }] +}' +``` + + + + +```javascript +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +const modelParams = { + model: 'gemini-pro', +}; + +const requestOptions = { + baseUrl: 'http://localhost:4000/gemini', // http:///gemini + customHeaders: { + "tags": "gemini-js-sdk,pass-through-endpoint" + } +}; + +const genAI = new GoogleGenerativeAI("sk-1234"); +const model = genAI.getGenerativeModel(modelParams, requestOptions); + +async function main() { + try { + const result = await model.generateContent("Explain how AI works"); + console.log(result.response.text()); + } catch (error) { + console.error('Error:', error); + } +} + +main(); +``` + + + diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 74e82b0ea..72d6c84c9 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2111,6 +2111,7 @@ class SpecialHeaders(enum.Enum): openai_authorization = "Authorization" azure_authorization = "API-Key" anthropic_authorization = "x-api-key" + google_ai_studio_authorization = "x-goog-api-key" class LitellmDataForBackendLLMCall(TypedDict, total=False): diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 669661e94..d19215245 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -95,6 +95,11 @@ anthropic_api_key_header = APIKeyHeader( auto_error=False, description="If anthropic client used.", ) +google_ai_studio_api_key_header = APIKeyHeader( + name=SpecialHeaders.google_ai_studio_authorization.value, + auto_error=False, + description="If google ai studio client used.", +) def _get_bearer_token( @@ -197,6 +202,9 @@ async def user_api_key_auth( # noqa: PLR0915 anthropic_api_key_header: Optional[str] = fastapi.Security( anthropic_api_key_header ), + google_ai_studio_api_key_header: Optional[str] = fastapi.Security( + google_ai_studio_api_key_header + ), ) -> UserAPIKeyAuth: from litellm.proxy.proxy_server import ( general_settings, @@ -233,6 +241,8 @@ async def user_api_key_auth( # noqa: PLR0915 api_key = azure_api_key_header elif isinstance(anthropic_api_key_header, str): api_key = anthropic_api_key_header + elif isinstance(google_ai_studio_api_key_header, str): + api_key = google_ai_studio_api_key_header elif pass_through_endpoints is not None: for endpoint in pass_through_endpoints: if endpoint.get("path", "") == route: diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index 3f4643afc..274ffda5b 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -61,10 +61,12 @@ async def gemini_proxy_route( fastapi_response: Response, ): ## CHECK FOR LITELLM API KEY IN THE QUERY PARAMS - ?..key=LITELLM_API_KEY - api_key = request.query_params.get("key") + google_ai_studio_api_key = request.query_params.get("key") or request.headers.get( + "x-goog-api-key" + ) user_api_key_dict = await user_api_key_auth( - request=request, api_key="Bearer {}".format(api_key) + request=request, api_key=f"Bearer {google_ai_studio_api_key}" ) base_target_url = "https://generativelanguage.googleapis.com" diff --git a/tests/pass_through_tests/test_gemini_with_spend.test.js b/tests/pass_through_tests/test_gemini_with_spend.test.js new file mode 100644 index 000000000..d02237fe3 --- /dev/null +++ b/tests/pass_through_tests/test_gemini_with_spend.test.js @@ -0,0 +1,123 @@ +const { GoogleGenerativeAI } = require("@google/generative-ai"); +const fs = require('fs'); +const path = require('path'); + +// Import fetch if the SDK uses it +const originalFetch = global.fetch || require('node-fetch'); + +let lastCallId; + +// Monkey-patch the fetch used internally +global.fetch = async function patchedFetch(url, options) { + const response = await originalFetch(url, options); + + // Store the call ID if it exists + lastCallId = response.headers.get('x-litellm-call-id'); + + return response; +}; + +describe('Gemini AI Tests', () => { + test('should successfully generate non-streaming content with tags', async () => { + const genAI = new GoogleGenerativeAI("sk-1234"); // litellm proxy API key + + const requestOptions = { + baseUrl: 'http://127.0.0.1:4000/gemini', + customHeaders: { + "tags": "gemini-js-sdk,pass-through-endpoint" + } + }; + + const model = genAI.getGenerativeModel({ + model: 'gemini-pro' + }, requestOptions); + + const prompt = 'Say "hello test" and nothing else'; + + const result = await model.generateContent(prompt); + expect(result).toBeDefined(); + + // Use the captured callId + const callId = lastCallId; + console.log("Captured Call ID:", callId); + + // Wait for spend to be logged + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check spend logs + const spendResponse = await fetch( + `http://127.0.0.1:4000/spend/logs?request_id=${callId}`, + { + headers: { + 'Authorization': 'Bearer sk-1234' + } + } + ); + + const spendData = await spendResponse.json(); + console.log("spendData", spendData) + expect(spendData).toBeDefined(); + expect(spendData[0].request_id).toBe(callId); + expect(spendData[0].call_type).toBe('pass_through_endpoint'); + expect(spendData[0].request_tags).toEqual(['gemini-js-sdk', 'pass-through-endpoint']); + expect(spendData[0].metadata).toHaveProperty('user_api_key'); + expect(spendData[0].model).toContain('gemini'); + expect(spendData[0].spend).toBeGreaterThan(0); + }, 25000); + + test('should successfully generate streaming content with tags', async () => { + const genAI = new GoogleGenerativeAI("sk-1234"); // litellm proxy API key + + const requestOptions = { + baseUrl: 'http://127.0.0.1:4000/gemini', + customHeaders: { + "tags": "gemini-js-sdk,pass-through-endpoint" + } + }; + + const model = genAI.getGenerativeModel({ + model: 'gemini-pro' + }, requestOptions); + + const prompt = 'Say "hello test" and nothing else'; + + const streamingResult = await model.generateContentStream(prompt); + expect(streamingResult).toBeDefined(); + + for await (const chunk of streamingResult.stream) { + console.log('stream chunk:', JSON.stringify(chunk)); + expect(chunk).toBeDefined(); + } + + const aggregatedResponse = await streamingResult.response; + console.log('aggregated response:', JSON.stringify(aggregatedResponse)); + expect(aggregatedResponse).toBeDefined(); + + // Use the captured callId + const callId = lastCallId; + console.log("Captured Call ID:", callId); + + // Wait for spend to be logged + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Check spend logs + const spendResponse = await fetch( + `http://127.0.0.1:4000/spend/logs?request_id=${callId}`, + { + headers: { + 'Authorization': 'Bearer sk-1234' + } + } + ); + + const spendData = await spendResponse.json(); + console.log("spendData", spendData) + expect(spendData).toBeDefined(); + expect(spendData[0].request_id).toBe(callId); + expect(spendData[0].call_type).toBe('pass_through_endpoint'); + expect(spendData[0].request_tags).toEqual(['gemini-js-sdk', 'pass-through-endpoint']); + expect(spendData[0].metadata).toHaveProperty('user_api_key'); + expect(spendData[0].model).toContain('gemini'); + expect(spendData[0].spend).toBeGreaterThan(0); + }, 25000); +}); diff --git a/tests/pass_through_tests/test_local_gemini.js b/tests/pass_through_tests/test_local_gemini.js new file mode 100644 index 000000000..7043a5ab4 --- /dev/null +++ b/tests/pass_through_tests/test_local_gemini.js @@ -0,0 +1,55 @@ +const { GoogleGenerativeAI, ModelParams, RequestOptions } = require("@google/generative-ai"); + +const modelParams = { + model: 'gemini-pro', +}; + +const requestOptions = { + baseUrl: 'http://127.0.0.1:4000/gemini', + customHeaders: { + "tags": "gemini-js-sdk,gemini-pro" + } +}; + +const genAI = new GoogleGenerativeAI("sk-1234"); // litellm proxy API key +const model = genAI.getGenerativeModel(modelParams, requestOptions); + +const testPrompt = "Explain how AI works"; + +async function main() { + console.log("making request") + try { + const result = await model.generateContent(testPrompt); + console.log(result.response.text()); + } catch (error) { + console.error('Error details:', { + name: error.name, + message: error.message, + cause: error.cause, + // Check if there's a network error + isNetworkError: error instanceof TypeError && error.message === 'fetch failed' + }); + + // Check if the server is running + if (error instanceof TypeError && error.message === 'fetch failed') { + console.error('Make sure your local server is running at http://localhost:4000'); + } + } +} + + +async function main_streaming() { + try { + const streamingResult = await model.generateContentStream(testPrompt); + for await (const item of streamingResult.stream) { + console.log('stream chunk: ', JSON.stringify(item)); + } + const aggregatedResponse = await streamingResult.response; + console.log('aggregated response: ', JSON.stringify(aggregatedResponse)); + } catch (error) { + console.error('Error details:', error); + } +} + +// main(); +main_streaming(); \ No newline at end of file diff --git a/tests/pass_through_tests/test_vertex_with_spend.test.js b/tests/pass_through_tests/test_vertex_with_spend.test.js index 8a5b91557..d49b1eda2 100644 --- a/tests/pass_through_tests/test_vertex_with_spend.test.js +++ b/tests/pass_through_tests/test_vertex_with_spend.test.js @@ -60,9 +60,9 @@ function loadVertexAiCredentials() { } // Run credential loading before tests -// beforeAll(() => { -// loadVertexAiCredentials(); -// }); +beforeAll(() => { + loadVertexAiCredentials(); +}); From e952c666f3a1d4e5faba85c16ca87c8856bbfa2f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 25 Nov 2024 22:41:45 -0800 Subject: [PATCH 06/16] (UI fix) UI does not reload when you login / open a new tab (#6909) * store current page on url * update menu history --- ui/litellm-dashboard/src/app/page.tsx | 30 ++++++++++++------- .../src/components/leftnav.tsx | 16 +++++++--- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 9448be82a..94af12574 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -82,16 +82,26 @@ const CreateKeyPage = () => { const invitation_id = searchParams.get("invitation_id"); const token = getCookie('token'); + // Get page from URL, default to 'api-keys' if not present const [page, setPage] = useState(() => { - if (typeof window !== 'undefined') { - return localStorage.getItem('selectedPage') || "api-keys"; - } - return "api-keys"; + return searchParams.get('page') || 'api-keys'; }); - useEffect(() => { - localStorage.setItem('selectedPage', page); - }, [page]); + // Custom setPage function that updates URL + const updatePage = (newPage: string) => { + // Update URL without full page reload + const newSearchParams = new URLSearchParams(searchParams); + newSearchParams.set('page', newPage); + + // Use Next.js router to update URL + window.history.pushState( + null, + '', + `?${newSearchParams.toString()}` + ); + + setPage(newPage); + }; const [accessToken, setAccessToken] = useState(null); @@ -172,8 +182,8 @@ const CreateKeyPage = () => { />
- @@ -289,7 +299,7 @@ const CreateKeyPage = () => { /> )}
-
+ ) } diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 4304fe836..ba7519324 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -7,7 +7,7 @@ const { Sider } = Layout; // Define the props type interface SidebarProps { - setPage: React.Dispatch>; + setPage: (page: string) => void; userRole: string; defaultSelectedKey: string; } @@ -67,9 +67,17 @@ const Sidebar: React.FC = ({ style={{ height: "100%", borderRight: 0 }} > {filteredMenuItems.map(item => ( - setPage(item.page)}> - {item.label} - + { + const newSearchParams = new URLSearchParams(window.location.search); + newSearchParams.set('page', item.page); + window.history.pushState(null, '', `?${newSearchParams.toString()}`); + setPage(item.page); + }} + > + {item.label} + ))} From d52aae4e82964f30a1c4a49d54caf960aafdbaba Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 25 Nov 2024 22:42:59 -0800 Subject: [PATCH 07/16] ui new build --- litellm/proxy/_experimental/out/404.html | 1 + .../_buildManifest.js | 0 .../_ssgManifest.js | 0 ...9bfc35ead00.js => 626-4e8df4039ecf4386.js} | 14 ++++---- .../chunks/app/layout-05e5448bd170dbcb.js | 1 - .../chunks/app/layout-61827b157521da1b.js | 1 + ...a8e772a56b.js => page-104cada6b5e5b14c.js} | 2 +- ...d08f8be397.js => page-bad6cfbe58b9d19c.js} | 2 +- ...7c2bc2f150.js => page-68b04cd7217f38ce.js} | 2 +- ...915716.js => main-app-9b4fb13a7db53edf.js} | 2 +- ...761a436.js => webpack-e8ad0a25b0c46e0b.js} | 2 +- ...759ed931c00b2.css => 00256a1984d35914.css} | 2 +- .../static/media/05a31a2ca4975f99-s.woff2 | Bin 10496 -> 0 bytes .../static/media/26a46d62cd723877-s.woff2 | Bin 0 -> 18820 bytes .../static/media/513657b02c5c193f-s.woff2 | Bin 17612 -> 0 bytes .../static/media/51ed15f9841b9f9d-s.woff2 | Bin 22524 -> 0 bytes .../static/media/55c55f0601d81cf3-s.woff2 | Bin 0 -> 25908 bytes .../static/media/581909926a08bbc8-s.woff2 | Bin 0 -> 19072 bytes .../static/media/6d93bde91c0c2823-s.woff2 | Bin 0 -> 74316 bytes .../static/media/97e0cb1ae144a2a9-s.woff2 | Bin 0 -> 11220 bytes .../static/media/a34f9d1faa5f3315-s.p.woff2 | Bin 0 -> 48556 bytes .../static/media/c9a5bc6a7c948fb0-s.p.woff2 | Bin 46552 -> 0 bytes .../static/media/d6b16ce4a6175f26-s.woff2 | Bin 80044 -> 0 bytes .../static/media/df0a9ae256c0569c-s.woff2 | Bin 0 -> 10280 bytes .../static/media/ec159349637c90ad-s.woff2 | Bin 27316 -> 0 bytes .../static/media/fd4db3eb5472fc27-s.woff2 | Bin 12768 -> 0 bytes litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 4 +-- .../proxy/_experimental/out/model_hub.html | 1 + litellm/proxy/_experimental/out/model_hub.txt | 4 +-- .../proxy/_experimental/out/onboarding.html | 1 + .../proxy/_experimental/out/onboarding.txt | 4 +-- ui/litellm-dashboard/out/404.html | 1 + .../WeMIGILYzOYN-R9DXbvCD/_buildManifest.js | 1 + .../WeMIGILYzOYN-R9DXbvCD/_ssgManifest.js | 1 + .../static/chunks/131-3d2257b0ff5aadb2.js | 8 +++++ .../chunks/2f6dbc85-cac2949a76539886.js | 1 + .../chunks/3014691f-b24e8254c7593934.js | 1 + .../static/chunks/626-4e8df4039ecf4386.js | 13 +++++++ .../static/chunks/684-16b194c83a169f6d.js | 1 + .../static/chunks/69-8316d07d1f41e39f.js | 1 + .../static/chunks/777-9d9df0b75010dbf9.js | 1 + .../static/chunks/902-58bf23027703b2e8.js | 13 +++++++ .../chunks/app/_not-found-4163791cb6a88df1.js | 1 + .../chunks/app/layout-61827b157521da1b.js | 1 + .../app/model_hub/page-104cada6b5e5b14c.js | 1 + .../app/onboarding/page-bad6cfbe58b9d19c.js | 1 + .../chunks/app/page-68b04cd7217f38ce.js | 1 + .../chunks/fd9d1056-f593049e31b05aeb.js | 1 + .../chunks/framework-b370f160bb96059c.js | 33 ++++++++++++++++++ .../static/chunks/main-a61244f130fbf565.js | 1 + .../chunks/main-app-9b4fb13a7db53edf.js | 1 + .../chunks/pages/_app-d21e88acd55d90f1.js | 1 + .../chunks/pages/_error-d6107f1aac0c574c.js | 1 + .../chunks/polyfills-c67a75d1b6f99dc8.js | 1 + .../static/chunks/webpack-e8ad0a25b0c46e0b.js | 1 + .../out/_next/static/css/00256a1984d35914.css | 5 +++ .../static/media/26a46d62cd723877-s.woff2 | Bin 0 -> 18820 bytes .../static/media/55c55f0601d81cf3-s.woff2 | Bin 0 -> 25908 bytes .../static/media/581909926a08bbc8-s.woff2 | Bin 0 -> 19072 bytes .../static/media/6d93bde91c0c2823-s.woff2 | Bin 0 -> 74316 bytes .../static/media/97e0cb1ae144a2a9-s.woff2 | Bin 0 -> 11220 bytes .../static/media/a34f9d1faa5f3315-s.p.woff2 | Bin 0 -> 48556 bytes .../static/media/df0a9ae256c0569c-s.woff2 | Bin 0 -> 10280 bytes ui/litellm-dashboard/out/favicon.ico | Bin 0 -> 15406 bytes ui/litellm-dashboard/out/index.html | 1 + ui/litellm-dashboard/out/index.txt | 7 ++++ ui/litellm-dashboard/out/model_hub.html | 1 + ui/litellm-dashboard/out/model_hub.txt | 7 ++++ ui/litellm-dashboard/out/next.svg | 1 + ui/litellm-dashboard/out/onboarding.html | 1 + ui/litellm-dashboard/out/onboarding.txt | 7 ++++ ui/litellm-dashboard/out/vercel.svg | 1 + 73 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 litellm/proxy/_experimental/out/404.html rename litellm/proxy/_experimental/out/_next/static/{e-Zsp_y3gSAoiJHmJByXA => WeMIGILYzOYN-R9DXbvCD}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{e-Zsp_y3gSAoiJHmJByXA => WeMIGILYzOYN-R9DXbvCD}/_ssgManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/chunks/{626-fc3969bfc35ead00.js => 626-4e8df4039ecf4386.js} (82%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-05e5448bd170dbcb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-61827b157521da1b.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/{page-748a83a8e772a56b.js => page-104cada6b5e5b14c.js} (97%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/{page-884a15d08f8be397.js => page-bad6cfbe58b9d19c.js} (94%) rename litellm/proxy/_experimental/out/_next/static/chunks/app/{page-bd2e157c2bc2f150.js => page-68b04cd7217f38ce.js} (85%) rename litellm/proxy/_experimental/out/_next/static/chunks/{main-app-096338c8e1915716.js => main-app-9b4fb13a7db53edf.js} (54%) rename litellm/proxy/_experimental/out/_next/static/chunks/{webpack-b9c71b6f9761a436.js => webpack-e8ad0a25b0c46e0b.js} (98%) rename litellm/proxy/_experimental/out/_next/static/css/{ea3759ed931c00b2.css => 00256a1984d35914.css} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/05a31a2ca4975f99-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/26a46d62cd723877-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/513657b02c5c193f-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/51ed15f9841b9f9d-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/55c55f0601d81cf3-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/581909926a08bbc8-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/6d93bde91c0c2823-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/97e0cb1ae144a2a9-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/a34f9d1faa5f3315-s.p.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/d6b16ce4a6175f26-s.woff2 create mode 100644 litellm/proxy/_experimental/out/_next/static/media/df0a9ae256c0569c-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/ec159349637c90ad-s.woff2 delete mode 100644 litellm/proxy/_experimental/out/_next/static/media/fd4db3eb5472fc27-s.woff2 create mode 100644 litellm/proxy/_experimental/out/model_hub.html create mode 100644 litellm/proxy/_experimental/out/onboarding.html create mode 100644 ui/litellm-dashboard/out/404.html create mode 100644 ui/litellm-dashboard/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_buildManifest.js create mode 100644 ui/litellm-dashboard/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_ssgManifest.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/131-3d2257b0ff5aadb2.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/2f6dbc85-cac2949a76539886.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/3014691f-b24e8254c7593934.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/626-4e8df4039ecf4386.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/684-16b194c83a169f6d.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/69-8316d07d1f41e39f.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/777-9d9df0b75010dbf9.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/902-58bf23027703b2e8.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/_not-found-4163791cb6a88df1.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/layout-61827b157521da1b.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/model_hub/page-104cada6b5e5b14c.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/onboarding/page-bad6cfbe58b9d19c.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/page-68b04cd7217f38ce.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/fd9d1056-f593049e31b05aeb.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/framework-b370f160bb96059c.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/main-a61244f130fbf565.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/main-app-9b4fb13a7db53edf.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/pages/_app-d21e88acd55d90f1.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/pages/_error-d6107f1aac0c574c.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/webpack-e8ad0a25b0c46e0b.js create mode 100644 ui/litellm-dashboard/out/_next/static/css/00256a1984d35914.css create mode 100644 ui/litellm-dashboard/out/_next/static/media/26a46d62cd723877-s.woff2 create mode 100644 ui/litellm-dashboard/out/_next/static/media/55c55f0601d81cf3-s.woff2 create mode 100644 ui/litellm-dashboard/out/_next/static/media/581909926a08bbc8-s.woff2 create mode 100644 ui/litellm-dashboard/out/_next/static/media/6d93bde91c0c2823-s.woff2 create mode 100644 ui/litellm-dashboard/out/_next/static/media/97e0cb1ae144a2a9-s.woff2 create mode 100644 ui/litellm-dashboard/out/_next/static/media/a34f9d1faa5f3315-s.p.woff2 create mode 100644 ui/litellm-dashboard/out/_next/static/media/df0a9ae256c0569c-s.woff2 create mode 100644 ui/litellm-dashboard/out/favicon.ico create mode 100644 ui/litellm-dashboard/out/index.html create mode 100644 ui/litellm-dashboard/out/index.txt create mode 100644 ui/litellm-dashboard/out/model_hub.html create mode 100644 ui/litellm-dashboard/out/model_hub.txt create mode 100644 ui/litellm-dashboard/out/next.svg create mode 100644 ui/litellm-dashboard/out/onboarding.html create mode 100644 ui/litellm-dashboard/out/onboarding.txt create mode 100644 ui/litellm-dashboard/out/vercel.svg diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html new file mode 100644 index 000000000..223f5d80e --- /dev/null +++ b/litellm/proxy/_experimental/out/404.html @@ -0,0 +1 @@ +404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/e-Zsp_y3gSAoiJHmJByXA/_buildManifest.js b/litellm/proxy/_experimental/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_buildManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/e-Zsp_y3gSAoiJHmJByXA/_buildManifest.js rename to litellm/proxy/_experimental/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_buildManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/e-Zsp_y3gSAoiJHmJByXA/_ssgManifest.js b/litellm/proxy/_experimental/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_ssgManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/e-Zsp_y3gSAoiJHmJByXA/_ssgManifest.js rename to litellm/proxy/_experimental/out/_next/static/WeMIGILYzOYN-R9DXbvCD/_ssgManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/626-fc3969bfc35ead00.js b/litellm/proxy/_experimental/out/_next/static/chunks/626-4e8df4039ecf4386.js similarity index 82% rename from litellm/proxy/_experimental/out/_next/static/chunks/626-fc3969bfc35ead00.js rename to litellm/proxy/_experimental/out/_next/static/chunks/626-4e8df4039ecf4386.js index d57a4844d..9b88eaeab 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/626-fc3969bfc35ead00.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/626-4e8df4039ecf4386.js @@ -1,13 +1,13 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[626],{90507:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"}}]},name:"check",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},20383:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"}}]},name:"down",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},31413:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"ellipsis",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},41311:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"}}]},name:"eye",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},98786:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"minus-circle",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},74325:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"}},{tag:"path",attrs:{d:"M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"}}]},name:"plus",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},96871:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"}}]},name:"search",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},97766:function(e,t,n){n.d(t,{Z:function(){return l}});var r=n(14749),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"upload",theme:"outlined"},a=n(60688),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},54518:function(e,t,n){n.d(t,{Z:function(){return i}});var r=n(69703),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M11.9999 13.1714L16.9497 8.22168L18.3639 9.63589L11.9999 15.9999L5.63599 9.63589L7.0502 8.22168L11.9999 13.1714Z"}))}},8903:function(e,t,n){n.d(t,{Z:function(){return i}});var r=n(69703),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"}))}},25707:function(e,t,n){n.d(t,{Z:function(){return et}});var r=n(69703),o=n(2265),i=n(26587),a=n(65558),l=n(75504),c=n(30638),s=n(80509),u=n.n(s),d=n(5037),f=n.n(d),p=n(71292),h=n.n(p),m=n(96240),v=n.n(m),g=n(93574),y=n.n(g),b=n(72996),x=n(84487),w=n(7986),O=n(71594),E=n(68139),S=n(20757),k=n(9586),C=n(765),j=["layout","type","stroke","connectNulls","isRange","ref"];function P(e){return(P="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function A(){return(A=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(i,j));return o.createElement(w.m,{clipPath:n?"url(#clipPath-".concat(r,")"):null},o.createElement(b.H,A({},(0,C.L6)(d,!0),{points:e,connectNulls:s,type:l,baseLine:t,layout:a,stroke:"none",className:"recharts-area-area"})),"none"!==c&&o.createElement(b.H,A({},(0,C.L6)(this.props,!1),{className:"recharts-area-curve",layout:a,type:l,connectNulls:s,fill:"none",points:e})),"none"!==c&&u&&o.createElement(b.H,A({},(0,C.L6)(this.props,!1),{className:"recharts-area-curve",layout:a,type:l,connectNulls:s,fill:"none",points:t})))}},{key:"renderAreaWithAnimation",value:function(e,t){var n=this,r=this.props,i=r.points,a=r.baseLine,l=r.isAnimationActive,s=r.animationBegin,u=r.animationDuration,d=r.animationEasing,f=r.animationId,p=this.state,m=p.prevPoints,g=p.prevBaseLine;return o.createElement(c.ZP,{begin:s,duration:u,isActive:l,easing:d,from:{t:0},to:{t:1},key:"area-".concat(f),onAnimationEnd:this.handleAnimationEnd,onAnimationStart:this.handleAnimationStart},function(r){var l=r.t;if(m){var c,s=m.length/i.length,u=i.map(function(e,t){var n=Math.floor(t*s);if(m[n]){var r=m[n],o=(0,S.k4)(r.x,e.x),i=(0,S.k4)(r.y,e.y);return M(M({},e),{},{x:o(l),y:i(l)})}return e});return c=(0,S.hj)(a)&&"number"==typeof a?(0,S.k4)(g,a)(l):h()(a)||v()(a)?(0,S.k4)(g,0)(l):a.map(function(e,t){var n=Math.floor(t*s);if(g[n]){var r=g[n],o=(0,S.k4)(r.x,e.x),i=(0,S.k4)(r.y,e.y);return M(M({},e),{},{x:o(l),y:i(l)})}return e}),n.renderAreaStatically(u,c,e,t)}return o.createElement(w.m,null,o.createElement("defs",null,o.createElement("clipPath",{id:"animationClipPath-".concat(t)},n.renderClipRect(l))),o.createElement(w.m,{clipPath:"url(#animationClipPath-".concat(t,")")},n.renderAreaStatically(i,a,e,t)))})}},{key:"renderArea",value:function(e,t){var n=this.props,r=n.points,o=n.baseLine,i=n.isAnimationActive,a=this.state,l=a.prevPoints,c=a.prevBaseLine,s=a.totalLength;return i&&r&&r.length&&(!l&&s>0||!y()(l,r)||!y()(c,o))?this.renderAreaWithAnimation(e,t):this.renderAreaStatically(r,o,e,t)}},{key:"render",value:function(){var e,t=this.props,n=t.hide,r=t.dot,i=t.points,a=t.className,c=t.top,s=t.left,u=t.xAxis,d=t.yAxis,f=t.width,p=t.height,m=t.isAnimationActive,v=t.id;if(n||!i||!i.length)return null;var g=this.state.isAnimationFinished,y=1===i.length,b=(0,l.Z)("recharts-area",a),x=u&&u.allowDataOverflow,E=d&&d.allowDataOverflow,S=x||E,k=h()(v)?this.id:v,j=null!==(e=(0,C.L6)(r,!1))&&void 0!==e?e:{r:3,strokeWidth:2},P=j.r,A=j.strokeWidth,T=((0,C.$k)(r)?r:{}).clipDot,M=void 0===T||T,N=2*(void 0===P?3:P)+(void 0===A?2:A);return o.createElement(w.m,{className:b},x||E?o.createElement("defs",null,o.createElement("clipPath",{id:"clipPath-".concat(k)},o.createElement("rect",{x:x?s:s-f/2,y:E?c:c-p/2,width:x?f:2*f,height:E?p:2*p})),!M&&o.createElement("clipPath",{id:"clipPath-dots-".concat(k)},o.createElement("rect",{x:s-N/2,y:c-N/2,width:f+N,height:p+N}))):null,y?null:this.renderArea(S,k),(r||y)&&this.renderDots(S,M,k),(!m||g)&&O.e.renderCallByParent(this.props,i))}}],r=[{key:"getDerivedStateFromProps",value:function(e,t){return e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curPoints:e.points,curBaseLine:e.baseLine,prevPoints:t.curPoints,prevBaseLine:t.curBaseLine}:e.points!==t.curPoints||e.baseLine!==t.curBaseLine?{curPoints:e.points,curBaseLine:e.baseLine}:null}}],n&&N(a.prototype,n),r&&N(a,r),Object.defineProperty(a,"prototype",{writable:!1}),a}(o.PureComponent);D(L,"displayName","Area"),D(L,"defaultProps",{stroke:"#3182bd",fill:"#3182bd",fillOpacity:.6,xAxisId:0,yAxisId:0,legendType:"line",connectNulls:!1,points:[],dot:!1,activeDot:!0,hide:!1,isAnimationActive:!E.x.isSsr,animationBegin:0,animationDuration:1500,animationEasing:"ease"}),D(L,"getBaseValue",function(e,t,n,r){var o=e.layout,i=e.baseValue,a=t.props.baseValue,l=null!=a?a:i;if((0,S.hj)(l)&&"number"==typeof l)return l;var c="horizontal"===o?r:n,s=c.scale.domain();if("number"===c.type){var u=Math.max(s[0],s[1]),d=Math.min(s[0],s[1]);return"dataMin"===l?d:"dataMax"===l?u:u<0?u:Math.max(Math.min(s[0],s[1]),0)}return"dataMin"===l?s[0]:"dataMax"===l?s[1]:s[0]}),D(L,"getComposedData",function(e){var t,n=e.props,r=e.item,o=e.xAxis,i=e.yAxis,a=e.xAxisTicks,l=e.yAxisTicks,c=e.bandSize,s=e.dataKey,u=e.stackedData,d=e.dataStartIndex,f=e.displayedData,p=e.offset,h=n.layout,m=u&&u.length,v=L.getBaseValue(n,r,o,i),g="horizontal"===h,y=!1,b=f.map(function(e,t){m?n=u[d+t]:Array.isArray(n=(0,k.F$)(e,s))?y=!0:n=[v,n];var n,r=null==n[1]||m&&null==(0,k.F$)(e,s);return g?{x:(0,k.Hv)({axis:o,ticks:a,bandSize:c,entry:e,index:t}),y:r?null:i.scale(n[1]),value:n,payload:e}:{x:r?null:o.scale(n[1]),y:(0,k.Hv)({axis:i,ticks:l,bandSize:c,entry:e,index:t}),value:n,payload:e}});return t=m||y?b.map(function(e){var t=Array.isArray(e.value)?e.value[0]:null;return g?{x:e.x,y:null!=t&&null!=e.y?i.scale(t):null}:{x:null!=t?o.scale(t):null,y:e.y}}):g?i.scale(v):o.scale(v),M({points:b,baseLine:t,layout:h,isRange:y},p)}),D(L,"renderDotItem",function(e,t){return o.isValidElement(e)?o.cloneElement(e,t):u()(e)?e(t):o.createElement(x.o,A({},t,{className:"recharts-area-dot"}))});var B=n(23356),z=n(22983),F=n(12627),H=(0,a.z)({chartName:"AreaChart",GraphicalChild:L,axisComponents:[{axisType:"xAxis",AxisComp:B.K},{axisType:"yAxis",AxisComp:z.B}],formatAxisMap:F.t9}),W=n(38333),q=n(10166),U=n(94866),V=n(98061),K=n(17280),Y=n(30470),X=n(77448),G=n(36342),$=n(54942),Q=n(2898),J=n(99250),ee=n(65492);let et=o.forwardRef((e,t)=>{let{data:n=[],categories:a=[],index:l,stack:c=!1,colors:s=Q.s,valueFormatter:u=ee.Cj,startEndOnly:d=!1,showXAxis:f=!0,showYAxis:p=!0,yAxisWidth:h=56,intervalType:m="equidistantPreserveStart",showAnimation:v=!1,animationDuration:g=900,showTooltip:y=!0,showLegend:b=!0,showGridLines:w=!0,showGradient:O=!0,autoMinValue:E=!1,curveType:S="linear",minValue:k,maxValue:C,connectNulls:j=!1,allowDecimals:P=!0,noDataText:A,className:T,onValueChange:M,enableLegendSlider:N=!1,customTooltip:I,rotateLabelX:R,tickGap:_=5}=e,D=(0,r._T)(e,["data","categories","index","stack","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","showAnimation","animationDuration","showTooltip","showLegend","showGridLines","showGradient","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap"]),Z=(f||p)&&(!d||p)?20:0,[F,et]=(0,o.useState)(60),[en,er]=(0,o.useState)(void 0),[eo,ei]=(0,o.useState)(void 0),ea=(0,G.me)(a,s),el=(0,G.i4)(E,k,C),ec=!!M;function es(e){ec&&(e===eo&&!en||(0,G.FB)(n,e)&&en&&en.dataKey===e?(ei(void 0),null==M||M(null)):(ei(e),null==M||M({eventType:"category",categoryClicked:e})),er(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,J.q)("w-full h-80",T)},D),o.createElement(i.h,{className:"h-full w-full"},(null==n?void 0:n.length)?o.createElement(H,{data:n,onClick:ec&&(eo||en)?()=>{er(void 0),ei(void 0),null==M||M(null)}:void 0},w?o.createElement(W.q,{className:(0,J.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(B.K,{padding:{left:Z,right:Z},hide:!f,dataKey:l,tick:{transform:"translate(0, 6)"},ticks:d?[n[0][l],n[n.length-1][l]]:void 0,fill:"",stroke:"",className:(0,J.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),interval:d?"preserveStartEnd":m,tickLine:!1,axisLine:!1,minTickGap:_,angle:null==R?void 0:R.angle,dy:null==R?void 0:R.verticalShift,height:null==R?void 0:R.xAxisHeight}),o.createElement(z.B,{width:h,hide:!p,axisLine:!1,tickLine:!1,type:"number",domain:el,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,J.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:u,allowDecimals:P}),o.createElement(q.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:y?e=>{let{active:t,payload:n,label:r}=e;return I?o.createElement(I,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=ea.get(e.dataKey))&&void 0!==t?t:$.fr.Gray})}),active:t,label:r}):o.createElement(Y.ZP,{active:t,payload:n,label:r,valueFormatter:u,categoryColors:ea})}:o.createElement(o.Fragment,null),position:{y:0}}),b?o.createElement(U.D,{verticalAlign:"top",height:F,content:e=>{let{payload:t}=e;return(0,K.Z)({payload:t},ea,et,eo,ec?e=>es(e):void 0,N)}}):null,a.map(e=>{var t,n;return o.createElement("defs",{key:e},O?o.createElement("linearGradient",{className:(0,ee.bM)(null!==(t=ea.get(e))&&void 0!==t?t:$.fr.Gray,Q.K.text).textColor,id:ea.get(e),x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{offset:"5%",stopColor:"currentColor",stopOpacity:en||eo&&eo!==e?.15:.4}),o.createElement("stop",{offset:"95%",stopColor:"currentColor",stopOpacity:0})):o.createElement("linearGradient",{className:(0,ee.bM)(null!==(n=ea.get(e))&&void 0!==n?n:$.fr.Gray,Q.K.text).textColor,id:ea.get(e),x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{stopColor:"currentColor",stopOpacity:en||eo&&eo!==e?.1:.3})))}),a.map(e=>{var t;return o.createElement(L,{className:(0,ee.bM)(null!==(t=ea.get(e))&&void 0!==t?t:$.fr.Gray,Q.K.text).strokeColor,strokeOpacity:en||eo&&eo!==e?.3:1,activeDot:e=>{var t;let{cx:r,cy:i,stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,dataKey:u}=e;return o.createElement(x.o,{className:(0,J.q)("stroke-tremor-background dark:stroke-dark-tremor-background",M?"cursor-pointer":"",(0,ee.bM)(null!==(t=ea.get(u))&&void 0!==t?t:$.fr.Gray,Q.K.text).fillColor),cx:r,cy:i,r:5,fill:"",stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,onClick:(t,r)=>{r.stopPropagation(),ec&&(e.index===(null==en?void 0:en.index)&&e.dataKey===(null==en?void 0:en.dataKey)||(0,G.FB)(n,e.dataKey)&&eo&&eo===e.dataKey?(ei(void 0),er(void 0),null==M||M(null)):(ei(e.dataKey),er({index:e.index,dataKey:e.dataKey}),null==M||M(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var r;let{stroke:i,strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,cx:s,cy:u,dataKey:d,index:f}=t;return(0,G.FB)(n,e)&&!(en||eo&&eo!==e)||(null==en?void 0:en.index)===f&&(null==en?void 0:en.dataKey)===e?o.createElement(x.o,{key:f,cx:s,cy:u,r:5,stroke:i,fill:"",strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,className:(0,J.q)("stroke-tremor-background dark:stroke-dark-tremor-background",M?"cursor-pointer":"",(0,ee.bM)(null!==(r=ea.get(d))&&void 0!==r?r:$.fr.Gray,Q.K.text).fillColor)}):o.createElement(o.Fragment,{key:f})},key:e,name:e,type:S,dataKey:e,stroke:"",fill:"url(#".concat(ea.get(e),")"),strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:v,animationDuration:g,stackId:c?"a":void 0,connectNulls:j})}),M?a.map(e=>o.createElement(V.x,{className:(0,J.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:S,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:j,onClick:(e,t)=>{t.stopPropagation();let{name:n}=e;es(n)}})):null):o.createElement(X.Z,{noDataText:A})))});et.displayName="AreaChart"},44041:function(e,t,n){n.d(t,{Z:function(){return E}});var r=n(69703),o=n(54942),i=n(2898),a=n(99250),l=n(65492),c=n(2265),s=n(26587),u=n(65558),d=n(78720),f=n(23356),p=n(22983),h=n(12627),m=(0,u.z)({chartName:"BarChart",GraphicalChild:d.$,defaultTooltipEventType:"axis",validateTooltipEventTypes:["axis","item"],axisComponents:[{axisType:"xAxis",AxisComp:f.K},{axisType:"yAxis",AxisComp:p.B}],formatAxisMap:h.t9}),v=n(38333),g=n(10166),y=n(94866),b=n(17280),x=n(30470),w=n(77448),O=n(36342);let E=c.forwardRef((e,t)=>{let{data:n=[],categories:u=[],index:h,colors:E=i.s,valueFormatter:S=l.Cj,layout:k="horizontal",stack:C=!1,relative:j=!1,startEndOnly:P=!1,animationDuration:A=900,showAnimation:T=!1,showXAxis:M=!0,showYAxis:N=!0,yAxisWidth:I=56,intervalType:R="equidistantPreserveStart",showTooltip:_=!0,showLegend:D=!0,showGridLines:Z=!0,autoMinValue:L=!1,minValue:B,maxValue:z,allowDecimals:F=!0,noDataText:H,onValueChange:W,enableLegendSlider:q=!1,customTooltip:U,rotateLabelX:V,tickGap:K=5,className:Y}=e,X=(0,r._T)(e,["data","categories","index","colors","valueFormatter","layout","stack","relative","startEndOnly","animationDuration","showAnimation","showXAxis","showYAxis","yAxisWidth","intervalType","showTooltip","showLegend","showGridLines","autoMinValue","minValue","maxValue","allowDecimals","noDataText","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap","className"]),G=M||N?20:0,[$,Q]=(0,c.useState)(60),J=(0,O.me)(u,E),[ee,et]=c.useState(void 0),[en,er]=(0,c.useState)(void 0),eo=!!W;function ei(e,t,n){var r,o,i,a;n.stopPropagation(),W&&((0,O.vZ)(ee,Object.assign(Object.assign({},e.payload),{value:e.value}))?(er(void 0),et(void 0),null==W||W(null)):(er(null===(o=null===(r=e.tooltipPayload)||void 0===r?void 0:r[0])||void 0===o?void 0:o.dataKey),et(Object.assign(Object.assign({},e.payload),{value:e.value})),null==W||W(Object.assign({eventType:"bar",categoryClicked:null===(a=null===(i=e.tooltipPayload)||void 0===i?void 0:i[0])||void 0===a?void 0:a.dataKey},e.payload))))}let ea=(0,O.i4)(L,B,z);return c.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-80",Y)},X),c.createElement(s.h,{className:"h-full w-full"},(null==n?void 0:n.length)?c.createElement(m,{data:n,stackOffset:C?"sign":j?"expand":"none",layout:"vertical"===k?"vertical":"horizontal",onClick:eo&&(en||ee)?()=>{et(void 0),er(void 0),null==W||W(null)}:void 0},Z?c.createElement(v.q,{className:(0,a.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:"vertical"!==k,vertical:"vertical"===k}):null,"vertical"!==k?c.createElement(f.K,{padding:{left:G,right:G},hide:!M,dataKey:h,interval:P?"preserveStartEnd":R,tick:{transform:"translate(0, 6)"},ticks:P?[n[0][h],n[n.length-1][h]]:void 0,fill:"",stroke:"",className:(0,a.q)("mt-4 text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,angle:null==V?void 0:V.angle,dy:null==V?void 0:V.verticalShift,height:null==V?void 0:V.xAxisHeight,minTickGap:K}):c.createElement(f.K,{hide:!M,type:"number",tick:{transform:"translate(-3, 0)"},domain:ea,fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,tickFormatter:S,minTickGap:K,allowDecimals:F,angle:null==V?void 0:V.angle,dy:null==V?void 0:V.verticalShift,height:null==V?void 0:V.xAxisHeight}),"vertical"!==k?c.createElement(p.B,{width:I,hide:!N,axisLine:!1,tickLine:!1,type:"number",domain:ea,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:j?e=>"".concat((100*e).toString()," %"):S,allowDecimals:F}):c.createElement(p.B,{width:I,hide:!N,dataKey:h,axisLine:!1,tickLine:!1,ticks:P?[n[0][h],n[n.length-1][h]]:void 0,type:"category",interval:"preserveStartEnd",tick:{transform:"translate(0, 6)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content")}),c.createElement(g.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{fill:"#d1d5db",opacity:"0.15"},content:_?e=>{let{active:t,payload:n,label:r}=e;return U?c.createElement(U,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=J.get(e.dataKey))&&void 0!==t?t:o.fr.Gray})}),active:t,label:r}):c.createElement(x.ZP,{active:t,payload:n,label:r,valueFormatter:S,categoryColors:J})}:c.createElement(c.Fragment,null),position:{y:0}}),D?c.createElement(y.D,{verticalAlign:"top",height:$,content:e=>{let{payload:t}=e;return(0,b.Z)({payload:t},J,Q,en,eo?e=>{eo&&(e!==en||ee?(er(e),null==W||W({eventType:"category",categoryClicked:e})):(er(void 0),null==W||W(null)),et(void 0))}:void 0,q)}}):null,u.map(e=>{var t;return c.createElement(d.$,{className:(0,a.q)((0,l.bM)(null!==(t=J.get(e))&&void 0!==t?t:o.fr.Gray,i.K.background).fillColor,W?"cursor-pointer":""),key:e,name:e,type:"linear",stackId:C||j?"a":void 0,dataKey:e,fill:"",isAnimationActive:T,animationDuration:A,shape:e=>((e,t,n,r)=>{let{fillOpacity:o,name:i,payload:a,value:l}=e,{x:s,width:u,y:d,height:f}=e;return"horizontal"===r&&f<0?(d+=f,f=Math.abs(f)):"vertical"===r&&u<0&&(s+=u,u=Math.abs(u)),c.createElement("rect",{x:s,y:d,width:u,height:f,opacity:t||n&&n!==i?(0,O.vZ)(t,Object.assign(Object.assign({},a),{value:l}))?o:.3:o})})(e,ee,en,k),onClick:ei})})):c.createElement(w.Z,{noDataText:H})))});E.displayName="BarChart"},52703:function(e,t,n){n.d(t,{Z:function(){return eB}});var r=n(69703),o=n(54942),i=n(2898),a=n(99250),l=n(65492),c=n(2265),s=n(15573),u=n(26587),d=n(65558),f=n(80509),p=n.n(f),h=n(7986),m=n(84487),v=n(75504),g=n(765),y=["points","className","baseLinePoints","connectNulls"];function b(){return(b=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:[],t=[[]];return e.forEach(function(e){O(e)?t[t.length-1].push(e):t[t.length-1].length>0&&t.push([])}),O(e[0])&&t[t.length-1].push(e[0]),t[t.length-1].length<=0&&(t=t.slice(0,-1)),t},S=function(e,t){var n=E(e);t&&(n=[n.reduce(function(e,t){return[].concat(x(e),x(t))},[])]);var r=n.map(function(e){return e.reduce(function(e,t,n){return"".concat(e).concat(0===n?"M":"L").concat(t.x,",").concat(t.y)},"")}).join("");return 1===n.length?"".concat(r,"Z"):r},k=function(e,t,n){var r=S(e,n);return"".concat("Z"===r.slice(-1)?r.slice(0,-1):r,"L").concat(S(t.reverse(),n).slice(1))},C=function(e){var t=e.points,n=e.className,r=e.baseLinePoints,o=e.connectNulls,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,y);if(!t||!t.length)return null;var a=(0,v.Z)("recharts-polygon",n);if(r&&r.length){var l=i.stroke&&"none"!==i.stroke,s=k(t,r,o);return c.createElement("g",{className:a},c.createElement("path",b({},(0,g.L6)(i,!0),{fill:"Z"===s.slice(-1)?i.fill:"none",stroke:"none",d:s})),l?c.createElement("path",b({},(0,g.L6)(i,!0),{fill:"none",d:S(t,o)})):null,l?c.createElement("path",b({},(0,g.L6)(i,!0),{fill:"none",d:S(r,o)})):null)}var u=S(t,o);return c.createElement("path",b({},(0,g.L6)(i,!0),{fill:"Z"===u.slice(-1)?i.fill:"none",className:a,d:u}))},j=n(8447),P=n(77749),A=n(57609);function T(e){return(T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function M(){return(M=Object.assign?Object.assign.bind():function(e){for(var t=1;t1e-5?"outer"===t?"start":"end":n<-.00001?"outer"===t?"end":"start":"middle"}},{key:"renderAxisLine",value:function(){var e=this.props,t=e.cx,n=e.cy,r=e.radius,o=e.axisLine,i=e.axisLineType,a=I(I({},(0,g.L6)(this.props,!1)),{},{fill:"none"},(0,g.L6)(o,!1));if("circle"===i)return c.createElement(m.o,M({className:"recharts-polar-angle-axis-line"},a,{cx:t,cy:n,r:r}));var l=this.props.ticks.map(function(e){return(0,A.op)(t,n,r,e.coordinate)});return c.createElement(C,M({className:"recharts-polar-angle-axis-line"},a,{points:l}))}},{key:"renderTicks",value:function(){var e=this,t=this.props,n=t.ticks,r=t.tick,o=t.tickLine,a=t.tickFormatter,l=t.stroke,s=(0,g.L6)(this.props,!1),u=(0,g.L6)(r,!1),d=I(I({},s),{},{fill:"none"},(0,g.L6)(o,!1)),f=n.map(function(t,n){var f=e.getTickLineCoord(t),p=I(I(I({textAnchor:e.getTickTextAnchor(t)},s),{},{stroke:"none",fill:l},u),{},{index:n,payload:t,x:f.x2,y:f.y2});return c.createElement(h.m,M({className:"recharts-polar-angle-axis-tick",key:"tick-".concat(t.coordinate)},(0,P.bw)(e.props,t,n)),o&&c.createElement("line",M({className:"recharts-polar-angle-axis-tick-line"},d,f)),r&&i.renderTickItem(r,p,a?a(t.value,n):t.value))});return c.createElement(h.m,{className:"recharts-polar-angle-axis-ticks"},f)}},{key:"render",value:function(){var e=this.props,t=e.ticks,n=e.radius,r=e.axisLine;return!(n<=0)&&t&&t.length?c.createElement(h.m,{className:"recharts-polar-angle-axis"},r&&this.renderAxisLine(),this.renderTicks()):null}}],r=[{key:"renderTickItem",value:function(e,t,n){return c.isValidElement(e)?c.cloneElement(e,t):p()(e)?e(t):c.createElement(j.x,M({},t,{className:"recharts-polar-angle-axis-tick-value"}),n)}}],n&&R(i.prototype,n),r&&R(i,r),Object.defineProperty(i,"prototype",{writable:!1}),i}(c.PureComponent);Z(z,"displayName","PolarAngleAxis"),Z(z,"axisType","angleAxis"),Z(z,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var F=n(99648),H=n.n(F),W=n(59713),q=n.n(W),U=n(11102),V=["cx","cy","angle","ticks","axisLine"],K=["ticks","tick","angle","tickFormatter","stroke"];function Y(e){return(Y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function X(){return(X=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function J(e,t){for(var n=0;n0?el()(e,"paddingAngle",0):0;if(n){var l=(0,ev.k4)(n.endAngle-n.startAngle,e.endAngle-e.startAngle),c=eE(eE({},e),{},{startAngle:i+a,endAngle:i+l(r)+a});o.push(c),i=c.endAngle}else{var s=e.endAngle,d=e.startAngle,f=(0,ev.k4)(0,s-d)(r),p=eE(eE({},e),{},{startAngle:i+a,endAngle:i+f+a});o.push(p),i=p.endAngle}}),c.createElement(h.m,null,e.renderSectorsStatically(o))})}},{key:"attachKeyboardHandlers",value:function(e){var t=this;e.onkeydown=function(e){if(!e.altKey)switch(e.key){case"ArrowLeft":var n=++t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[n].focus(),t.setState({sectorToFocus:n});break;case"ArrowRight":var r=--t.state.sectorToFocus<0?t.sectorRefs.length-1:t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[r].focus(),t.setState({sectorToFocus:r});break;case"Escape":t.sectorRefs[t.state.sectorToFocus].blur(),t.setState({sectorToFocus:0})}}}},{key:"renderSectors",value:function(){var e=this.props,t=e.sectors,n=e.isAnimationActive,r=this.state.prevSectors;return n&&t&&t.length&&(!r||!es()(r,t))?this.renderSectorsWithAnimation():this.renderSectorsStatically(t)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var e=this,t=this.props,n=t.hide,r=t.sectors,o=t.className,i=t.label,a=t.cx,l=t.cy,s=t.innerRadius,u=t.outerRadius,d=t.isAnimationActive,f=this.state.isAnimationFinished;if(n||!r||!r.length||!(0,ev.hj)(a)||!(0,ev.hj)(l)||!(0,ev.hj)(s)||!(0,ev.hj)(u))return null;var p=(0,v.Z)("recharts-pie",o);return c.createElement(h.m,{tabIndex:this.props.rootTabIndex,className:p,ref:function(t){e.pieRef=t}},this.renderSectors(),i&&this.renderLabels(r),U._.renderCallByParent(this.props,null,!1),(!d||f)&&ep.e.renderCallByParent(this.props,r,!1))}}],r=[{key:"getDerivedStateFromProps",value:function(e,t){return t.prevIsAnimationActive!==e.isAnimationActive?{prevIsAnimationActive:e.isAnimationActive,prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:[],isAnimationFinished:!0}:e.isAnimationActive&&e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:t.curSectors,isAnimationFinished:!0}:e.sectors!==t.curSectors?{curSectors:e.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(e,t){return e>t?"start":e=360?x:x-1)*u,O=i.reduce(function(e,t){var n=(0,eg.F$)(t,b,0);return e+((0,ev.hj)(n)?n:0)},0);return O>0&&(t=i.map(function(e,t){var r,o=(0,eg.F$)(e,b,0),i=(0,eg.F$)(e,f,t),a=((0,ev.hj)(o)?o:0)/O,s=(r=t?n.endAngle+(0,ev.uY)(g)*u*(0!==o?1:0):c)+(0,ev.uY)(g)*((0!==o?m:0)+a*w),d=(r+s)/2,p=(v.innerRadius+v.outerRadius)/2,y=[{name:i,value:o,payload:e,dataKey:b,type:h}],x=(0,A.op)(v.cx,v.cy,p,d);return n=eE(eE(eE({percent:a,cornerRadius:l,name:i,tooltipPayload:y,midAngle:d,middleRadius:p,tooltipPosition:x},e),v),{},{value:(0,eg.F$)(e,b),startAngle:r,endAngle:s,payload:e,paddingAngle:(0,ev.uY)(g)*u})})),eE(eE({},v),{},{sectors:t,data:i})});var eM=(0,d.z)({chartName:"PieChart",GraphicalChild:eT,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:z},{axisType:"radiusAxis",AxisComp:eo}],formatAxisMap:A.t9,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}}),eN=n(10166),eI=n(77448),eR=n(30470);let e_=e=>{let{active:t,payload:n,valueFormatter:r}=e;if(t&&(null==n?void 0:n[0])){let e=null==n?void 0:n[0];return c.createElement(eR.$B,null,c.createElement("div",{className:(0,a.q)("px-4 py-2")},c.createElement(eR.zX,{value:r(e.value),name:e.name,color:e.payload.color})))}return null},eD=(e,t)=>e.map((e,n)=>{let r=ne||t((0,l.vP)(n.map(e=>e[r]))),eL=e=>{let{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l}=e;return c.createElement("g",null,c.createElement(s.L,{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l,fill:"",opacity:.3,style:{outline:"none"}}))},eB=c.forwardRef((e,t)=>{let{data:n=[],category:s="value",index:d="name",colors:f=i.s,variant:p="donut",valueFormatter:h=l.Cj,label:m,showLabel:v=!0,animationDuration:g=900,showAnimation:y=!1,showTooltip:b=!0,noDataText:x,onValueChange:w,customTooltip:O,className:E}=e,S=(0,r._T)(e,["data","category","index","colors","variant","valueFormatter","label","showLabel","animationDuration","showAnimation","showTooltip","noDataText","onValueChange","customTooltip","className"]),k="donut"==p,C=eZ(m,h,n,s),[j,P]=c.useState(void 0),A=!!w;return(0,c.useEffect)(()=>{let e=document.querySelectorAll(".recharts-pie-sector");e&&e.forEach(e=>{e.setAttribute("style","outline: none")})},[j]),c.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-40",E)},S),c.createElement(u.h,{className:"h-full w-full"},(null==n?void 0:n.length)?c.createElement(eM,{onClick:A&&j?()=>{P(void 0),null==w||w(null)}:void 0,margin:{top:0,left:0,right:0,bottom:0}},v&&k?c.createElement("text",{className:(0,a.q)("fill-tremor-content-emphasis","dark:fill-dark-tremor-content-emphasis"),x:"50%",y:"50%",textAnchor:"middle",dominantBaseline:"middle"},C):null,c.createElement(eT,{className:(0,a.q)("stroke-tremor-background dark:stroke-dark-tremor-background",w?"cursor-pointer":"cursor-default"),data:eD(n,f),cx:"50%",cy:"50%",startAngle:90,endAngle:-270,innerRadius:k?"75%":"0%",outerRadius:"100%",stroke:"",strokeLinejoin:"round",dataKey:s,nameKey:d,isAnimationActive:y,animationDuration:g,onClick:function(e,t,n){n.stopPropagation(),A&&(j===t?(P(void 0),null==w||w(null)):(P(t),null==w||w(Object.assign({eventType:"slice"},e.payload.payload))))},activeIndex:j,inactiveShape:eL,style:{outline:"none"}}),c.createElement(eN.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,content:b?e=>{var t;let{active:n,payload:r}=e;return O?c.createElement(O,{payload:null==r?void 0:r.map(e=>{var t,n,i;return Object.assign(Object.assign({},e),{color:null!==(i=null===(n=null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.payload)||void 0===n?void 0:n.color)&&void 0!==i?i:o.fr.Gray})}),active:n,label:null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.name}):c.createElement(e_,{active:n,payload:r,valueFormatter:h})}:c.createElement(c.Fragment,null)})):c.createElement(eI.Z,{noDataText:x})))});eB.displayName="DonutChart"},91118:function(e,t,n){n.d(t,{Z:function(){return S}});var r=n(69703),o=n(2265),i=n(26587),a=n(65558),l=n(98061),c=n(23356),s=n(22983),u=n(12627),d=(0,a.z)({chartName:"LineChart",GraphicalChild:l.x,axisComponents:[{axisType:"xAxis",AxisComp:c.K},{axisType:"yAxis",AxisComp:s.B}],formatAxisMap:u.t9}),f=n(38333),p=n(10166),h=n(94866),m=n(84487),v=n(17280),g=n(30470),y=n(77448),b=n(36342),x=n(54942),w=n(2898),O=n(99250),E=n(65492);let S=o.forwardRef((e,t)=>{let{data:n=[],categories:a=[],index:u,colors:S=w.s,valueFormatter:k=E.Cj,startEndOnly:C=!1,showXAxis:j=!0,showYAxis:P=!0,yAxisWidth:A=56,intervalType:T="equidistantPreserveStart",animationDuration:M=900,showAnimation:N=!1,showTooltip:I=!0,showLegend:R=!0,showGridLines:_=!0,autoMinValue:D=!1,curveType:Z="linear",minValue:L,maxValue:B,connectNulls:z=!1,allowDecimals:F=!0,noDataText:H,className:W,onValueChange:q,enableLegendSlider:U=!1,customTooltip:V,rotateLabelX:K,tickGap:Y=5}=e,X=(0,r._T)(e,["data","categories","index","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","animationDuration","showAnimation","showTooltip","showLegend","showGridLines","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap"]),G=j||P?20:0,[$,Q]=(0,o.useState)(60),[J,ee]=(0,o.useState)(void 0),[et,en]=(0,o.useState)(void 0),er=(0,b.me)(a,S),eo=(0,b.i4)(D,L,B),ei=!!q;function ea(e){ei&&(e===et&&!J||(0,b.FB)(n,e)&&J&&J.dataKey===e?(en(void 0),null==q||q(null)):(en(e),null==q||q({eventType:"category",categoryClicked:e})),ee(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,O.q)("w-full h-80",W)},X),o.createElement(i.h,{className:"h-full w-full"},(null==n?void 0:n.length)?o.createElement(d,{data:n,onClick:ei&&(et||J)?()=>{ee(void 0),en(void 0),null==q||q(null)}:void 0},_?o.createElement(f.q,{className:(0,O.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(c.K,{padding:{left:G,right:G},hide:!j,dataKey:u,interval:C?"preserveStartEnd":T,tick:{transform:"translate(0, 6)"},ticks:C?[n[0][u],n[n.length-1][u]]:void 0,fill:"",stroke:"",className:(0,O.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,minTickGap:Y,angle:null==K?void 0:K.angle,dy:null==K?void 0:K.verticalShift,height:null==K?void 0:K.xAxisHeight}),o.createElement(s.B,{width:A,hide:!P,axisLine:!1,tickLine:!1,type:"number",domain:eo,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,O.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:k,allowDecimals:F}),o.createElement(p.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:I?e=>{let{active:t,payload:n,label:r}=e;return V?o.createElement(V,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=er.get(e.dataKey))&&void 0!==t?t:x.fr.Gray})}),active:t,label:r}):o.createElement(g.ZP,{active:t,payload:n,label:r,valueFormatter:k,categoryColors:er})}:o.createElement(o.Fragment,null),position:{y:0}}),R?o.createElement(h.D,{verticalAlign:"top",height:$,content:e=>{let{payload:t}=e;return(0,v.Z)({payload:t},er,Q,et,ei?e=>ea(e):void 0,U)}}):null,a.map(e=>{var t;return o.createElement(l.x,{className:(0,O.q)((0,E.bM)(null!==(t=er.get(e))&&void 0!==t?t:x.fr.Gray,w.K.text).strokeColor),strokeOpacity:J||et&&et!==e?.3:1,activeDot:e=>{var t;let{cx:r,cy:i,stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,dataKey:u}=e;return o.createElement(m.o,{className:(0,O.q)("stroke-tremor-background dark:stroke-dark-tremor-background",q?"cursor-pointer":"",(0,E.bM)(null!==(t=er.get(u))&&void 0!==t?t:x.fr.Gray,w.K.text).fillColor),cx:r,cy:i,r:5,fill:"",stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,onClick:(t,r)=>{r.stopPropagation(),ei&&(e.index===(null==J?void 0:J.index)&&e.dataKey===(null==J?void 0:J.dataKey)||(0,b.FB)(n,e.dataKey)&&et&&et===e.dataKey?(en(void 0),ee(void 0),null==q||q(null)):(en(e.dataKey),ee({index:e.index,dataKey:e.dataKey}),null==q||q(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var r;let{stroke:i,strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,cx:s,cy:u,dataKey:d,index:f}=t;return(0,b.FB)(n,e)&&!(J||et&&et!==e)||(null==J?void 0:J.index)===f&&(null==J?void 0:J.dataKey)===e?o.createElement(m.o,{key:f,cx:s,cy:u,r:5,stroke:i,fill:"",strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,className:(0,O.q)("stroke-tremor-background dark:stroke-dark-tremor-background",q?"cursor-pointer":"",(0,E.bM)(null!==(r=er.get(d))&&void 0!==r?r:x.fr.Gray,w.K.text).fillColor)}):o.createElement(o.Fragment,{key:f})},key:e,name:e,type:Z,dataKey:e,stroke:"",strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:N,animationDuration:M,connectNulls:z})}),q?a.map(e=>o.createElement(l.x,{className:(0,O.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:Z,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:z,onClick:(e,t)=>{t.stopPropagation();let{name:n}=e;ea(n)}})):null):o.createElement(y.Z,{noDataText:H})))});S.displayName="LineChart"},17280:function(e,t,n){n.d(t,{Z:function(){return m}});var r=n(2265);let o=(e,t)=>{let[n,o]=(0,r.useState)(t);(0,r.useEffect)(()=>{let t=()=>{o(window.innerWidth),e()};return t(),window.addEventListener("resize",t),()=>window.removeEventListener("resize",t)},[e,n])};var i=n(69703),a=n(2898),l=n(99250),c=n(65492);let s=e=>{var t=(0,i._T)(e,[]);return r.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),r.createElement("path",{d:"M8 12L14 6V18L8 12Z"}))},u=e=>{var t=(0,i._T)(e,[]);return r.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),r.createElement("path",{d:"M16 12L10 18V6L16 12Z"}))},d=(0,c.fn)("Legend"),f=e=>{let{name:t,color:n,onClick:o,activeLegend:i}=e,s=!!o;return r.createElement("li",{className:(0,l.q)(d("legendItem"),"group inline-flex items-center px-2 py-0.5 rounded-tremor-small transition whitespace-nowrap",s?"cursor-pointer":"cursor-default","text-tremor-content",s?"hover:bg-tremor-background-subtle":"","dark:text-dark-tremor-content",s?"dark:hover:bg-dark-tremor-background-subtle":""),onClick:e=>{e.stopPropagation(),null==o||o(t,n)}},r.createElement("svg",{className:(0,l.q)("flex-none h-2 w-2 mr-1.5",(0,c.bM)(n,a.K.text).textColor,i&&i!==t?"opacity-40":"opacity-100"),fill:"currentColor",viewBox:"0 0 8 8"},r.createElement("circle",{cx:4,cy:4,r:4})),r.createElement("p",{className:(0,l.q)("whitespace-nowrap truncate text-tremor-default","text-tremor-content",s?"group-hover:text-tremor-content-emphasis":"","dark:text-dark-tremor-content",i&&i!==t?"opacity-40":"opacity-100",s?"dark:group-hover:text-dark-tremor-content-emphasis":"")},t))},p=e=>{let{icon:t,onClick:n,disabled:o}=e,[i,a]=r.useState(!1),c=r.useRef(null);return r.useEffect(()=>(i?c.current=setInterval(()=>{null==n||n()},300):clearInterval(c.current),()=>clearInterval(c.current)),[i,n]),(0,r.useEffect)(()=>{o&&(clearInterval(c.current),a(!1))},[o]),r.createElement("button",{type:"button",className:(0,l.q)(d("legendSliderButton"),"w-5 group inline-flex items-center truncate rounded-tremor-small transition",o?"cursor-not-allowed":"cursor-pointer",o?"text-tremor-content-subtle":"text-tremor-content hover:text-tremor-content-emphasis hover:bg-tremor-background-subtle",o?"dark:text-dark-tremor-subtle":"dark:text-dark-tremor dark:hover:text-tremor-content-emphasis dark:hover:bg-dark-tremor-background-subtle"),disabled:o,onClick:e=>{e.stopPropagation(),null==n||n()},onMouseDown:e=>{e.stopPropagation(),a(!0)},onMouseUp:e=>{e.stopPropagation(),a(!1)}},r.createElement(t,{className:"w-full"}))},h=r.forwardRef((e,t)=>{var n,o;let{categories:c,colors:h=a.s,className:m,onClickLegendItem:v,activeLegend:g,enableLegendSlider:y=!1}=e,b=(0,i._T)(e,["categories","colors","className","onClickLegendItem","activeLegend","enableLegendSlider"]),x=r.useRef(null),[w,O]=r.useState(null),[E,S]=r.useState(null),k=r.useRef(null),C=(0,r.useCallback)(()=>{let e=null==x?void 0:x.current;e&&O({left:e.scrollLeft>0,right:e.scrollWidth-e.clientWidth>e.scrollLeft})},[O]),j=(0,r.useCallback)(e=>{var t;let n=null==x?void 0:x.current,r=null!==(t=null==n?void 0:n.clientWidth)&&void 0!==t?t:0;n&&y&&(n.scrollTo({left:"left"===e?n.scrollLeft-r:n.scrollLeft+r,behavior:"smooth"}),setTimeout(()=>{C()},400))},[y,C]);r.useEffect(()=>{let e=e=>{"ArrowLeft"===e?j("left"):"ArrowRight"===e&&j("right")};return E?(e(E),k.current=setInterval(()=>{e(E)},300)):clearInterval(k.current),()=>clearInterval(k.current)},[E,j]);let P=e=>{e.stopPropagation(),"ArrowLeft"!==e.key&&"ArrowRight"!==e.key||(e.preventDefault(),S(e.key))},A=e=>{e.stopPropagation(),S(null)};return r.useEffect(()=>{let e=null==x?void 0:x.current;return y&&(C(),null==e||e.addEventListener("keydown",P),null==e||e.addEventListener("keyup",A)),()=>{null==e||e.removeEventListener("keydown",P),null==e||e.removeEventListener("keyup",A)}},[C,y]),r.createElement("ol",Object.assign({ref:t,className:(0,l.q)(d("root"),"relative overflow-hidden",m)},b),r.createElement("div",{ref:x,tabIndex:0,className:(0,l.q)("h-full flex",y?(null==w?void 0:w.right)||(null==w?void 0:w.left)?"pl-4 pr-12 items-center overflow-auto snap-mandatory [&::-webkit-scrollbar]:hidden [scrollbar-width:none]":"":"flex-wrap")},c.map((e,t)=>r.createElement(f,{key:"item-".concat(t),name:e,color:h[t],onClick:v,activeLegend:g}))),y&&((null==w?void 0:w.right)||(null==w?void 0:w.left))?r.createElement(r.Fragment,null,r.createElement("div",{className:(0,l.q)("from-tremor-background","dark:from-dark-tremor-background","absolute top-0 bottom-0 left-0 w-4 bg-gradient-to-r to-transparent pointer-events-none")}),r.createElement("div",{className:(0,l.q)("to-tremor-background","dark:to-dark-tremor-background","absolute top-0 bottom-0 right-10 w-4 bg-gradient-to-r from-transparent pointer-events-none")}),r.createElement("div",{className:(0,l.q)("bg-tremor-background","dark:bg-dark-tremor-background","absolute flex top-0 pr-1 bottom-0 right-0 items-center justify-center h-full")},r.createElement(p,{icon:s,onClick:()=>{S(null),j("left")},disabled:!(null==w?void 0:w.left)}),r.createElement(p,{icon:u,onClick:()=>{S(null),j("right")},disabled:!(null==w?void 0:w.right)}))):null)});h.displayName="Legend";let m=(e,t,n,i,a,l)=>{let{payload:c}=e,s=(0,r.useRef)(null);o(()=>{var e,t;n((t=null===(e=s.current)||void 0===e?void 0:e.clientHeight)?Number(t)+20:60)});let u=c.filter(e=>"none"!==e.type);return r.createElement("div",{ref:s,className:"flex items-center justify-end"},r.createElement(h,{categories:u.map(e=>e.value),colors:u.map(e=>t.get(e.value)),onClickLegendItem:a,activeLegend:i,enableLegendSlider:l}))}},30470:function(e,t,n){n.d(t,{$B:function(){return c},ZP:function(){return u},zX:function(){return s}});var r=n(2265),o=n(54942),i=n(2898),a=n(99250),l=n(65492);let c=e=>{let{children:t}=e;return r.createElement("div",{className:(0,a.q)("rounded-tremor-default text-tremor-default border","bg-tremor-background shadow-tremor-dropdown border-tremor-border","dark:bg-dark-tremor-background dark:shadow-dark-tremor-dropdown dark:border-dark-tremor-border")},t)},s=e=>{let{value:t,name:n,color:o}=e;return r.createElement("div",{className:"flex items-center justify-between space-x-8"},r.createElement("div",{className:"flex items-center space-x-2"},r.createElement("span",{className:(0,a.q)("shrink-0 rounded-tremor-full border-2 h-3 w-3","border-tremor-background shadow-tremor-card","dark:border-dark-tremor-background dark:shadow-dark-tremor-card",(0,l.bM)(o,i.K.background).bgColor)}),r.createElement("p",{className:(0,a.q)("text-right whitespace-nowrap","text-tremor-content","dark:text-dark-tremor-content")},n)),r.createElement("p",{className:(0,a.q)("font-medium tabular-nums text-right whitespace-nowrap","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},t))},u=e=>{let{active:t,payload:n,label:i,categoryColors:l,valueFormatter:u}=e;if(t&&n){let e=n.filter(e=>"none"!==e.type);return r.createElement(c,null,r.createElement("div",{className:(0,a.q)("border-tremor-border border-b px-4 py-2","dark:border-dark-tremor-border")},r.createElement("p",{className:(0,a.q)("font-medium","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},i)),r.createElement("div",{className:(0,a.q)("px-4 py-2 space-y-1")},e.map((e,t)=>{var n;let{value:i,name:a}=e;return r.createElement(s,{key:"id-".concat(t),value:u(i),name:a,color:null!==(n=l.get(a))&&void 0!==n?n:o.fr.Blue})})))}return null}},77448:function(e,t,n){n.d(t,{Z:function(){return f}});var r=n(99250),o=n(2265),i=n(69703);let a=(0,n(65492).fn)("Flex"),l={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"},c={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"},s={row:"flex-row",col:"flex-col","row-reverse":"flex-row-reverse","col-reverse":"flex-col-reverse"},u=o.forwardRef((e,t)=>{let{flexDirection:n="row",justifyContent:u="between",alignItems:d="center",children:f,className:p}=e,h=(0,i._T)(e,["flexDirection","justifyContent","alignItems","children","className"]);return o.createElement("div",Object.assign({ref:t,className:(0,r.q)(a("root"),"flex w-full",s[n],l[u],c[d],p)},h),f)});u.displayName="Flex";var d=n(71801);let f=e=>{let{noDataText:t="No data"}=e;return o.createElement(u,{alignItems:"center",justifyContent:"center",className:(0,r.q)("w-full h-full border border-dashed rounded-tremor-default","border-tremor-border","dark:border-dark-tremor-border")},o.createElement(d.Z,{className:(0,r.q)("text-tremor-content","dark:text-dark-tremor-content")},t))}},36342:function(e,t,n){n.d(t,{FB:function(){return i},i4:function(){return o},me:function(){return r},vZ:function(){return function e(t,n){if(t===n)return!0;if("object"!=typeof t||"object"!=typeof n||null===t||null===n)return!1;let r=Object.keys(t),o=Object.keys(n);if(r.length!==o.length)return!1;for(let i of r)if(!o.includes(i)||!e(t[i],n[i]))return!1;return!0}}});let r=(e,t)=>{let n=new Map;return e.forEach((e,r)=>{n.set(e,t[r])}),n},o=(e,t,n)=>[e?"auto":null!=t?t:0,null!=n?n:"auto"];function i(e,t){let n=[];for(let r of e)if(Object.prototype.hasOwnProperty.call(r,t)&&(n.push(r[t]),n.length>1))return!1;return!0}},5:function(e,t,n){n.d(t,{Z:function(){return p}});var r=n(69703),o=n(2265),i=n(58437),a=n(54942),l=n(2898),c=n(99250),s=n(65492);let u={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},d={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},f=(0,s.fn)("Badge"),p=o.forwardRef((e,t)=>{let{color:n,icon:p,size:h=a.u8.SM,tooltip:m,className:v,children:g}=e,y=(0,r._T)(e,["color","icon","size","tooltip","className","children"]),b=p||null,{tooltipProps:x,getReferenceProps:w}=(0,i.l)();return o.createElement("span",Object.assign({ref:(0,s.lq)([t,x.refs.setReference]),className:(0,c.q)(f("root"),"w-max flex-shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-full",n?(0,c.q)((0,s.bM)(n,l.K.background).bgColor,(0,s.bM)(n,l.K.text).textColor,"bg-opacity-20 dark:bg-opacity-25"):(0,c.q)("bg-tremor-brand-muted text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted dark:text-dark-tremor-brand-emphasis"),u[h].paddingX,u[h].paddingY,u[h].fontSize,v)},w,y),o.createElement(i.Z,Object.assign({text:m},x)),b?o.createElement(b,{className:(0,c.q)(f("icon"),"shrink-0 -ml-1 mr-1.5",d[h].height,d[h].width)}):null,o.createElement("p",{className:(0,c.q)(f("text"),"text-sm whitespace-nowrap")},g))});p.displayName="Badge"},61244:function(e,t,n){n.d(t,{Z:function(){return m}});var r=n(69703),o=n(2265),i=n(58437),a=n(54942),l=n(99250),c=n(65492),s=n(2898);let u={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},d={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},f={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},p=(e,t)=>{switch(e){case"simple":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,c.bM)(t,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,l.q)((0,c.bM)(t,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},h=(0,c.fn)("Icon"),m=o.forwardRef((e,t)=>{let{icon:n,variant:s="simple",tooltip:m,size:v=a.u8.SM,color:g,className:y}=e,b=(0,r._T)(e,["icon","variant","tooltip","size","color","className"]),x=p(s,g),{tooltipProps:w,getReferenceProps:O}=(0,i.l)();return o.createElement("span",Object.assign({ref:(0,c.lq)([t,w.refs.setReference]),className:(0,l.q)(h("root"),"inline-flex flex-shrink-0 items-center",x.bgColor,x.textColor,x.borderColor,x.ringColor,f[s].rounded,f[s].border,f[s].shadow,f[s].ring,u[v].paddingX,u[v].paddingY,y)},O,b),o.createElement(i.Z,Object.assign({text:m},w)),o.createElement(n,{className:(0,l.q)(h("icon"),"shrink-0",d[v].height,d[v].width)}))});m.displayName="Icon"},35087:function(e,t,n){n.d(t,{Z:function(){return eU}});var r,o,i,a=n(69703),l=n(2265),c=n(54887),s=n(10641),u=n(39790),d=n(21210),f=n(94819);function p(){for(var e=arguments.length,t=Array(e),n=0;n(0,f.r)(...t),[...t])}var h=n(92144),m=n(36601);let v=(0,l.createContext)(!1);var g=n(41879),y=n(18318);let b=l.Fragment,x=l.Fragment,w=(0,l.createContext)(null),O=(0,l.createContext)(null);Object.assign((0,y.yV)(function(e,t){let n,r,o=(0,l.useRef)(null),i=(0,m.T)((0,m.h)(e=>{o.current=e}),t),a=p(o),f=function(e){let t=(0,l.useContext)(v),n=(0,l.useContext)(w),r=p(e),[o,i]=(0,l.useState)(()=>{if(!t&&null!==n||g.O.isServer)return null;let e=null==r?void 0:r.getElementById("headlessui-portal-root");if(e)return e;if(null===r)return null;let o=r.createElement("div");return o.setAttribute("id","headlessui-portal-root"),r.body.appendChild(o)});return(0,l.useEffect)(()=>{null!==o&&(null!=r&&r.body.contains(o)||null==r||r.body.appendChild(o))},[o,r]),(0,l.useEffect)(()=>{t||null!==n&&i(n.current)},[n,i,t]),o}(o),[x]=(0,l.useState)(()=>{var e;return g.O.isServer?null:null!=(e=null==a?void 0:a.createElement("div"))?e:null}),E=(0,l.useContext)(O),S=(0,h.H)();return(0,u.e)(()=>{!f||!x||f.contains(x)||(x.setAttribute("data-headlessui-portal",""),f.appendChild(x))},[f,x]),(0,u.e)(()=>{if(x&&E)return E.register(x)},[E,x]),n=(0,s.z)(()=>{var e;f&&x&&(x instanceof Node&&f.contains(x)&&f.removeChild(x),f.childNodes.length<=0&&(null==(e=f.parentElement)||e.removeChild(f)))}),r=(0,l.useRef)(!1),(0,l.useEffect)(()=>(r.current=!1,()=>{r.current=!0,(0,d.Y)(()=>{r.current&&n()})}),[n]),S&&f&&x?(0,c.createPortal)((0,y.sY)({ourProps:{ref:i},theirProps:e,defaultTag:b,name:"Portal"}),x):null}),{Group:(0,y.yV)(function(e,t){let{target:n,...r}=e,o={ref:(0,m.T)(t)};return l.createElement(w.Provider,{value:n},(0,y.sY)({ourProps:o,theirProps:r,defaultTag:x,name:"Popover.Group"}))})});var E=n(85235),S=n(92381),k=n(15058),C=n(71454),j=n(37700),P=n(61372),A=((r=A||{})[r.Forwards=0]="Forwards",r[r.Backwards=1]="Backwards",r);function T(){let e=(0,l.useRef)(0);return(0,P.s)("keydown",t=>{"Tab"===t.key&&(e.current=t.shiftKey?1:0)},!0),e}var M=n(88358),N=n(84152),I=n(48803),R=n(72640),_=n(67409),D=((o=D||{})[o.Open=0]="Open",o[o.Closed=1]="Closed",o),Z=((i=Z||{})[i.TogglePopover=0]="TogglePopover",i[i.ClosePopover=1]="ClosePopover",i[i.SetButton=2]="SetButton",i[i.SetButtonId=3]="SetButtonId",i[i.SetPanel=4]="SetPanel",i[i.SetPanelId=5]="SetPanelId",i);let L={0:e=>{let t={...e,popoverState:(0,R.E)(e.popoverState,{0:1,1:0})};return 0===t.popoverState&&(t.__demoMode=!1),t},1:e=>1===e.popoverState?e:{...e,popoverState:1},2:(e,t)=>e.button===t.button?e:{...e,button:t.button},3:(e,t)=>e.buttonId===t.buttonId?e:{...e,buttonId:t.buttonId},4:(e,t)=>e.panel===t.panel?e:{...e,panel:t.panel},5:(e,t)=>e.panelId===t.panelId?e:{...e,panelId:t.panelId}},B=(0,l.createContext)(null);function z(e){let t=(0,l.useContext)(B);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,z),t}return t}B.displayName="PopoverContext";let F=(0,l.createContext)(null);function H(e){let t=(0,l.useContext)(F);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,H),t}return t}F.displayName="PopoverAPIContext";let W=(0,l.createContext)(null);function q(){return(0,l.useContext)(W)}W.displayName="PopoverGroupContext";let U=(0,l.createContext)(null);function V(e,t){return(0,R.E)(t.type,L,e,t)}U.displayName="PopoverPanelContext";let K=y.AN.RenderStrategy|y.AN.Static,Y=y.AN.RenderStrategy|y.AN.Static,X=Object.assign((0,y.yV)(function(e,t){var n,r,o;let i,a,c,u,d,f;let{__demoMode:h=!1,...v}=e,g=(0,l.useRef)(null),b=(0,m.T)(t,(0,m.h)(e=>{g.current=e})),x=(0,l.useRef)([]),w=(0,l.useReducer)(V,{__demoMode:h,popoverState:h?0:1,buttons:x,button:null,buttonId:null,panel:null,panelId:null,beforePanelSentinel:(0,l.createRef)(),afterPanelSentinel:(0,l.createRef)()}),[{popoverState:S,button:C,buttonId:P,panel:A,panelId:T,beforePanelSentinel:N,afterPanelSentinel:_},D]=w,Z=p(null!=(n=g.current)?n:C),L=(0,l.useMemo)(()=>{if(!C||!A)return!1;for(let e of document.querySelectorAll("body > *"))if(Number(null==e?void 0:e.contains(C))^Number(null==e?void 0:e.contains(A)))return!0;let e=(0,I.GO)(),t=e.indexOf(C),n=(t+e.length-1)%e.length,r=(t+1)%e.length,o=e[n],i=e[r];return!A.contains(o)&&!A.contains(i)},[C,A]),z=(0,E.E)(P),H=(0,E.E)(T),W=(0,l.useMemo)(()=>({buttonId:z,panelId:H,close:()=>D({type:1})}),[z,H,D]),K=q(),Y=null==K?void 0:K.registerPopover,X=(0,s.z)(()=>{var e;return null!=(e=null==K?void 0:K.isFocusWithinPopoverGroup())?e:(null==Z?void 0:Z.activeElement)&&((null==C?void 0:C.contains(Z.activeElement))||(null==A?void 0:A.contains(Z.activeElement)))});(0,l.useEffect)(()=>null==Y?void 0:Y(W),[Y,W]);let[G,$]=(i=(0,l.useContext)(O),a=(0,l.useRef)([]),c=(0,s.z)(e=>(a.current.push(e),i&&i.register(e),()=>u(e))),u=(0,s.z)(e=>{let t=a.current.indexOf(e);-1!==t&&a.current.splice(t,1),i&&i.unregister(e)}),d=(0,l.useMemo)(()=>({register:c,unregister:u,portals:a}),[c,u,a]),[a,(0,l.useMemo)(()=>function(e){let{children:t}=e;return l.createElement(O.Provider,{value:d},t)},[d])]),Q=function(){var e;let{defaultContainers:t=[],portals:n,mainTreeNodeRef:r}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=(0,l.useRef)(null!=(e=null==r?void 0:r.current)?e:null),i=p(o),a=(0,s.z)(()=>{var e,r,a;let l=[];for(let e of t)null!==e&&(e instanceof HTMLElement?l.push(e):"current"in e&&e.current instanceof HTMLElement&&l.push(e.current));if(null!=n&&n.current)for(let e of n.current)l.push(e);for(let t of null!=(e=null==i?void 0:i.querySelectorAll("html > *, body > *"))?e:[])t!==document.body&&t!==document.head&&t instanceof HTMLElement&&"headlessui-portal-root"!==t.id&&(t.contains(o.current)||t.contains(null==(a=null==(r=o.current)?void 0:r.getRootNode())?void 0:a.host)||l.some(e=>t.contains(e))||l.push(t));return l});return{resolveContainers:a,contains:(0,s.z)(e=>a().some(t=>t.contains(e))),mainTreeNodeRef:o,MainTreeNode:(0,l.useMemo)(()=>function(){return null!=r?null:l.createElement(j._,{features:j.A.Hidden,ref:o})},[o,r])}}({mainTreeNodeRef:null==K?void 0:K.mainTreeNodeRef,portals:G,defaultContainers:[C,A]});r=null==Z?void 0:Z.defaultView,o="focus",f=(0,E.E)(e=>{var t,n,r,o;e.target!==window&&e.target instanceof HTMLElement&&0===S&&(X()||C&&A&&(Q.contains(e.target)||null!=(n=null==(t=N.current)?void 0:t.contains)&&n.call(t,e.target)||null!=(o=null==(r=_.current)?void 0:r.contains)&&o.call(r,e.target)||D({type:1})))}),(0,l.useEffect)(()=>{function e(e){f.current(e)}return(r=null!=r?r:window).addEventListener(o,e,!0),()=>r.removeEventListener(o,e,!0)},[r,o,!0]),(0,k.O)(Q.resolveContainers,(e,t)=>{D({type:1}),(0,I.sP)(t,I.tJ.Loose)||(e.preventDefault(),null==C||C.focus())},0===S);let J=(0,s.z)(e=>{D({type:1});let t=e?e instanceof HTMLElement?e:"current"in e&&e.current instanceof HTMLElement?e.current:C:C;null==t||t.focus()}),ee=(0,l.useMemo)(()=>({close:J,isPortalled:L}),[J,L]),et=(0,l.useMemo)(()=>({open:0===S,close:J}),[S,J]);return l.createElement(U.Provider,{value:null},l.createElement(B.Provider,{value:w},l.createElement(F.Provider,{value:ee},l.createElement(M.up,{value:(0,R.E)(S,{0:M.ZM.Open,1:M.ZM.Closed})},l.createElement($,null,(0,y.sY)({ourProps:{ref:b},theirProps:v,slot:et,defaultTag:"div",name:"Popover"}),l.createElement(Q.MainTreeNode,null))))))}),{Button:(0,y.yV)(function(e,t){let n=(0,S.M)(),{id:r="headlessui-popover-button-".concat(n),...o}=e,[i,a]=z("Popover.Button"),{isPortalled:c}=H("Popover.Button"),u=(0,l.useRef)(null),d="headlessui-focus-sentinel-".concat((0,S.M)()),f=q(),h=null==f?void 0:f.closeOthers,v=null!==(0,l.useContext)(U);(0,l.useEffect)(()=>{if(!v)return a({type:3,buttonId:r}),()=>{a({type:3,buttonId:null})}},[v,r,a]);let[g]=(0,l.useState)(()=>Symbol()),b=(0,m.T)(u,t,v?null:e=>{if(e)i.buttons.current.push(g);else{let e=i.buttons.current.indexOf(g);-1!==e&&i.buttons.current.splice(e,1)}i.buttons.current.length>1&&console.warn("You are already using a but only 1 is supported."),e&&a({type:2,button:e})}),x=(0,m.T)(u,t),w=p(u),O=(0,s.z)(e=>{var t,n,r;if(v){if(1===i.popoverState)return;switch(e.key){case _.R.Space:case _.R.Enter:e.preventDefault(),null==(n=(t=e.target).click)||n.call(t),a({type:1}),null==(r=i.button)||r.focus()}}else switch(e.key){case _.R.Space:case _.R.Enter:e.preventDefault(),e.stopPropagation(),1===i.popoverState&&(null==h||h(i.buttonId)),a({type:0});break;case _.R.Escape:if(0!==i.popoverState)return null==h?void 0:h(i.buttonId);if(!u.current||null!=w&&w.activeElement&&!u.current.contains(w.activeElement))return;e.preventDefault(),e.stopPropagation(),a({type:1})}}),E=(0,s.z)(e=>{v||e.key===_.R.Space&&e.preventDefault()}),k=(0,s.z)(t=>{var n,r;(0,N.P)(t.currentTarget)||e.disabled||(v?(a({type:1}),null==(n=i.button)||n.focus()):(t.preventDefault(),t.stopPropagation(),1===i.popoverState&&(null==h||h(i.buttonId)),a({type:0}),null==(r=i.button)||r.focus()))}),P=(0,s.z)(e=>{e.preventDefault(),e.stopPropagation()}),M=0===i.popoverState,D=(0,l.useMemo)(()=>({open:M}),[M]),Z=(0,C.f)(e,u),L=v?{ref:x,type:Z,onKeyDown:O,onClick:k}:{ref:b,id:i.buttonId,type:Z,"aria-expanded":0===i.popoverState,"aria-controls":i.panel?i.panelId:void 0,onKeyDown:O,onKeyUp:E,onClick:k,onMouseDown:P},B=T(),F=(0,s.z)(()=>{let e=i.panel;e&&(0,R.E)(B.current,{[A.Forwards]:()=>(0,I.jA)(e,I.TO.First),[A.Backwards]:()=>(0,I.jA)(e,I.TO.Last)})===I.fE.Error&&(0,I.jA)((0,I.GO)().filter(e=>"true"!==e.dataset.headlessuiFocusGuard),(0,R.E)(B.current,{[A.Forwards]:I.TO.Next,[A.Backwards]:I.TO.Previous}),{relativeTo:i.button})});return l.createElement(l.Fragment,null,(0,y.sY)({ourProps:L,theirProps:o,slot:D,defaultTag:"button",name:"Popover.Button"}),M&&!v&&c&&l.createElement(j._,{id:d,features:j.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:F}))}),Overlay:(0,y.yV)(function(e,t){let n=(0,S.M)(),{id:r="headlessui-popover-overlay-".concat(n),...o}=e,[{popoverState:i},a]=z("Popover.Overlay"),c=(0,m.T)(t),u=(0,M.oJ)(),d=null!==u?(u&M.ZM.Open)===M.ZM.Open:0===i,f=(0,s.z)(e=>{if((0,N.P)(e.currentTarget))return e.preventDefault();a({type:1})}),p=(0,l.useMemo)(()=>({open:0===i}),[i]);return(0,y.sY)({ourProps:{ref:c,id:r,"aria-hidden":!0,onClick:f},theirProps:o,slot:p,defaultTag:"div",features:K,visible:d,name:"Popover.Overlay"})}),Panel:(0,y.yV)(function(e,t){let n=(0,S.M)(),{id:r="headlessui-popover-panel-".concat(n),focus:o=!1,...i}=e,[a,c]=z("Popover.Panel"),{close:d,isPortalled:f}=H("Popover.Panel"),h="headlessui-focus-sentinel-before-".concat((0,S.M)()),v="headlessui-focus-sentinel-after-".concat((0,S.M)()),g=(0,l.useRef)(null),b=(0,m.T)(g,t,e=>{c({type:4,panel:e})}),x=p(g),w=(0,y.Y2)();(0,u.e)(()=>(c({type:5,panelId:r}),()=>{c({type:5,panelId:null})}),[r,c]);let O=(0,M.oJ)(),E=null!==O?(O&M.ZM.Open)===M.ZM.Open:0===a.popoverState,k=(0,s.z)(e=>{var t;if(e.key===_.R.Escape){if(0!==a.popoverState||!g.current||null!=x&&x.activeElement&&!g.current.contains(x.activeElement))return;e.preventDefault(),e.stopPropagation(),c({type:1}),null==(t=a.button)||t.focus()}});(0,l.useEffect)(()=>{var t;e.static||1===a.popoverState&&(null==(t=e.unmount)||t)&&c({type:4,panel:null})},[a.popoverState,e.unmount,e.static,c]),(0,l.useEffect)(()=>{if(a.__demoMode||!o||0!==a.popoverState||!g.current)return;let e=null==x?void 0:x.activeElement;g.current.contains(e)||(0,I.jA)(g.current,I.TO.First)},[a.__demoMode,o,g,a.popoverState]);let C=(0,l.useMemo)(()=>({open:0===a.popoverState,close:d}),[a,d]),P={ref:b,id:r,onKeyDown:k,onBlur:o&&0===a.popoverState?e=>{var t,n,r,o,i;let l=e.relatedTarget;l&&g.current&&(null!=(t=g.current)&&t.contains(l)||(c({type:1}),(null!=(r=null==(n=a.beforePanelSentinel.current)?void 0:n.contains)&&r.call(n,l)||null!=(i=null==(o=a.afterPanelSentinel.current)?void 0:o.contains)&&i.call(o,l))&&l.focus({preventScroll:!0})))}:void 0,tabIndex:-1},N=T(),D=(0,s.z)(()=>{let e=g.current;e&&(0,R.E)(N.current,{[A.Forwards]:()=>{var t;(0,I.jA)(e,I.TO.First)===I.fE.Error&&(null==(t=a.afterPanelSentinel.current)||t.focus())},[A.Backwards]:()=>{var e;null==(e=a.button)||e.focus({preventScroll:!0})}})}),Z=(0,s.z)(()=>{let e=g.current;e&&(0,R.E)(N.current,{[A.Forwards]:()=>{var e;if(!a.button)return;let t=(0,I.GO)(),n=t.indexOf(a.button),r=t.slice(0,n+1),o=[...t.slice(n+1),...r];for(let t of o.slice())if("true"===t.dataset.headlessuiFocusGuard||null!=(e=a.panel)&&e.contains(t)){let e=o.indexOf(t);-1!==e&&o.splice(e,1)}(0,I.jA)(o,I.TO.First,{sorted:!1})},[A.Backwards]:()=>{var t;(0,I.jA)(e,I.TO.Previous)===I.fE.Error&&(null==(t=a.button)||t.focus())}})});return l.createElement(U.Provider,{value:r},E&&f&&l.createElement(j._,{id:h,ref:a.beforePanelSentinel,features:j.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:D}),(0,y.sY)({mergeRefs:w,ourProps:P,theirProps:i,slot:C,defaultTag:"div",features:Y,visible:E,name:"Popover.Panel"}),E&&f&&l.createElement(j._,{id:v,ref:a.afterPanelSentinel,features:j.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:Z}))}),Group:(0,y.yV)(function(e,t){let n;let r=(0,l.useRef)(null),o=(0,m.T)(r,t),[i,a]=(0,l.useState)([]),c={mainTreeNodeRef:n=(0,l.useRef)(null),MainTreeNode:(0,l.useMemo)(()=>function(){return l.createElement(j._,{features:j.A.Hidden,ref:n})},[n])},u=(0,s.z)(e=>{a(t=>{let n=t.indexOf(e);if(-1!==n){let e=t.slice();return e.splice(n,1),e}return t})}),d=(0,s.z)(e=>(a(t=>[...t,e]),()=>u(e))),p=(0,s.z)(()=>{var e;let t=(0,f.r)(r);if(!t)return!1;let n=t.activeElement;return!!(null!=(e=r.current)&&e.contains(n))||i.some(e=>{var r,o;return(null==(r=t.getElementById(e.buttonId.current))?void 0:r.contains(n))||(null==(o=t.getElementById(e.panelId.current))?void 0:o.contains(n))})}),h=(0,s.z)(e=>{for(let t of i)t.buttonId.current!==e&&t.close()}),v=(0,l.useMemo)(()=>({registerPopover:d,unregisterPopover:u,isFocusWithinPopoverGroup:p,closeOthers:h,mainTreeNodeRef:c.mainTreeNodeRef}),[d,u,p,h,c.mainTreeNodeRef]),g=(0,l.useMemo)(()=>({}),[]);return l.createElement(W.Provider,{value:v},(0,y.sY)({ourProps:{ref:o},theirProps:e,slot:g,defaultTag:"div",name:"Popover.Group"}),l.createElement(c.MainTreeNode,null))})});var G=n(70129),$=n(25163);let Q=e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor"}),l.createElement("path",{fillRule:"evenodd",d:"M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z",clipRule:"evenodd"}))};var J=n(8903),ee=n(49492);function et(){return(0,ee.Z)(Date.now())}var en=n(32633),er=n(99250),eo=n(91753),ei=n(74416),ea=n(50295),el=n(6976),ec=n(13256),es=n(68309),eu=n(84120),ed=n(27552);function ef(e,t){if((0,ed.Z)(2,arguments),!t||"object"!==(0,el.Z)(t))return new Date(NaN);var n=t.years?(0,es.Z)(t.years):0,r=t.months?(0,es.Z)(t.months):0,o=t.weeks?(0,es.Z)(t.weeks):0,i=t.days?(0,es.Z)(t.days):0,a=t.hours?(0,es.Z)(t.hours):0,l=t.minutes?(0,es.Z)(t.minutes):0,c=t.seconds?(0,es.Z)(t.seconds):0,s=function(e,t){(0,ed.Z)(2,arguments);var n=(0,es.Z)(t);return(0,eu.Z)(e,-n)}(e,r+12*n);return new Date((0,ec.Z)(s,i+7*o).getTime()-1e3*(c+60*(l+60*a)))}var ep=n(8053),eh=n(68005),em=n(22893),ev=n(65492);let eg=(0,ev.fn)("DateRangePicker"),ey=(e,t,n,r)=>{var o;if(n&&(e=null===(o=r.get(n))||void 0===o?void 0:o.from),e)return(0,ee.Z)(e&&!t?e:(0,ei.Z)([e,t]))},eb=(e,t,n,r)=>{var o,i;if(n&&(e=(0,ee.Z)(null!==(i=null===(o=r.get(n))||void 0===o?void 0:o.to)&&void 0!==i?i:et())),e)return(0,ee.Z)(e&&!t?e:(0,ea.Z)([e,t]))},ex=[{value:"tdy",text:"Today",from:et()},{value:"w",text:"Last 7 days",from:ef(et(),{days:7})},{value:"t",text:"Last 30 days",from:ef(et(),{days:30})},{value:"m",text:"Month to Date",from:(0,en.Z)(et())},{value:"y",text:"Year to Date",from:(0,ep.Z)(et())}],ew=(e,t,n,r)=>{let o=(null==n?void 0:n.code)||"en-US";if(!e&&!t)return"";if(e&&!t)return r?(0,eh.Z)(e,r):e.toLocaleDateString(o,{year:"numeric",month:"short",day:"numeric"});if(e&&t){if(function(e,t){(0,ed.Z)(2,arguments);var n=(0,em.Z)(e),r=(0,em.Z)(t);return n.getTime()===r.getTime()}(e,t))return r?(0,eh.Z)(e,r):e.toLocaleDateString(o,{year:"numeric",month:"short",day:"numeric"});if(e.getMonth()===t.getMonth()&&e.getFullYear()===t.getFullYear())return r?"".concat((0,eh.Z)(e,r)," - ").concat((0,eh.Z)(t,r)):"".concat(e.toLocaleDateString(o,{month:"short",day:"numeric"})," - \n ").concat(t.getDate(),", ").concat(t.getFullYear());{if(r)return"".concat((0,eh.Z)(e,r)," - ").concat((0,eh.Z)(t,r));let n={year:"numeric",month:"short",day:"numeric"};return"".concat(e.toLocaleDateString(o,n)," - \n ").concat(t.toLocaleDateString(o,n))}}return""};var eO=n(26463);let eE=e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M10.8284 12.0007L15.7782 16.9504L14.364 18.3646L8 12.0007L14.364 5.63672L15.7782 7.05093L10.8284 12.0007Z"}))},eS=e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"}))},ek=e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M4.83582 12L11.0429 18.2071L12.4571 16.7929L7.66424 12L12.4571 7.20712L11.0429 5.79291L4.83582 12ZM10.4857 12L16.6928 18.2071L18.107 16.7929L13.3141 12L18.107 7.20712L16.6928 5.79291L10.4857 12Z"}))},eC=e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M19.1642 12L12.9571 5.79291L11.5429 7.20712L16.3358 12L11.5429 16.7929L12.9571 18.2071L19.1642 12ZM13.5143 12L7.30722 5.79291L5.89301 7.20712L10.6859 12L5.89301 16.7929L7.30722 18.2071L13.5143 12Z"}))};var ej=n(45503),eP=n(71801);n(5);var eA=n(58437),eT=n(54942),eM=n(2898);let eN={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-1",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-1.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-1.5",fontSize:"text-lg"},xl:{paddingX:"px-3.5",paddingY:"py-1.5",fontSize:"text-xl"}},eI={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},eR={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},e_={[eT.wu.Increase]:{bgColor:(0,ev.bM)(eT.fr.Emerald,eM.K.background).bgColor,textColor:(0,ev.bM)(eT.fr.Emerald,eM.K.text).textColor},[eT.wu.ModerateIncrease]:{bgColor:(0,ev.bM)(eT.fr.Emerald,eM.K.background).bgColor,textColor:(0,ev.bM)(eT.fr.Emerald,eM.K.text).textColor},[eT.wu.Decrease]:{bgColor:(0,ev.bM)(eT.fr.Rose,eM.K.background).bgColor,textColor:(0,ev.bM)(eT.fr.Rose,eM.K.text).textColor},[eT.wu.ModerateDecrease]:{bgColor:(0,ev.bM)(eT.fr.Rose,eM.K.background).bgColor,textColor:(0,ev.bM)(eT.fr.Rose,eM.K.text).textColor},[eT.wu.Unchanged]:{bgColor:(0,ev.bM)(eT.fr.Orange,eM.K.background).bgColor,textColor:(0,ev.bM)(eT.fr.Orange,eM.K.text).textColor}},eD={[eT.wu.Increase]:e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M13.0001 7.82843V20H11.0001V7.82843L5.63614 13.1924L4.22192 11.7782L12.0001 4L19.7783 11.7782L18.3641 13.1924L13.0001 7.82843Z"}))},[eT.wu.ModerateIncrease]:e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M16.0037 9.41421L7.39712 18.0208L5.98291 16.6066L14.5895 8H7.00373V6H18.0037V17H16.0037V9.41421Z"}))},[eT.wu.Decrease]:e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M13.0001 16.1716L18.3641 10.8076L19.7783 12.2218L12.0001 20L4.22192 12.2218L5.63614 10.8076L11.0001 16.1716V4H13.0001V16.1716Z"}))},[eT.wu.ModerateDecrease]:e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M14.5895 16.0032L5.98291 7.39664L7.39712 5.98242L16.0037 14.589V7.00324H18.0037V18.0032H7.00373V16.0032H14.5895Z"}))},[eT.wu.Unchanged]:e=>{var t=(0,a._T)(e,[]);return l.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),l.createElement("path",{d:"M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"}))}},eZ=(0,ev.fn)("BadgeDelta");l.forwardRef((e,t)=>{let{deltaType:n=eT.wu.Increase,isIncreasePositive:r=!0,size:o=eT.u8.SM,tooltip:i,children:c,className:s}=e,u=(0,a._T)(e,["deltaType","isIncreasePositive","size","tooltip","children","className"]),d=eD[n],f=(0,ev.Fo)(n,r),p=c?eI:eN,{tooltipProps:h,getReferenceProps:m}=(0,eA.l)();return l.createElement("span",Object.assign({ref:(0,ev.lq)([t,h.refs.setReference]),className:(0,er.q)(eZ("root"),"w-max flex-shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-full bg-opacity-20 dark:bg-opacity-25",e_[f].bgColor,e_[f].textColor,p[o].paddingX,p[o].paddingY,p[o].fontSize,s)},m,u),l.createElement(eA.Z,Object.assign({text:i},h)),l.createElement(d,{className:(0,er.q)(eZ("icon"),"shrink-0",c?(0,er.q)("-ml-1 mr-1.5"):eR[o].height,eR[o].width)}),c?l.createElement("p",{className:(0,er.q)(eZ("text"),"text-sm whitespace-nowrap")},c):null)}).displayName="BadgeDelta";var eL=n(61244);let eB=e=>{var{onClick:t,icon:n}=e,r=(0,a._T)(e,["onClick","icon"]);return l.createElement("button",Object.assign({type:"button",className:(0,er.q)("flex items-center justify-center p-1 h-7 w-7 outline-none focus:ring-2 transition duration-100 border border-tremor-border dark:border-dark-tremor-border hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-muted rounded-tremor-small focus:border-tremor-brand-subtle select-none dark:focus:border-dark-tremor-brand-subtle focus:ring-tremor-brand-muted dark:focus:ring-dark-tremor-brand-muted text-tremor-content-subtle dark:text-dark-tremor-content-subtle hover:text-tremor-content dark:hover:text-dark-tremor-content")},r),l.createElement(eL.Z,{onClick:t,icon:n,variant:"simple",color:"slate",size:"sm"}))};function ez(e){var{mode:t,defaultMonth:n,selected:r,onSelect:o,locale:i,disabled:c,enableYearNavigation:s,classNames:u,weekStartsOn:d=0}=e,f=(0,a._T)(e,["mode","defaultMonth","selected","onSelect","locale","disabled","enableYearNavigation","classNames","weekStartsOn"]);return l.createElement(eO._W,Object.assign({showOutsideDays:!0,mode:t,defaultMonth:n,selected:r,onSelect:o,locale:i,disabled:c,weekStartsOn:d,classNames:Object.assign({months:"flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",month:"space-y-4",caption:"flex justify-center pt-2 relative items-center",caption_label:"text-tremor-default text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis font-medium",nav:"space-x-1 flex items-center",nav_button:"flex items-center justify-center p-1 h-7 w-7 outline-none focus:ring-2 transition duration-100 border border-tremor-border dark:border-dark-tremor-border hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-muted rounded-tremor-small focus:border-tremor-brand-subtle dark:focus:border-dark-tremor-brand-subtle focus:ring-tremor-brand-muted dark:focus:ring-dark-tremor-brand-muted text-tremor-content-subtle dark:text-dark-tremor-content-subtle hover:text-tremor-content dark:hover:text-dark-tremor-content",nav_button_previous:"absolute left-1",nav_button_next:"absolute right-1",table:"w-full border-collapse space-y-1",head_row:"flex",head_cell:"w-9 font-normal text-center text-tremor-content-subtle dark:text-dark-tremor-content-subtle",row:"flex w-full mt-0.5",cell:"text-center p-0 relative focus-within:relative text-tremor-default text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis",day:"h-9 w-9 p-0 hover:bg-tremor-background-subtle dark:hover:bg-dark-tremor-background-subtle outline-tremor-brand dark:outline-dark-tremor-brand rounded-tremor-default",day_today:"font-bold",day_selected:"aria-selected:bg-tremor-background-emphasis aria-selected:text-tremor-content-inverted dark:aria-selected:bg-dark-tremor-background-emphasis dark:aria-selected:text-dark-tremor-content-inverted ",day_disabled:"text-tremor-content-subtle dark:text-dark-tremor-content-subtle disabled:hover:bg-transparent",day_outside:"text-tremor-content-subtle dark:text-dark-tremor-content-subtle"},u),components:{IconLeft:e=>{var t=(0,a._T)(e,[]);return l.createElement(eE,Object.assign({className:"h-4 w-4"},t))},IconRight:e=>{var t=(0,a._T)(e,[]);return l.createElement(eS,Object.assign({className:"h-4 w-4"},t))},Caption:e=>{var t=(0,a._T)(e,[]);let{goToMonth:n,nextMonth:r,previousMonth:o,currentMonth:c}=(0,eO.HJ)();return l.createElement("div",{className:"flex justify-between items-center"},l.createElement("div",{className:"flex items-center space-x-1"},s&&l.createElement(eB,{onClick:()=>c&&n((0,ej.Z)(c,-1)),icon:ek}),l.createElement(eB,{onClick:()=>o&&n(o),icon:eE})),l.createElement(eP.Z,{className:"text-tremor-default tabular-nums capitalize text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis font-medium"},(0,eh.Z)(t.displayMonth,"LLLL yyy",{locale:i})),l.createElement("div",{className:"flex items-center space-x-1"},l.createElement(eB,{onClick:()=>r&&n(r),icon:eS}),s&&l.createElement(eB,{onClick:()=>c&&n((0,ej.Z)(c,1)),icon:eC})))}}},f))}ez.displayName="DateRangePicker",n(95093);var eF=n(27166),eH=n(82985),eW=n(46457);let eq=et(),eU=l.forwardRef((e,t)=>{var n,r;let{value:o,defaultValue:i,onValueChange:c,enableSelect:s=!0,minDate:u,maxDate:d,placeholder:f="Select range",selectPlaceholder:p="Select range",disabled:h=!1,locale:m=eH.Z,enableClear:v=!0,displayFormat:g,children:y,className:b,enableYearNavigation:x=!1,weekStartsOn:w=0,disabledDates:O}=e,E=(0,a._T)(e,["value","defaultValue","onValueChange","enableSelect","minDate","maxDate","placeholder","selectPlaceholder","disabled","locale","enableClear","displayFormat","children","className","enableYearNavigation","weekStartsOn","disabledDates"]),[S,k]=(0,eW.Z)(i,o),[C,j]=(0,l.useState)(!1),[P,A]=(0,l.useState)(!1),T=(0,l.useMemo)(()=>{let e=[];return u&&e.push({before:u}),d&&e.push({after:d}),[...e,...null!=O?O:[]]},[u,d,O]),M=(0,l.useMemo)(()=>{let e=new Map;return y?l.Children.forEach(y,t=>{var n;e.set(t.props.value,{text:null!==(n=(0,eo.qg)(t))&&void 0!==n?n:t.props.value,from:t.props.from,to:t.props.to})}):ex.forEach(t=>{e.set(t.value,{text:t.text,from:t.from,to:eq})}),e},[y]),N=(0,l.useMemo)(()=>{if(y)return(0,eo.sl)(y);let e=new Map;return ex.forEach(t=>e.set(t.value,t.text)),e},[y]),I=(null==S?void 0:S.selectValue)||"",R=ey(null==S?void 0:S.from,u,I,M),_=eb(null==S?void 0:S.to,d,I,M),D=R||_?ew(R,_,m,g):f,Z=(0,en.Z)(null!==(r=null!==(n=null!=_?_:R)&&void 0!==n?n:d)&&void 0!==r?r:eq),L=v&&!h;return l.createElement("div",Object.assign({ref:t,className:(0,er.q)("w-full min-w-[10rem] relative flex justify-between text-tremor-default max-w-sm shadow-tremor-input dark:shadow-dark-tremor-input rounded-tremor-default",b)},E),l.createElement(X,{as:"div",className:(0,er.q)("w-full",s?"rounded-l-tremor-default":"rounded-tremor-default",C&&"ring-2 ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted z-10")},l.createElement("div",{className:"relative w-full"},l.createElement(X.Button,{onFocus:()=>j(!0),onBlur:()=>j(!1),disabled:h,className:(0,er.q)("w-full outline-none text-left whitespace-nowrap truncate focus:ring-2 transition duration-100 rounded-l-tremor-default flex flex-nowrap border pl-3 py-2","rounded-l-tremor-default border-tremor-border text-tremor-content-emphasis focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",s?"rounded-l-tremor-default":"rounded-tremor-default",L?"pr-8":"pr-4",(0,eo.um)((0,eo.Uh)(R||_),h))},l.createElement(Q,{className:(0,er.q)(eg("calendarIcon"),"flex-none shrink-0 h-5 w-5 -ml-0.5 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle"),"aria-hidden":"true"}),l.createElement("p",{className:"truncate"},D)),L&&R?l.createElement("button",{type:"button",className:(0,er.q)("absolute outline-none inset-y-0 right-0 flex items-center transition duration-100 mr-4"),onClick:e=>{e.preventDefault(),null==c||c({}),k({})}},l.createElement(J.Z,{className:(0,er.q)(eg("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null),l.createElement(G.u,{className:"absolute z-10 min-w-min left-0",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},l.createElement(X.Panel,{focus:!0,className:(0,er.q)("divide-y overflow-y-auto outline-none rounded-tremor-default p-3 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},l.createElement(ez,Object.assign({mode:"range",showOutsideDays:!0,defaultMonth:Z,selected:{from:R,to:_},onSelect:e=>{null==c||c({from:null==e?void 0:e.from,to:null==e?void 0:e.to}),k({from:null==e?void 0:e.from,to:null==e?void 0:e.to})},locale:m,disabled:T,enableYearNavigation:x,classNames:{day_range_middle:(0,er.q)("!rounded-none aria-selected:!bg-tremor-background-subtle aria-selected:dark:!bg-dark-tremor-background-subtle aria-selected:!text-tremor-content aria-selected:dark:!bg-dark-tremor-background-subtle"),day_range_start:"rounded-r-none rounded-l-tremor-small aria-selected:text-tremor-brand-inverted dark:aria-selected:text-dark-tremor-brand-inverted",day_range_end:"rounded-l-none rounded-r-tremor-small aria-selected:text-tremor-brand-inverted dark:aria-selected:text-dark-tremor-brand-inverted"},weekStartsOn:w},e))))),s&&l.createElement($.R,{as:"div",className:(0,er.q)("w-48 -ml-px rounded-r-tremor-default",P&&"ring-2 ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted z-10"),value:I,onChange:e=>{let{from:t,to:n}=M.get(e),r=null!=n?n:eq;null==c||c({from:t,to:r,selectValue:e}),k({from:t,to:r,selectValue:e})},disabled:h},e=>{var t;let{value:n}=e;return l.createElement(l.Fragment,null,l.createElement($.R.Button,{onFocus:()=>A(!0),onBlur:()=>A(!1),className:(0,er.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-r-tremor-default transition duration-100 border px-4 py-2","border-tremor-border shadow-tremor-input text-tremor-content-emphasis focus:border-tremor-brand-subtle","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle",(0,eo.um)((0,eo.Uh)(n),h))},n&&null!==(t=N.get(n))&&void 0!==t?t:p),l.createElement(G.u,{className:"absolute z-10 w-full inset-x-0 right-0",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},l.createElement($.R.Options,{className:(0,er.q)("divide-y overflow-y-auto outline-none border my-1","shadow-tremor-dropdown bg-tremor-background border-tremor-border divide-tremor-border rounded-tremor-default","dark:shadow-dark-tremor-dropdown dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border")},null!=y?y:ex.map(e=>l.createElement(eF.Z,{key:e.value,value:e.value},e.text)))))}))});eU.displayName="DateRangePicker"},47047:function(e,t,n){n.d(t,{Z:function(){return g}});var r=n(69703),o=n(2265);n(50027),n(18174),n(21871);var i=n(41213),a=n(46457),l=n(54518);let c=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"}))};var s=n(8903),u=n(25163),d=n(70129);let f=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",width:"100%",height:"100%",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},t),o.createElement("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),o.createElement("line",{x1:"6",y1:"6",x2:"18",y2:"18"}))};var p=n(99250),h=n(65492),m=n(91753);let v=(0,h.fn)("MultiSelect"),g=o.forwardRef((e,t)=>{let{defaultValue:n,value:h,onValueChange:g,placeholder:y="Select...",placeholderSearch:b="Search",disabled:x=!1,icon:w,children:O,className:E}=e,S=(0,r._T)(e,["defaultValue","value","onValueChange","placeholder","placeholderSearch","disabled","icon","children","className"]),[k,C]=(0,a.Z)(n,h),{reactElementChildren:j,optionsAvailable:P}=(0,o.useMemo)(()=>{let e=o.Children.toArray(O).filter(o.isValidElement);return{reactElementChildren:e,optionsAvailable:(0,m.n0)("",e)}},[O]),[A,T]=(0,o.useState)(""),M=(null!=k?k:[]).length>0,N=(0,o.useMemo)(()=>A?(0,m.n0)(A,j):P,[A,j,P]),I=()=>{T("")};return o.createElement(u.R,Object.assign({as:"div",ref:t,defaultValue:k,value:k,onChange:e=>{null==g||g(e),C(e)},disabled:x,className:(0,p.q)("w-full min-w-[10rem] relative text-tremor-default",E)},S,{multiple:!0}),e=>{let{value:t}=e;return o.createElement(o.Fragment,null,o.createElement(u.R.Button,{className:(0,p.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-1.5","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",w?"pl-11 -ml-0.5":"pl-3",(0,m.um)(t.length>0,x))},w&&o.createElement("span",{className:(0,p.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},o.createElement(w,{className:(0,p.q)(v("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("div",{className:"h-6 flex items-center"},t.length>0?o.createElement("div",{className:"flex flex-nowrap overflow-x-scroll [&::-webkit-scrollbar]:hidden [scrollbar-width:none] gap-x-1 mr-5 -ml-1.5 relative"},P.filter(e=>t.includes(e.props.value)).map((e,n)=>{var r;return o.createElement("div",{key:n,className:(0,p.q)("max-w-[100px] lg:max-w-[200px] flex justify-center items-center pl-2 pr-1.5 py-1 font-medium","rounded-tremor-small","bg-tremor-background-muted dark:bg-dark-tremor-background-muted","bg-tremor-background-subtle dark:bg-dark-tremor-background-subtle","text-tremor-content-default dark:text-dark-tremor-content-default","text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis")},o.createElement("div",{className:"text-xs truncate "},null!==(r=e.props.children)&&void 0!==r?r:e.props.value),o.createElement("div",{onClick:n=>{n.preventDefault();let r=t.filter(t=>t!==e.props.value);null==g||g(r),C(r)}},o.createElement(f,{className:(0,p.q)(v("clearIconItem"),"cursor-pointer rounded-tremor-full w-3.5 h-3.5 ml-2","text-tremor-content-subtle hover:text-tremor-content","dark:text-dark-tremor-content-subtle dark:hover:text-tremor-content")})))})):o.createElement("span",null,y)),o.createElement("span",{className:(0,p.q)("absolute inset-y-0 right-0 flex items-center mr-2.5")},o.createElement(l.Z,{className:(0,p.q)(v("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),M&&!x?o.createElement("button",{type:"button",className:(0,p.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),C([]),null==g||g([])}},o.createElement(s.Z,{className:(0,p.q)(v("clearIconAllItems"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,o.createElement(d.u,{className:"absolute z-10 w-full",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},o.createElement(u.R.Options,{className:(0,p.q)("divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] left-0 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},o.createElement("div",{className:(0,p.q)("flex items-center w-full px-2.5","bg-tremor-background-muted","dark:bg-dark-tremor-background-muted")},o.createElement("span",null,o.createElement(c,{className:(0,p.q)("flex-none w-4 h-4 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("input",{name:"search",type:"input",autoComplete:"off",placeholder:b,className:(0,p.q)("w-full focus:outline-none focus:ring-none bg-transparent text-tremor-default py-2","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis"),onKeyDown:e=>{"Space"===e.code&&""!==e.target.value&&e.stopPropagation()},onChange:e=>T(e.target.value),value:A})),o.createElement(i.Z.Provider,Object.assign({},{onBlur:{handleResetSearch:I}},{value:{selectedValue:t}}),N))))})});g.displayName="MultiSelect"},76628:function(e,t,n){n.d(t,{Z:function(){return u}});var r=n(69703);n(50027),n(18174),n(21871);var o=n(41213),i=n(2265),a=n(99250),l=n(65492),c=n(25163);let s=(0,l.fn)("MultiSelectItem"),u=i.forwardRef((e,t)=>{let{value:n,className:u,children:d}=e,f=(0,r._T)(e,["value","className","children"]),{selectedValue:p}=(0,i.useContext)(o.Z),h=(0,l.NZ)(n,p);return i.createElement(c.R.Option,Object.assign({className:(0,a.q)(s("root"),"flex justify-start items-center cursor-default text-tremor-default p-2.5","ui-active:bg-tremor-background-muted ui-active:text-tremor-content-strong ui-selected:text-tremor-content-strong text-tremor-content-emphasis","dark:ui-active:bg-dark-tremor-background-muted dark:ui-active:text-dark-tremor-content-strong dark:ui-selected:text-dark-tremor-content-strong dark:ui-selected:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",u),ref:t,key:n,value:n},f),i.createElement("input",{type:"checkbox",className:(0,a.q)(s("checkbox"),"flex-none focus:ring-none focus:outline-none cursor-pointer mr-2.5","accent-tremor-brand","dark:accent-dark-tremor-brand"),checked:h,readOnly:!0}),i.createElement("span",{className:"whitespace-nowrap truncate"},null!=d?d:n))});u.displayName="MultiSelectItem"},95093:function(e,t,n){n.d(t,{Z:function(){return h}});var r=n(69703),o=n(2265),i=n(54518),a=n(8903),l=n(99250),c=n(65492),s=n(91753),u=n(25163),d=n(70129),f=n(46457);let p=(0,c.fn)("Select"),h=o.forwardRef((e,t)=>{let{defaultValue:n,value:c,onValueChange:h,placeholder:m="Select...",disabled:v=!1,icon:g,enableClear:y=!0,children:b,className:x}=e,w=(0,r._T)(e,["defaultValue","value","onValueChange","placeholder","disabled","icon","enableClear","children","className"]),[O,E]=(0,f.Z)(n,c),S=(0,o.useMemo)(()=>{let e=o.Children.toArray(b).filter(o.isValidElement);return(0,s.sl)(e)},[b]);return o.createElement(u.R,Object.assign({as:"div",ref:t,defaultValue:O,value:O,onChange:e=>{null==h||h(e),E(e)},disabled:v,className:(0,l.q)("w-full min-w-[10rem] relative text-tremor-default",x)},w),e=>{var t;let{value:n}=e;return o.createElement(o.Fragment,null,o.createElement(u.R.Button,{className:(0,l.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-2","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",g?"pl-10":"pl-3",(0,s.um)((0,s.Uh)(n),v))},g&&o.createElement("span",{className:(0,l.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},o.createElement(g,{className:(0,l.q)(p("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("span",{className:"w-[90%] block truncate"},n&&null!==(t=S.get(n))&&void 0!==t?t:m),o.createElement("span",{className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-3")},o.createElement(i.Z,{className:(0,l.q)(p("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),y&&O?o.createElement("button",{type:"button",className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),E(""),null==h||h("")}},o.createElement(a.Z,{className:(0,l.q)(p("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,o.createElement(d.u,{className:"absolute z-10 w-full",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},o.createElement(u.R.Options,{className:(0,l.q)("divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] left-0 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},b)))})});h.displayName="Select"},27166:function(e,t,n){n.d(t,{Z:function(){return c}});var r=n(69703),o=n(2265),i=n(25163),a=n(99250);let l=(0,n(65492).fn)("SelectItem"),c=o.forwardRef((e,t)=>{let{value:n,icon:c,className:s,children:u}=e,d=(0,r._T)(e,["value","icon","className","children"]);return o.createElement(i.R.Option,Object.assign({className:(0,a.q)(l("root"),"flex justify-start items-center cursor-default text-tremor-default px-2.5 py-2.5","ui-active:bg-tremor-background-muted ui-active:text-tremor-content-strong ui-selected:text-tremor-content-strong ui-selected:bg-tremor-background-muted text-tremor-content-emphasis","dark:ui-active:bg-dark-tremor-background-muted dark:ui-active:text-dark-tremor-content-strong dark:ui-selected:text-dark-tremor-content-strong dark:ui-selected:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",s),ref:t,key:n,value:n},d),c&&o.createElement(c,{className:(0,a.q)(l("icon"),"flex-none w-5 h-5 mr-1.5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}),o.createElement("span",{className:"whitespace-nowrap truncate"},null!=u?u:n))});c.displayName="SelectItem"},42556:function(e,t,n){n.d(t,{Z:function(){return T}});var r=n(69703),o=n(2265),i=n(83891),a=n(20044),l=n(10641),c=n(92381),s=n(71454),u=n(36601),d=n(37700),f=n(84152),p=n(34797),h=n(18318),m=n(39790);let v=(0,o.createContext)(null),g=Object.assign((0,h.yV)(function(e,t){let n=(0,c.M)(),{id:r="headlessui-description-".concat(n),...i}=e,a=function e(){let t=(0,o.useContext)(v);if(null===t){let t=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(t,e),t}return t}(),l=(0,u.T)(t);(0,m.e)(()=>a.register(r),[r,a.register]);let s={ref:l,...a.props,id:r};return(0,h.sY)({ourProps:s,theirProps:i,slot:a.slot||{},defaultTag:"p",name:a.name||"Description"})}),{});var y=n(67409);let b=(0,o.createContext)(null),x=Object.assign((0,h.yV)(function(e,t){let n=(0,c.M)(),{id:r="headlessui-label-".concat(n),passive:i=!1,...a}=e,l=function e(){let t=(0,o.useContext)(b);if(null===t){let t=Error("You used a