diff --git a/backend/provider_price_list.json b/backend/provider_price_list.json index 88802539..1af622f6 100644 --- a/backend/provider_price_list.json +++ b/backend/provider_price_list.json @@ -220,14 +220,74 @@ "is_active": true }, { - "model_name": "gemini-pro", - "provider": "Google Vertex AI", + "model_name": "gemini-1.0-pro-001", + "provider": "Google VertexAI", "start_date": "", - "match_pattern": "(?i)^(gemini-pro)(@[a-zA-Z0-9]+)?$", - "input_price": 0.00025, - "output_price": 0.0005, + "match_pattern": "(?i)^(gemini-1.0-pro-001)?$", + "input_price": 0.000125, + "output_price": 0.000375, "total_price": 0, - "is_active": false + "is_active": true + }, + { + "model_name": "gemini-1.0-pro-002", + "provider": "Google VertexAI", + "start_date": "", + "match_pattern": "(?i)^(gemini-1.0-pro-002)?$", + "input_price": 0.000125, + "output_price": 0.000375, + "total_price": 0, + "is_active": true + }, + { + "model_name": "gemini-1.0-pro-vision-001", + "provider": "Google VertexAI", + "start_date": "", + "match_pattern": "(?i)^(gemini-1.0-pro-vision-001)?$", + "input_price": 0.000125, + "output_price": 0.000375, + "total_price": 0, + "is_active": true + }, + { + "model_name": "gemini-1.5-flash-001", + "provider": "Google VertexAI", + "start_date": "", + "match_pattern": "(?i)^(gemini-1.5-flash-001)?$", + "input_price": 0.000125, + "output_price": 0.000375, + "total_price": 0, + "is_active": true + }, + { + "model_name": "gemini-1.5-flash-preview-0514", + "provider": "Google VertexAI", + "start_date": "", + "match_pattern": "(?i)^(gemini-1.5-flash-preview-0514)?$", + "input_price": 0.000125, + "output_price": 0.000375, + "total_price": 0, + "is_active": true + }, + { + "model_name": "gemini-1.5-pro-001", + "provider": "Google VertexAI", + "start_date": "", + "match_pattern": "(?i)^(gemini-1.5-pro-001)?$", + "input_price": 0.00125, + "output_price": 0.00375, + "total_price": 0, + "is_active": true + }, + { + "model_name": "gemini-1.5-pro-preview-0514", + "provider": "Google VertexAI", + "start_date": "", + "match_pattern": "(?i)^(gemini-1.5-pro-preview-0514)?$", + "input_price": 0.00125, + "output_price": 0.00375, + "total_price": 0, + "is_active": true }, { "model_name": "gpt-35-turbo-0125", @@ -479,6 +539,26 @@ "total_price": 0, "is_active": true }, + { + "model_name": "gpt-4o-2024-05-13", + "provider": "Azure OpenAI", + "start_date": "", + "match_pattern": "(?i)^(gpt-4o)(|-2024-05-13)$", + "input_price": 0.005, + "output_price": 0.015, + "total_price": 0, + "is_active": true + }, + { + "model_name": "gpt-4o-2024-05-13", + "provider": "OpenAI", + "start_date": "", + "match_pattern": "(?i)^(gpt-4o)(|-2024-05-13)$", + "input_price": 0.005, + "output_price": 0.015, + "total_price": 0, + "is_active": true + }, { "model_name": "text-bison", "provider": "Google Vertex AI", diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 44636b8e..8921f8a8 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -35,18 +35,6 @@ requests-mock = "^1.11.0" locust = "^2.24.1" -[tool.poetry.group.examples] -optional = true - -[tool.poetry.group.examples.dependencies] -ipykernel = "^6.25.2" -langchain = "^0.0" -openai = "^1" -boto3 = "^1.34.13" -anthropic = "^0.25.6" -rich = "^13.7.1" -langchain-anthropic = "^0.1.11" - [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/backend/src/app/web_api.py b/backend/src/app/web_api.py index b1664273..e3aceef6 100644 --- a/backend/src/app/web_api.py +++ b/backend/src/app/web_api.py @@ -1,10 +1,13 @@ +import re from typing import Annotated, Any import utils from _datetime import datetime, timezone from app.dependencies import get_provider_pricelist, get_transaction_context from auth.authorization import decode_and_validate_token -from auth.schemas import GetUserSchema +from auth.models import User +from auth.schemas import GetPartialUserSchema, GetUserSchema +from auth.use_cases import get_all_users from fastapi import Depends, Request, Security from fastapi.responses import JSONResponse from lato import TransactionContext @@ -27,8 +30,10 @@ from slugify import slugify from transactions.models import generate_uuid from transactions.schemas import ( + CreateTransactionSchema, GetTransactionLatencyStatisticsWithoutDateSchema, GetTransactionPageResponseSchema, + GetTransactionSchema, GetTransactionsLatencyStatisticsSchema, GetTransactionStatusStatisticsSchema, GetTransactionsUsageStatisticsSchema, @@ -37,6 +42,7 @@ StatisticTransactionSchema, ) from transactions.use_cases import ( + add_transaction, count_token_usage_for_project, count_transactions, delete_multiple_transactions, @@ -51,7 +57,7 @@ @app.get("/api/auth/whoami", dependencies=[Security(decode_and_validate_token)]) def whoami( - request: Request, user: dict = Depends(decode_and_validate_token) + request: Request, user: User = Depends(decode_and_validate_token) ) -> GetUserSchema: return GetUserSchema( external_id=user.external_id, @@ -256,6 +262,9 @@ async def get_paginated_transactions( project_id: str | None = None, sort_field: str | None = None, sort_type: str | None = None, + status_codes: str | None = None, + providers: str | None = None, + models: str | None = None, ) -> GetTransactionPageResponseSchema: """ API endpoint to retrieve a paginated list of transactions based on specified filters. @@ -269,9 +278,18 @@ async def get_paginated_transactions( :param project_id: Optional. Project ID to filter transactions by. :param sort_field: Optional. Field to sort by. :param sort_type: Optional. Ordering method (asc or desc). + :param status_codes: Optional. List of status codes for filtering transactions. + :param providers: Optional. List of providers for filtering transactions. + :param models: Optional. List of models for filtering transactions. """ if tags is not None: tags = tags.split(",") + if status_codes is not None: + status_codes = list(map(lambda x: int(x), status_codes.split(","))) + if providers is not None: + providers = providers.split(",") + if models is not None: + models = models.split(",") transactions = ctx.call( get_all_filtered_and_paginated_transactions, @@ -283,6 +301,9 @@ async def get_paginated_transactions( project_id=project_id, sort_field=sort_field, sort_type=sort_type, + status_codes=status_codes, + providers=providers, + models=models, ) projects = ctx.call(get_all_projects) @@ -305,6 +326,9 @@ async def get_paginated_transactions( date_from=date_from, date_to=date_to, project_id=project_id, + status_codes=status_codes, + providers=providers, + models=models, ) page_response = GetTransactionPageResponseSchema( items=new_transactions, @@ -355,7 +379,7 @@ async def get_transaction_usage_statistics_over_time( project_id=project_id, date_from=date_from, date_to=date_to, - status_code=200, + status_codes=[200], ) if count == 0: return [] @@ -365,7 +389,7 @@ async def get_transaction_usage_statistics_over_time( project_id=project_id, date_from=date_from, date_to=date_to, - status_code=200, + status_codes=[200], ) transactions = [ StatisticTransactionSchema( @@ -527,7 +551,7 @@ async def get_transaction_latency_statistics_over_time( project_id=project_id, date_from=date_from, date_to=date_to, - status_code=200, + status_codes=[200], null_generation_speed=False, ) if count == 0: @@ -538,7 +562,7 @@ async def get_transaction_latency_statistics_over_time( project_id=project_id, date_from=date_from, date_to=date_to, - status_code=200, + status_codes=[200], null_generation_speed=False, ) transactions = [ @@ -640,6 +664,87 @@ async def get_config( } +@app.get("/api/users", dependencies=[Security(decode_and_validate_token)]) +async def get_users( + ctx: Annotated[TransactionContext, Depends(get_transaction_context)], + auth_user: User = Depends(decode_and_validate_token), +) -> list[GetPartialUserSchema]: + users = ctx.call(get_all_users) + idx = next( + (i for i, usr in enumerate(users) if usr.external_id == auth_user.external_id), + None, + ) + if idx is not None: + temp = users.pop(idx) + users.insert(0, temp) + else: + users.insert(0, User(**auth_user.model_dump())) + + parsed_users = map( + lambda user: GetPartialUserSchema( + id=user.id, + email=user.email, + full_name=str(user.given_name + " " + user.family_name), + picture=user.picture, + ), + users, + ) + return list(parsed_users) + + +@app.post( + "/api/transactions", + response_class=JSONResponse, + status_code=201, + dependencies=[Security(decode_and_validate_token)], +) +def create_transaction( + request: Request, + data: CreateTransactionSchema, + ctx: Annotated[TransactionContext, Depends(get_transaction_context)], +) -> GetTransactionSchema: + if ((data.status_code == 200) and data.model and data.provider) and not ( + data.input_cost or data.output_cost or data.total_cost + ): + pricelist = get_provider_pricelist(request) + pricelist = [ + item + for item in pricelist + if item.provider == data.provider + and re.match(item.match_pattern, data.model) + ] + if len(pricelist) > 0: + if pricelist[0].input_price == 0: + data.input_cost, data.output_cost = 0, 0 + data.total_cost = ( + (data.input_tokens + data.output_tokens) + / 1000 + * pricelist[0].total_price + ) + else: + data.input_cost = pricelist[0].input_price * (data.input_tokens / 1000) + data.output_cost = pricelist[0].output_price * ( + data.output_tokens / 1000 + ) + data.total_cost = data.input_cost + data.output_cost + else: + data.input_cost, data.output_cost, data.total_cost = None, None, None + + if not data.generation_speed: + if data.output_tokens is not None and (data.output_tokens > 0): + data.generation_speed = ( + data.output_tokens + / (datetime.now(tz=timezone.utc) - data.request_time).total_seconds() + ) + elif data.output_tokens == 0: + data.generation_speed = None + else: + data.generation_speed = 0 + + created_transaction = ctx.call(add_transaction, data=data) + return GetTransactionSchema(**created_transaction.model_dump()) + + @app.post("/api/only_for_purpose/mock_transactions", response_class=JSONResponse) async def mock_transactions( ctx: Annotated[TransactionContext, Depends(get_transaction_context)], diff --git a/backend/src/auth/authorization.py b/backend/src/auth/authorization.py index c68e72b9..a5fb24c8 100644 --- a/backend/src/auth/authorization.py +++ b/backend/src/auth/authorization.py @@ -1,13 +1,14 @@ import json import urllib.request -from typing import Annotated, Optional +from typing import Annotated import jwt from app.dependencies import get_transaction_context from auth.models import User from auth.use_cases import add_user, get_user from config import config -from fastapi import Depends, Header, HTTPException +from fastapi import Depends, HTTPException, Security +from fastapi.security import APIKeyHeader from lato import TransactionContext @@ -56,101 +57,109 @@ def get_jwks_url(issuer_url): well_known_url = issuer_url + "/.well-known/openid-configuration" with urllib.request.urlopen(well_known_url) as response: well_known = json.load(response) - if not "jwks_uri" in well_known: + if "jwks_uri" not in well_known: raise Exception("jwks_uri not found in OpenID configuration") return well_known["jwks_uri"] -def verify_authorization(authorization: Optional[str] = Header(None)) -> str: - if not config.SSO_AUTH: - return "auth off" - try: - if authorization is None: - raise HTTPException( - status_code=401, detail="Authorization header is missing" - ) - token_type, token = authorization.split(" ") - if token_type.lower() != "bearer": - raise HTTPException(status_code=401, detail="Invalid authentication scheme") - return token - except ValueError: - raise HTTPException(status_code=401, detail="Invalid authorization header") - - -def decode_and_validate_token( - ctx: Annotated[TransactionContext, Depends(get_transaction_context)], - token: str = Depends(verify_authorization), -) -> User: - if not config.SSO_AUTH: - return User( - external_id="anonymous", - email="anonymous@unknown.com", - organization="Anonymous", - given_name="Anonymous", - family_name="Unknown", - picture="", - issuer="test", - ) - try: - unvalidated = jwt.decode(token, options={"verify_signature": False}) - - if "test" == unvalidated["iss"]: - return User( - external_id="test", - email="test@test.com", - organization="test", - given_name="test", - family_name="test", - picture="", - issuer="test", - ) +if config.SSO_AUTH: + api_key_header = APIKeyHeader(name="Authorization") - jwks_url = get_jwks_url(unvalidated["iss"]) - jwks_client = jwt.PyJWKClient(jwks_url) - header = jwt.get_unverified_header(token) - key = jwks_client.get_signing_key(header["kid"]).key - - if "microsoft" in unvalidated["iss"]: - expected_audience = config.AZURE_CLIENT_ID - print("using microsoft", expected_audience) - elif "google" in unvalidated["iss"]: - expected_audience = config.GOOGLE_CLIENT_ID - print("using google", expected_audience) - else: - raise HTTPException(status_code=401, detail="Invalid issuer") - - if expected_audience is None: - expected_audience = unvalidated.get("aud") - - decoded_token = jwt.decode( - token, key, [header["alg"]], audience=expected_audience - ) - user_id = decoded_token.get("sub") - if (db_user := ctx.call(get_user, external_id=user_id)) is None: - token_user = ( - UserBuilder() - .add_external_id(user_id) - .add_issuer(decoded_token.get("iss")) - ) - if "microsoft" in decoded_token.get("iss"): - given_name, family_name = decoded_token.get("name").split(" ") - token_user = ( - token_user.add_email(decoded_token.get("preferred_username")) - .add_given_name(given_name) - .add_family_name(family_name) + def verify_authorization(authorization: str = Security(api_key_header)) -> str: + try: + token_type, token = authorization.split(" ") + if token_type.lower() != "bearer": + raise HTTPException( + status_code=401, detail="Invalid authentication scheme" + ) + return token + except ValueError: + raise HTTPException(status_code=401, detail="Invalid authorization header") + + def decode_and_validate_token( + ctx: Annotated[TransactionContext, Depends(get_transaction_context)], + token: str = Depends(verify_authorization), + ) -> User: + try: + unvalidated = jwt.decode(token, options={"verify_signature": False}) + + if "test" == unvalidated["iss"]: + return User( + external_id="test", + email="test@test.com", + organization="test", + given_name="test", + family_name="test", + picture="", + issuer="test", ) - if "google" in decoded_token.get("iss"): + jwks_url = get_jwks_url(unvalidated["iss"]) + jwks_client = jwt.PyJWKClient(jwks_url) + header = jwt.get_unverified_header(token) + key = jwks_client.get_signing_key(header["kid"]).key + + if "microsoft" in unvalidated["iss"]: + expected_audience = config.AZURE_CLIENT_ID + elif "google" in unvalidated["iss"]: + expected_audience = config.GOOGLE_CLIENT_ID + else: + raise HTTPException(status_code=401, detail="Invalid issuer") + + if expected_audience is None: + expected_audience = unvalidated.get("aud") + + decoded_token = jwt.decode( + token, key, [header["alg"]], audience=expected_audience, leeway=10 + ) + user_id = decoded_token.get("sub") + new_user = None + if (db_user := ctx.call(get_user, external_id=user_id)) is None: token_user = ( - token_user.add_email(decoded_token.get("email")) - .add_organization(decoded_token.get("hd")) - .add_given_name(decoded_token.get("given_name")) - .add_family_name(decoded_token.get("family_name")) - .add_picture(decoded_token.get("picture")) + UserBuilder() + .add_external_id(user_id) + .add_issuer(decoded_token.get("iss")) ) - print(token_user.build()) - new_user = ctx.call(add_user, user=token_user.build()) + if "microsoft" in decoded_token.get("iss"): + given_name, family_name = decoded_token.get("name").split(" ") + token_user = ( + token_user.add_email(decoded_token.get("preferred_username")) + .add_given_name(given_name) + .add_family_name(family_name) + ) + + if "google" in decoded_token.get("iss"): + token_user = ( + token_user.add_email(decoded_token.get("email")) + .add_organization(decoded_token.get("hd")) + .add_given_name(decoded_token.get("given_name")) + .add_family_name(decoded_token.get("family_name")) + .add_picture(decoded_token.get("picture")) + ) + new_user = ctx.call(add_user, user=token_user.build()) + + return db_user if db_user is not None else new_user + except jwt.exceptions.DecodeError: + raise HTTPException(status_code=401, detail="Invalid token") + except jwt.exceptions.ImmatureSignatureError: + raise HTTPException( + status_code=401, detail="Token is not yet valid (iat claim)." + ) + except jwt.exceptions.ExpiredSignatureError: + raise HTTPException( + status_code=401, detail="Token has expired (exp claim)." + ) - return db_user if db_user is not None else new_user - except jwt.exceptions.DecodeError: - raise HTTPException(status_code=401, detail="Invalid token") +else: + + def decode_and_validate_token() -> User: + if not config.SSO_AUTH: + return User( + external_id="anonymous", + email="anonymous@unknown.com", + organization="Anonymous", + given_name="Anonymous", + family_name="Unknown", + picture="", + issuer="test", + ) diff --git a/backend/src/auth/schemas.py b/backend/src/auth/schemas.py index 62545184..c213b3fc 100644 --- a/backend/src/auth/schemas.py +++ b/backend/src/auth/schemas.py @@ -9,3 +9,10 @@ class GetUserSchema(BaseModel): family_name: str picture: str | None issuer: str + + +class GetPartialUserSchema(BaseModel): + id: str + email: str + full_name: str + picture: str | None diff --git a/backend/src/auth/use_cases.py b/backend/src/auth/use_cases.py index 74ac1874..7f4b146e 100644 --- a/backend/src/auth/use_cases.py +++ b/backend/src/auth/use_cases.py @@ -24,3 +24,14 @@ def add_user(user: User, user_repository: UserRepository) -> User: """ user_repository.add(user) return user + + +def get_all_users(user_repository: UserRepository) -> list[User]: + """ + Retrieve all users. + + :param user_repository: An instance of UserRepository used for accessing user data. + :return: The list of User objects. + """ + users = user_repository.get_all() + return users diff --git a/backend/src/transactions/schemas.py b/backend/src/transactions/schemas.py index 582d2032..3f50d79a 100644 --- a/backend/src/transactions/schemas.py +++ b/backend/src/transactions/schemas.py @@ -140,3 +140,28 @@ class GetTransactionPageResponseSchema(BaseModel): page_size: int total_pages: int total_elements: int + + +class CreateTransactionSchema(BaseModel): + project_id: str + request: dict[str, Any] + response: dict[str, Any] + tags: list[str] + provider: str + model: str | None + type: str + os: str | None + input_tokens: int | None + output_tokens: int | None + library: str + status_code: int + messages: list[dict[str, Any]] | str | None + last_message: str | None + prompt: str + error_message: str | None + generation_speed: int | float | None + request_time: datetime + input_cost: int | float | None + output_cost: int | float | None + total_cost: int | float | None + response_time: datetime | None diff --git a/backend/src/transactions/use_cases.py b/backend/src/transactions/use_cases.py index 587bd59c..2e85269f 100644 --- a/backend/src/transactions/use_cases.py +++ b/backend/src/transactions/use_cases.py @@ -1,13 +1,14 @@ import json import re +import utils from _datetime import datetime, timezone from transactions.models import Transaction from transactions.repositories import TransactionRepository +from transactions.schemas import CreateTransactionSchema from utils import ( count_tokens_for_streaming_response, create_transaction_query_from_filters, - req_resp_to_transaction_parser, ) @@ -58,8 +59,10 @@ def count_transactions( date_from: datetime | None = None, date_to: datetime | None = None, project_id: str | None = None, - status_code: int | None = None, null_generation_speed: bool = True, + status_codes: list[str] | None = None, + providers: list[str] | None = None, + models: list[str] | None = None, ) -> int: """ Count the number of transactions based on specified filters. @@ -69,12 +72,21 @@ def count_transactions( :param date_from: Optional. Start date for filtering transactions. :param date_to: Optional. End date for filtering transactions. :param project_id: Optional. Project ID to filter transactions by. - :param status_code: Optional. Status code to filter transactions by. :param null_generation_speed: Optional. Flag to include transactions with null generation speed. + :param status_codes: Optional. Status codes to filter transactions by. + :param providers: Providers to filter transactions by. + :param models: Models to filter transactions by. :return: The count of transactions that meet the specified filtering criteria. """ query = create_transaction_query_from_filters( - tags, date_from, date_to, project_id, status_code, null_generation_speed + tags, + date_from, + date_to, + project_id, + null_generation_speed, + status_codes, + providers, + models, ) return transaction_repository.count(query) @@ -104,12 +116,15 @@ def get_all_filtered_and_paginated_transactions( transaction_repository: TransactionRepository, page: int, page_size: int, - tags: str | None = None, + tags: list[str] | None = None, date_from: datetime | None = None, date_to: datetime | None = None, project_id: str | None = None, sort_field: str | None = None, sort_type: str | None = None, + status_codes: list[int] | None = None, + providers: list[str] | None = None, + models: list[str] | None = None, ) -> list[Transaction]: """ Retrieve a paginated and filtered list of transactions based on specified criteria. @@ -123,9 +138,14 @@ def get_all_filtered_and_paginated_transactions( :param project_id: Optional. Project ID to filter transactions by. :param sort_field: Optional. Field to sort by. :param sort_type: Optional. Ordering method (asc or desc). + :param status_codes: The transactions' status codes. + :param providers: The transactions' providers. + :param models: The transactions' models. :return: A paginated and filtered list of Transaction objects based on the specified criteria. """ - query = create_transaction_query_from_filters(tags, date_from, date_to, project_id) + query = create_transaction_query_from_filters( + tags, date_from, date_to, project_id, True, status_codes, providers, models + ) transactions = transaction_repository.get_paginated_and_filtered( page, page_size, query, sort_field, sort_type ) @@ -185,14 +205,13 @@ def store_transaction( ) content = "".join(content) example = json.loads(chunks[0]) - prompt = [ - message["content"] + messages = [ + message for message in json.loads(request.__dict__["_content"].decode("utf8"))[ "messages" ] - if message["role"] == "user" - ][::-1][0] - input_tokens = count_tokens_for_streaming_response(prompt, example["model"]) + ] + input_tokens = count_tokens_for_streaming_response(messages, example["model"]) output_tokens = count_tokens_for_streaming_response(content, example["model"]) response_content = dict( id=example["id"], @@ -216,12 +235,13 @@ def store_transaction( ) if "usage" not in response_content: - # TODO: check why we don't get usage data with streaming response response_content["usage"] = dict( prompt_tokens=0, completion_tokens=0, total_tokens=0 ) - - params = req_resp_to_transaction_parser(request, response, response_content) + param_extractor = utils.TransactionParamExtractor( + request, response, response_content + ) + params = param_extractor.extract() ai_model_version = ( ai_model_version if ai_model_version is not None else params["model"] @@ -259,12 +279,20 @@ def store_transaction( input_cost, output_cost, total_cost = 0, 0, 0 if params["output_tokens"] is not None and params["output_tokens"] > 0: - generation_speed = params["output_tokens"] / (datetime.now(tz=timezone.utc) - request_time).total_seconds() + generation_speed = ( + params["output_tokens"] + / (datetime.now(tz=timezone.utc) - request_time).total_seconds() + ) elif params["output_tokens"] == 0: generation_speed = None else: generation_speed = 0 + try: + content = json.loads(request.content) + except UnicodeDecodeError: + content = param_extractor.request_content + transaction = Transaction( project_id=project_id, request=dict( @@ -273,7 +301,7 @@ def store_transaction( host=request.headers.get("host", ""), headers=dict(request.headers), extensions=dict(request.extensions), - content=json.loads(request.content), + content=content, ), response=dict( status_code=response.status_code, @@ -304,7 +332,6 @@ def store_transaction( request_time=request_time, generation_speed=generation_speed, ) - transaction_repository.add(transaction) @@ -313,8 +340,10 @@ def get_list_of_filtered_transactions( date_from: datetime, date_to: datetime, transaction_repository: TransactionRepository, - status_code: int | None = None, null_generation_speed: bool = True, + status_codes: list[str] | None = None, + providers: list[str] | None = None, + models: list[str] | None = None, ) -> list[Transaction]: """ Retrieve a list of transactions filtered by project ID and date range. @@ -326,16 +355,28 @@ def get_list_of_filtered_transactions( :param date_from: The starting date for the filter. :param date_to: The ending date for the filter. :param transaction_repository: An instance of TransactionRepository for data retrieval. - :param status_code: The transactions' status code. :param null_generation_speed: Optional. Flag to include transactions with null generation speed. + :param status_codes: The transactions' status codes. + :param providers: The transactions' providers. + :param models: The transactions' models. :return: A list of Transaction objects that meet the specified criteria. """ query = create_transaction_query_from_filters( date_from=date_from, date_to=date_to, project_id=project_id, - status_code=status_code, null_generation_speed=null_generation_speed, + status_codes=status_codes, + providers=providers, + models=models, ) transactions = transaction_repository.get_filtered(query) return transactions + + +def add_transaction( + data: CreateTransactionSchema, transaction_repository: TransactionRepository +) -> Transaction: + transaction = Transaction(**data.model_dump()) + transaction_repository.add(transaction) + return transaction diff --git a/backend/src/utils.py b/backend/src/utils.py index bb348915..68ddbe6f 100644 --- a/backend/src/utils.py +++ b/backend/src/utils.py @@ -1,3 +1,4 @@ +import base64 import json import random import re @@ -66,12 +67,14 @@ def detect_subdomain(host, base_url) -> str | None: def create_transaction_query_from_filters( - tags: str | None = None, + tags: list[str] | None = None, date_from: datetime | None = None, date_to: datetime | None = None, project_id: str | None = None, - status_code: int | None = None, null_generation_speed: bool = True, + status_codes: list[int] | None = None, + providers: list[str] | None = None, + models: list[str] | None = None, ) -> dict: """ Create a MongoDB query dictionary based on specified filters for transactions. @@ -80,11 +83,14 @@ def create_transaction_query_from_filters( :param date_from: Optional. Start date for filtering transactions. :param date_to: Optional. End date for filtering transactions. :param project_id: Optional. Project ID to filter transactions by. - :param status_code: Optional. Status code of the transactions. :param null_generation_speed: Optional. Flag to include transactions with null generation speed. + :param status_codes: Optional. List of status codes of the transactions. + :param providers: Optional. List of providers of the transactions. + :param models: Optional. List of models of the transactions. :return: MongoDB query dictionary representing the specified filters. """ query = {} + or_conditions = [] if project_id is not None: query["project_id"] = project_id if tags is not None: @@ -95,10 +101,18 @@ def create_transaction_query_from_filters( query["response_time"]["$gte"] = date_from if date_to is not None: query["response_time"]["$lte"] = date_to - if status_code is not None: - query["status_code"] = status_code if not null_generation_speed: query["generation_speed"] = {"$ne": None} + if status_codes is not None: + for code in status_codes: + if code % 100 == 0: + or_conditions.append({"status_code": {"$gte": code, "$lt": code + 100}}) + if providers is not None: + query["provider"] = {"$in": providers} + if models is not None: + query["model"] = {"$in": models} + if or_conditions: + query["$or"] = or_conditions return query @@ -191,223 +205,521 @@ def build(self): } -def req_resp_to_transaction_parser(request, response, response_content) -> dict: - """ - Parse information from a request, response, and response content into a dictionary representing a transaction. +class UnsupportedProviderError(Exception): + """Exception raised for unsupported providers.""" - :param request: The request object. - :param response: The response object. - :param response_content: The content of the response. - :return: A dictionary containing parsed information from the request, response, and response content. - """ - response_headers = parse_headers_to_dict( - response.__dict__["headers"].__dict__["_list"] - ) - request_headers = parse_headers_to_dict( - request.__dict__["headers"].__dict__["_list"] - ) - request_content = json.loads(request.__dict__["_content"].decode("utf8")) - url = str(request.__dict__["url"]) + def __init__(self, url): + self.message = f"Unsupported provider for URL: {url}" + super().__init__(self.message) - azure_embeddings_pattern = re.compile(r".*openai\.azure\.com.*embeddings.*") - azure_completions_pattern = re.compile(r".*openai\.azure\.com.*completions.*") - openai_chat_completions_pattern = re.compile( - r".*api\.openai\.com.*chat.*completions.*" - ) - openai_completions_pattern = re.compile( - r".*api\.openai\.com(?!.*chat).*\/completions.*" - ) - openai_embeddings_pattern = re.compile(r".*api\.openai\.com.*embeddings.*") - anthropic_pattern = re.compile(r".*anthropic\.com.*") - - transaction_params = TransactionParamsBuilder() - transaction_params.add_library(request_headers["user-agent"]).add_status_code( - response.__dict__["status_code"] - ).add_model(request_content.get("model", None)).add_os( - request_headers.get("x-stainless-os", None) - ) - if "usage" in response_content: - transaction_params.add_input_tokens( - response_content["usage"].get("prompt_tokens", 0) - ).add_output_tokens(response_content["usage"].get("completion_tokens", 0)) +class TransactionParamExtractor: + def __init__(self, request, response, response_content) -> None: + self.response_headers = parse_headers_to_dict( + response.__dict__["headers"].__dict__["_list"] + ) + self.request_headers = parse_headers_to_dict( + request.__dict__["headers"].__dict__["_list"] + ) + self.request_content = self._decode_request_content(request) + + self.response = response + self.response_content = response_content + self.url = str(getattr(request, "url", "")) + self.pattern = self._detect_pattern(self.url) + + @staticmethod + def _decode_request_content(request): + try: + return json.loads(request.__dict__["_content"].decode("utf8")) + except UnicodeDecodeError: + data = {} + parts = request.__dict__["_content"].split( + request.__dict__["_content"].split(b"\r\n")[0] + )[:-1] + + if b"mask" not in request.__dict__["_content"]: + photo_part = parts[-1] + parts = parts[:-1] + else: + mask_part = parts[-1] + photo_part = parts[-2] + parts = parts[:-2] + mask_bytes = mask_part.split(b"\r\n\r\n")[1] + data["mask"] = base64.b64encode(mask_bytes).decode("utf-8") + + for part in parts: + if part: + part = part.strip().split(b"; ")[1] + header, value = part.split(b"\r\n\r\n") + header = header.split(b"=")[1].replace(b'"', b"").decode("utf-8") + data[header] = value.decode("utf-8") - if azure_embeddings_pattern.match(url): - transaction_params.add_type("embedding").add_provider("Azure OpenAI") + photo_bytes = photo_part.split(b"\r\n\r\n")[1] + data["image"] = base64.b64encode(photo_bytes).decode("utf-8") + + return data + + @staticmethod + def _detect_pattern(url): + patterns = { + "Azure Embeddings": r".*openai\.azure\.com.*embeddings.*", + "Azure Completions": r".*openai\.azure\.com.*completions.*", + "OpenAI Chat Completions": r".*api\.openai\.com.*chat.*completions.*", + "OpenAI Completions": r".*api\.openai\.com(?!.*chat).*\/completions.*", + "OpenAI Embeddings": r".*api\.openai\.com.*embeddings.*", + "OpenAI Images Variations": r".*api\.openai\.com.*images.*variations.*", + "OpenAI Image Generations": r".*api\.openai\.com.*images.*generations.*", + "OpenAI Image Edits": r".*api\.openai\.com.*images.*edits.*", + "Anthropic": r".*anthropic\.com.*", + "VertexAI": r".*-aiplatform\.googleapis\.com/v1.*", + } + + for pattern_name, pattern_regex in patterns.items(): + if re.match(pattern_regex, url): + return pattern_name + return "Unsupported" + + def _extract_from_azure_embeddings(self) -> dict: + extracted = { + "type": "embedding", + "provider": "Azure OpenAI", + } messages = [] - if isinstance(request_content["input"], list): + if isinstance(self.request_content["input"], list): prompt = ( - "[" + ", ".join(map(lambda x: str(x), request_content["input"])) + "]" + "[" + + ", ".join(map(lambda x: str(x), self.request_content["input"])) + + "]" ) else: - prompt = request_content["input"] - transaction_params.add_prompt(prompt) + prompt = self.request_content["input"] + extracted["prompt"] = prompt messages.append({"role": "user", "content": prompt}) - if response.__dict__["status_code"] > 200: - last_message = response_content["error"]["message"] - transaction_params.add_error_message(last_message) + + if self.response.__dict__["status_code"] > 200: + last_message = self.response_content["error"]["message"] + extracted["error_message"] = last_message + extracted["last_message"] = last_message messages.append({"role": "error", "content": last_message}) else: - transaction_params.add_model(response_content["model"]) - if isinstance(response_content["data"][0]["embedding"], list): + extracted["model"] = self.response_content["model"] + if isinstance(self.response_content["data"][0]["embedding"], list): last_message = ( "[" + ", ".join( - map(lambda x: str(x), response_content["data"][0]["embedding"]) + map( + lambda x: str(x), + self.response_content["data"][0]["embedding"], + ) ) + "]" ) else: - last_message = response_content["data"][0]["embedding"] - transaction_params.add_last_message(last_message) + last_message = self.response_content["data"][0]["embedding"] + extracted["last_message"] = last_message messages.append({"role": "system", "content": last_message}) - transaction_params.add_messages(messages) + extracted["messages"] = messages + + return extracted - if azure_completions_pattern.match(url): + def _extract_from_azure_completions(self) -> dict: + extracted = { + "type": "completions", + "provider": "Azure OpenAI", + } prompt = [ message["content"] - for message in request_content["messages"] + for message in self.request_content["messages"] if message["role"] == "user" ][::-1][0] - transaction_params.add_type("completions").add_provider( - "Azure OpenAI" - ).add_prompt(prompt if prompt else request_content["messages"][0]["content"]) - messages = request_content["messages"] - if response.__dict__["status_code"] > 200: + extracted["prompt"] = ( + prompt if prompt else self.request_content["messages"][0]["content"] + ) + messages = self.request_content["messages"] + if self.response.__dict__["status_code"] > 200: messages.append( { "role": "error", - "content": response_content["error"]["message"] - if "error" in response_content.keys() - else response_content["message"], + "content": self.response_content["error"]["message"] + if "error" in self.response_content.keys() + else self.response_content["message"], } ) - - transaction_params.add_error_message( - response_content["error"]["message"] - if "error" in response_content.keys() - else response_content["message"] - ).add_last_message( - response_content["error"]["message"] - if "error" in response_content.keys() - else response_content["message"] - ).add_messages( - messages + extracted["error_message"] = ( + self.response_content["error"]["message"] + if "error" in self.response_content.keys() + else self.response_content["message"] + ) + extracted["last_message"] = ( + self.response_content["error"]["message"] + if "error" in self.response_content.keys() + else self.response_content["message"] ) + extracted["messages"] = messages else: - messages.append(response_content["choices"][0]["message"]) - - transaction_params.add_messages(messages).add_model( - response_content["model"] - ).add_last_message(response_content["choices"][0]["message"]["content"]) - - if openai_chat_completions_pattern.match(url): + messages.append(self.response_content["choices"][0]["message"]) + extracted["messages"] = messages + extracted["model"] = self.response_content["model"] + extracted["last_message"] = self.response_content["choices"][0]["message"][ + "content" + ] + + return extracted + + def _extract_from_openai_chat_completions(self) -> dict: + extracted = { + "type": "chat completions", + "provider": "OpenAI", + } prompt = [ message["content"] - for message in request_content["messages"] + for message in self.request_content["messages"] if message["role"] == "user" ][::-1][0] - transaction_params.add_type("chat completions").add_provider( - "OpenAI" - ).add_prompt(prompt if prompt else request_content["messages"][0]["content"]) - messages = request_content["messages"] - if response.__dict__["status_code"] > 200: + + if len(prompt) == 2: + prompt = [cont for cont in prompt if cont["type"] == "text"][0]["text"] + + extracted["prompt"] = ( + prompt if prompt else self.request_content["messages"][0]["content"] + ) + messages = self.request_content["messages"] + if self.response.__dict__["status_code"] > 200: messages.append( - {"role": "error", "content": response_content["error"]["message"]} + {"role": "error", "content": self.response_content["error"]["message"]} + ) + extracted["error_message"] = self.response_content["error"]["message"] + extracted["last_message"] = self.response_content["error"]["message"] + else: + messages.append(self.response_content["choices"][0]["message"]) + extracted["messages"] = messages + extracted["model"] = ( + self.response_headers["openai-model"] + if "openai-model" in self.response_headers + else self.response_content["model"] ) - transaction_params.add_error_message( - response_content["error"]["message"] - ).add_last_message(response_content["error"]["message"]).add_messages( - messages + extracted["last_message"] = self.response_content["choices"][0]["message"][ + "content" + ] + + return extracted + + def _extract_from_openai_completions(self) -> dict: + extracted = { + "type": "completions", + "provider": "OpenAI", + "prompt": self.request_content["prompt"], + } + messages = [{"role": "user", "content": self.request_content["prompt"]}] + if self.response.__dict__["status_code"] > 200: + messages.append( + {"role": "error", "content": self.response_content["error"]["message"]} ) + extracted["error_message"] = self.response_content["error"]["message"] + extracted["last_message"] = self.response_content["error"]["message"] + extracted["messages"] = messages + else: - messages.append(response_content["choices"][0]["message"]) - transaction_params.add_messages(messages).add_model( - response_headers["openai-model"] - if "openai-model" in response_headers - else response_content["model"] - ).add_last_message(response_content["choices"][0]["message"]["content"]) - - if openai_completions_pattern.match(url): - transaction_params.add_type("completions").add_provider("OpenAI").add_prompt( - request_content["prompt"] - ) - messages = [{"role": "user", "content": request_content["prompt"]}] - if response.__dict__["status_code"] > 200: messages.append( - {"role": "error", "content": response_content["error"]["message"]} + { + "role": "system", + "content": self.response_content["choices"][0]["text"], + } ) - transaction_params.add_error_message( - response_content["error"]["message"] - ).add_last_message(response_content["error"]["message"]).add_messages( - messages + extracted["messages"] = messages + extracted["model"] = ( + self.response_headers["openai-model"] + if "openai-model" in self.response_headers + else self.response_content["model"] ) + extracted["last_message"] = self.response_content["choices"][0]["text"] + + return extracted + + def _extract_from_openai_images_variations(self) -> dict: + extracted = { + "type": "images variations", + "provider": "OpenAI", + "prompt": self.request_content["image"], + "model": self.request_content["model"], + } + messages = [{"role": "user", "content": self.request_content["image"]}] + if self.response.__dict__["status_code"] > 200: + # possible TOFIX + messages.append( + {"role": "error", "content": self.response_content["error"]["message"]} + ) + extracted["error_message"] = self.response_content["error"]["message"] + extracted["last_message"] = self.response_content["error"]["message"] + extracted["messages"] = messages else: messages.append( - {"role": "system", "content": response_content["choices"][0]["text"]} + { + "role": "system", + "content": "\n".join( + [data["url"] for data in self.response_content["data"]] + ), + } + ) + extracted["messages"] = messages + extracted["last_message"] = "\n".join( + [data["url"] for data in self.response_content["data"]] ) - transaction_params.add_messages(messages).add_model( - response_headers["openai-model"] - if "openai-model" in response_headers - else response_content["model"] - ).add_last_message(response_content["choices"][0]["text"]) - - if openai_embeddings_pattern.match(url): - transaction_params.add_type("embedding").add_provider("OpenAI") + + return extracted + + def _extract_from_openai_images_generations(self) -> dict: + extracted = { + "type": "images generations", + "provider": "OpenAI", + "prompt": self.request_content["prompt"], + "model": self.request_content["model"], + } + messages = [{"role": "user", "content": self.request_content["prompt"]}] + if self.response.__dict__["status_code"] > 200: + # possible TOFIX + messages.append( + {"role": "error", "content": self.response_content["error"]["message"]} + ) + extracted["error_message"] = self.response_content["error"]["message"] + extracted["last_message"] = self.response_content["error"]["message"] + extracted["messages"] = messages + else: + messages.append( + { + "role": "system", + "content": "\n".join( + [data["url"] for data in self.response_content["data"]] + ), + } + ) + extracted["messages"] = messages + extracted["last_message"] = "\n".join( + [data["url"] for data in self.response_content["data"]] + ) + + return extracted + + def _extract_from_openai_images_edit(self) -> dict: + extracted = { + "type": "images edits", + "provider": "OpenAI", + "prompt": self.request_content["prompt"], + "model": self.request_content["model"], + } + messages = [ + { + "role": "user", + "content": self.request_content["prompt"], + "image": self.request_content["image"], + "mask": self.request_content["mask"], + } + ] + if self.response.__dict__["status_code"] > 200: + # possible TOFIX + messages.append( + {"role": "error", "content": self.response_content["error"]["message"]} + ) + extracted["error_message"] = self.response_content["error"]["message"] + extracted["last_message"] = self.response_content["error"]["message"] + extracted["messages"] = messages + else: + messages.append( + { + "role": "system", + "content": "\n".join( + [data["url"] for data in self.response_content["data"]] + ), + } + ) + extracted["messages"] = messages + extracted["last_message"] = "\n".join( + [data["url"] for data in self.response_content["data"]] + ) + return extracted + + def _extract_from_openai_embeddings(self): + extracted = {"type": "embedding", "provider": "OpenAI"} messages = [] - if isinstance(request_content["input"], list): + if isinstance(self.request_content["input"], list): prompt = ( - "[" + ", ".join(map(lambda x: str(x), request_content["input"])) + "]" + "[" + + ", ".join(map(lambda x: str(x), self.request_content["input"])) + + "]" ) else: - prompt = request_content["input"] + prompt = self.request_content["input"] messages.append({"role": "user", "content": prompt}) - transaction_params.add_prompt(prompt) - if response.__dict__["status_code"] > 200: - last_message = response_content["error"]["message"] - transaction_params.add_error_message(last_message).add_last_message(None) + extracted["prompt"] = prompt + if self.response.__dict__["status_code"] > 200: + last_message = self.response_content["error"]["message"] + extracted["error_message"] = last_message + extracted["last_message"] = last_message messages.append({"role": "error", "content": last_message}) else: - transaction_params.add_model(response_content["model"]) - if isinstance(response_content["data"][0]["embedding"], list): + extracted["model"] = self.response_content["model"] + if isinstance(self.response_content["data"][0]["embedding"], list): last_message = ( "[" + ", ".join( - map(lambda x: str(x), response_content["data"][0]["embedding"]) + map( + lambda x: str(x), + self.response_content["data"][0]["embedding"], + ) ) + "]" ) else: - last_message = response_content["data"][0]["embedding"] - transaction_params.add_last_message(last_message) + last_message = self.response_content["data"][0]["embedding"] + extracted["last_message"] = last_message messages.append({"role": "system", "content": last_message}) - transaction_params.add_messages(messages) + extracted["messages"] = messages + + return extracted + + def _extract_from_anthropic(self): + extracted = { + "type": "chat", + "provider": "Anthropic", + "prompt": self.request_content["messages"][0]["content"], + } + messages = self.request_content["messages"] - if anthropic_pattern.match(url): - transaction_params.add_type("chat").add_provider("Anthropic") - transaction_params.add_prompt(request_content["messages"][0]["content"]) - messages = request_content["messages"] - if response.__dict__["status_code"] > 200: + if self.response.__dict__["status_code"] > 200: + messages.append( + {"role": "error", "content": self.response_content["error"]["message"]} + ) + extracted["error_message"] = self.response_content["error"]["message"] + extracted["last_message"] = self.response_content["error"]["message"] + extracted["messages"] = messages + else: messages.append( - {"role": "error", "content": response_content["error"]["message"]} + { + "role": "assistant", + "content": self.response_content["content"][0]["text"], + } + ) + extracted["messages"] = messages + extracted["model"] = self.response_content["model"] + extracted["last_message"] = self.response_content["content"][0]["text"] + extracted["input_tokens"] = self.response_content["usage"].get( + "input_tokens", 0 ) - transaction_params.add_error_message( - response_content["error"]["message"] - ).add_last_message(response_content["error"]["message"]).add_messages( - messages + extracted["output_tokens"] = self.response_content["usage"].get( + "output_tokens", 0 + ) + + return extracted + + def _extract_from_vertexai(self): + extracted = { + "type": "chat", + "provider": "Google VertexAI", + "prompt": self.request_content["contents"][::-1][0]["parts"]["text"], + "model": self.url.split("/")[-1].split(":")[0], + } + messages = [ + {"role": message["role"], "content": message["parts"]["text"]} + for message in self.request_content["contents"] + ] + if self.response.__dict__["status_code"] > 200: + messages.append( + {"role": "error", "content": self.response_content["error"]["message"]} ) + extracted["error_message"] = self.response_content["error"]["message"] + extracted["last_message"] = self.response_content["error"]["message"] + extracted["messages"] = messages else: messages.append( - {"role": "assistant", "content": response_content["content"][0]["text"]} + { + "role": self.response_content["candidates"][0]["content"]["role"], + "content": " ".join( + [ + message["text"] + for message in self.response_content["candidates"][0][ + "content" + ]["parts"] + ] + ), + } + ) + extracted["messages"] = messages + extracted["last_message"] = " ".join( + [ + part["text"] + for part in self.response_content["candidates"][0]["content"][ + "parts" + ] + ] + ) + extracted["input_tokens"] = self.response_content["usageMetadata"].get( + "promptTokenCount", 0 ) - transaction_params.add_messages(messages).add_model( - response_content["model"] - ).add_last_message(response_content["content"][0]["text"]) + extracted["output_tokens"] = self.response_content["usageMetadata"].get( + "candidatesTokenCount", 0 + ) + + return extracted + + def extract(self) -> dict: + transaction_params = TransactionParamsBuilder() + transaction_params.add_library( + self.request_headers["user-agent"] + ).add_status_code(self.response.__dict__["status_code"]).add_model( + self.request_content.get("model", None) + ).add_os( + self.request_headers.get("x-stainless-os", None) + ) + + if "usage" in self.response_content: transaction_params.add_input_tokens( - response_content["usage"].get("input_tokens", 0) - ).add_output_tokens(response_content["usage"].get("output_tokens", 0)) + self.response_content["usage"].get("prompt_tokens", 0) + ) + output_tokens = self.response_content["usage"].get("completion_tokens", 0) + transaction_params.add_output_tokens(output_tokens) + + if self.pattern == "Azure Embeddings": + extracted = self._extract_from_azure_embeddings() + if self.pattern == "Azure Completions": + extracted = self._extract_from_azure_completions() + if self.pattern == "OpenAI Chat Completions": + extracted = self._extract_from_openai_chat_completions() + if self.pattern == "OpenAI Completions": + extracted = self._extract_from_openai_completions() + if self.pattern == "OpenAI Embeddings": + extracted = self._extract_from_openai_embeddings() + if self.pattern == "Anthropic": + extracted = self._extract_from_anthropic() + if self.pattern == "VertexAI": + extracted = self._extract_from_vertexai() + if self.pattern == "OpenAI Images Variations": + extracted = self._extract_from_openai_images_variations() + if self.pattern == "OpenAI Image Generations": + extracted = self._extract_from_openai_images_generations() + if self.pattern == "OpenAI Image Edits": + extracted = self._extract_from_openai_images_edit() + if self.pattern == "Unsupported": + raise UnsupportedProviderError(self.url) + + transaction_params.add_type(extracted["type"]) + transaction_params.add_provider(extracted["provider"]) + transaction_params.add_prompt(extracted["prompt"]) + + if self.response.__dict__["status_code"] > 200: + transaction_params.add_error_message(extracted["error_message"]) + else: + transaction_params.add_last_message(extracted["last_message"]) - return transaction_params.build() + if "model" in extracted: + transaction_params.add_model(extracted["model"]) + if "input_tokens" in extracted: + transaction_params.add_input_tokens(extracted["input_tokens"]) + if "output_tokens" in extracted: + transaction_params.add_output_tokens(extracted["output_tokens"]) + + transaction_params.add_messages(extracted["messages"]) + return transaction_params.build() def token_counter_for_transactions( @@ -1062,11 +1374,9 @@ def count_tokens_for_streaming_response(messages: list | str, model: str) -> int if isinstance(messages, str): tokens = encoder.encode(messages) else: - full_prompt = "" - for message in messages: - role = message["role"] - content = message["content"] - full_prompt += f"{role}: {content}\n" + full_prompt = "\n".join( + [f"{message['role']}: {message['content']}" for message in messages] + ) tokens = encoder.encode(full_prompt) return len(tokens) @@ -1080,6 +1390,15 @@ def json(self): return self.content +def truncate_float(number, decimals): + if isinstance(number, float): + str_number = str(number) + integer_part, decimal_part = str_number.split(".") + truncated_decimal_part = decimal_part[:decimals] + return float(f"{integer_part}.{truncated_decimal_part}") + return number + + class PeriodEnum(str, Enum): week = "week" year = "year" @@ -1093,12 +1412,16 @@ class PeriodEnum(str, Enum): {"provider_name": "OpenAI", "api_base_placeholder": "https://api.openai.com/v1"}, { "provider_name": "Azure OpenAI", - "api_base_placeholder": "https://your-deployment-name.openai.azure.com", + "api_base_placeholder": "https://.openai.azure.com", }, { "provider_name": "Anthropic", "api_base_placeholder": "https://api.anthropic.com", }, + { + "provider_name": "Google VertexAI", + "api_base_placeholder": "https://-aiplatform.googleapis.com/v1", + }, { "provider_name": "Other", "api_base_placeholder": "https://llmapi.provider.com/v1", diff --git a/backend/test_transactions_tokens_cost_speed.csv b/backend/test_transactions_tokens_cost_speed.csv new file mode 100644 index 00000000..86d2cd9c --- /dev/null +++ b/backend/test_transactions_tokens_cost_speed.csv @@ -0,0 +1 @@ +provider;model;total_tokens;input_tokens;output_tokens;status_code;request_time;response_time Azure OpenAI;gpt-35-turbo-0613;30;30;0;404;2023-11-01T12:00:01.000Z;2023-11-01T12:00:05.000Z Azure OpenAI;gpt-35-turbo-0613;100;50;50;200;2023-11-01T12:01:00.000Z;2023-11-01T12:01:11.000Z Azure OpenAI;gpt-35-turbo-0613;74;15;59;200;2023-11-01T12:01:50.000Z;2023-11-01T12:01:51.000Z Azure OpenAI;gpt-35-turbo-0613;69;69;0;401;2023-11-01T12:03:23.000Z;2023-11-01T12:03:25.000Z Azure OpenAI;gpt-35-turbo-0613;80;80;0;303;2023-11-01T12:04:45.000Z;2023-11-01T12:04:45.010Z Azure OpenAI;gpt-35-turbo-0613;556;200;356;200;2023-11-01T12:04:59.000Z;2023-11-01T12:06:59.999Z OpenAI;babbage-002;142;65;77;200;2023-11-01T12:06:01.000Z;2023-11-01T12:06:11.000Z OpenAI;gpt-4-0613;332;300;32;200;2023-11-01T12:06:23.000Z;2023-11-01T12:06:25.000Z Anthropic;claude-2.0;107;42;65;200;2023-11-01T12:15:41.000Z;2023-11-01T12:15:41.998Z OpenAI;babbage-002;900;900;0;404;2023-11-01T12:17:19.000Z;2023-11-01T12:17:33.000Z OpenAI;babbage-002;801;36;765;200;2023-11-01T12:23:01.000Z;2023-11-01T12:23:09.871Z OpenAI;babbage-002;873;87;786;200;2023-11-01T12:23:10.000Z;2023-11-01T12:23:20.000Z OpenAI;babbage-002;988;988;0;301;2023-11-01T12:23:40.000Z;2023-11-01T12:23:59.000Z OpenAI;gpt-4-0613;165;65;100;200;2023-11-01T12:25:10.000Z;2023-11-01T12:25:15.000Z Anthropic;claude-2.0;312;36;276;200;2023-11-01T12:25:50.000Z;2023-11-01T12:25:52.997Z Azure OpenAI;gpt-35-turbo-0613;105;29;76;200;2023-11-01T12:29:03.000Z;2023-11-01T12:29:03.850Z Azure OpenAI;gpt-35-turbo-0613;977;977;0;500;2023-11-01T12:29:20.000Z;2023-11-01T12:29:30.000Z OpenAI;gpt-4-0613;377;377;0;502;2023-11-01T12:29:36.000Z;2023-11-01T12:29:47.000Z Azure OpenAI;gpt-35-turbo-0613;2012;12;2000;200;2023-11-01T12:36:01.000Z;2023-11-01T12:36:21.311Z Azure OpenAI;gpt-35-turbo-0613;577;34;543;200;2023-11-01T12:41:12.000Z;2023-11-01T12:41:20.000Z Azure OpenAI;gpt-35-turbo-0613;289;88;201;200;2023-11-01T12:42:12.000Z;2023-11-01T12:42:15.000Z Azure OpenAI;gpt-35-turbo-0613;664;56;608;200;2023-11-01T12:50:03.000Z;2023-11-01T12:50:13.000Z Azure OpenAI;gpt-35-turbo-0613;2050;43;2007;200;2023-11-01T12:51:05.000Z;2023-11-01T12:51:37.000Z Azure OpenAI;gpt-35-turbo-0613;300;300;0;404;2023-11-01T12:52:15.000Z;2023-11-01T12:52:15.056Z Azure OpenAI;gpt-35-turbo-0613;65;10;55;200;2023-11-01T12:52:55.000Z;2023-11-01T12:52:57.000Z Azure OpenAI;gpt-35-turbo-0613;23;23;0;401;2023-11-01T12:53:05.000Z;2023-11-01T12:53:05.789Z Azure OpenAI;gpt-35-turbo-0613;1078;578;500;200;2023-11-01T12:53:57.000Z;2023-11-01T12:54:59.942Z Azure OpenAI;gpt-35-turbo-0613;334;234;100;200;2023-11-01T12:54:07.000Z;2023-11-01T12:54:10.000Z OpenAI;gpt-4-0613;81;27;54;200;2023-11-01T12:57:08.000Z;2023-11-01T12:57:08.975Z OpenAI;babbage-002;154;56;98;200;2023-11-01T12:57:16.000Z;2023-11-01T12:57:16.985Z OpenAI;babbage-002;201;98;103;200;2023-11-01T12:57:56.000Z;2023-11-01T12:57:58.080Z OpenAI;babbage-002;607;40;567;200;2023-11-01T12:58:11.000Z;2023-11-01T12:58:17.100Z OpenAI;babbage-002;90;90;0;301;2023-11-01T12:58:20.000Z;2023-11-01T12:58:22.000Z OpenAI;babbage-002;309;309;0;404;2023-11-01T12:58:30.000Z;2023-11-01T12:58:30.048Z OpenAI;babbage-002;10;10;0;401;2023-11-01T12:59:30.000Z;2023-11-01T12:59:31.000Z Azure OpenAI;gpt-35-turbo-0613;49;4;45;200;2023-11-01T13:59:30.000Z;2023-11-01T13:59:40.911Z Azure OpenAI;gpt-35-turbo-0613;92;67;25;200;2023-11-01T14:50:30.000Z;2023-11-01T14:50:30.600Z Azure OpenAI;gpt-35-turbo-0613;976;976;0;404;2023-11-01T14:51:30.000Z;2023-11-01T14:51:35.000Z Anthropic;claude-2.0;100;100;0;401;2023-11-01T14:52:31.000Z;2023-11-01T14:52:31.030Z Anthropic;claude-2.0;812;26;786;200;2023-11-01T15:11:31.000Z;2023-11-01T15:11:39.060Z Anthropic;claude-2.0;67;67;0;300;2023-11-01T15:33:21.000Z;2023-11-01T15:33:23.000Z Anthropic;claude-2.0;237;28;209;200;2023-11-01T17:13:11.000Z;2023-11-01T17:13:15.000Z Anthropic;claude-2.0;313;26;287;200;2023-11-01T20:34:00.000Z;2023-11-01T20:34:05.392Z Azure OpenAI;gpt-35-turbo-0613;34;11;23;200;2023-11-01T20:55:40.000Z;2023-11-01T20:55:41.000Z OpenAI;babbage-002;9;4;5;200;2023-11-02T04:32:11.000Z;2023-11-02T04:32:11.835Z OpenAI;gpt-4-0613;363;98;265;200;2023-11-02T04:39:14.000Z;2023-11-02T04:39:20.000Z Anthropic;claude-2.0;2000;2000;0;500;2023-11-02T04:51:15.000Z;2023-11-02T04:51:15.058Z OpenAI;babbage-002;197;197;0;501;2023-11-02T04:55:17.000Z;2023-11-02T04:55:17.980Z OpenAI;babbage-002;621;87;534;200;2023-11-02T08:10:19.000Z;2023-11-02T08:10:25.600Z OpenAI;babbage-002;339;39;300;200;2023-11-02T08:11:25.000Z;2023-11-02T08:11:29.999Z Anthropic;claude-2.0;90;67;23;200;2023-11-02T08:51:27.000Z;2023-11-02T08:51:27.730Z OpenAI;babbage-002;682;672;10;200;2023-11-02T08:57:29.000Z;2023-11-02T08:57:35.000Z Anthropic;claude-2.0;382;37;345;200;2023-11-02T10:17:19.000Z;2023-11-02T10:17:25.002Z OpenAI;babbage-002;908;876;32;200;2023-11-02T11:59:29.000Z;2023-11-02T11:59:30.000Z Anthropic;claude-2.0;251;29;222;200;2023-11-02T12:00:00.000Z;2023-11-02T12:00:02.999Z Anthropic;claude-2.0;301;25;276;200;2023-11-02T13:05:21.000Z;2023-11-02T13:05:29.000Z Anthropic;claude-2.0;97;74;23;200;2023-11-06T12:07:21.000Z;2023-11-06T12:07:21.980Z Anthropic;claude-2.0;99;99;0;301;2023-11-06T15:09:22.000Z;2023-11-06T15:09:24.000Z OpenAI;gpt-4-0613;11;11;0;404;2023-11-06T18:07:21.000Z;2023-11-06T18:07:21.030Z OpenAI;gpt-4-0613;164;89;75;200;2023-11-12T23:10:23.000Z;2023-11-12T23:10:23.820Z OpenAI;gpt-4-0613;285;63;222;200;2023-11-12T23:56:25.000Z;2023-11-12T23:56:29.000Z OpenAI;gpt-4-0613;700;29;671;200;2023-11-12T23:59:59.000Z;2023-11-13T00:01:59.980Z OpenAI;babbage-002;1011;666;345;200;2023-11-13T00:00:00.000Z;2023-11-13T00:00:03.980Z OpenAI;babbage-002;434;434;0;404;2023-11-14T12:00:01.000Z;2023-11-14T12:00:01.980Z OpenAI;babbage-002;48;48;0;403;2023-11-14T13:05:24.000Z;2023-11-14T13:05:26.000Z OpenAI;babbage-002;283;78;205;200;2023-11-17T12:09:33.000Z;2023-11-17T12:09:39.800Z Azure OpenAI;gpt-35-turbo-0613;419;22;397;200;2023-11-19T17:11:11.000Z;2023-11-19T17:11:23.900Z Azure OpenAI;gpt-35-turbo-0613;145;66;79;200;2023-11-20T20:17:59.000Z;2023-11-20T20:17:59.990Z OpenAI;gpt-4-0613;62;33;29;200;2023-11-25T23:10:23.000Z;2023-11-25T23:10:25.000Z OpenAI;gpt-4-0613;565;565;0;501;2023-11-25T23:11:23.000Z;2023-11-25T23:11:23.009Z OpenAI;gpt-4-0613;30205;200;30005;200;2023-11-28T13:59:59.000Z;2023-11-28T14:05:59.997Z OpenAI;babbage-002;6985;201;6784;200;2023-11-30T00:01:01.000Z;2023-11-30T00:02:33.000Z OpenAI;gpt-4-0613;47;21;26;200;2023-12-01T00:01:02.000Z;2023-12-01T00:01:02.654Z OpenAI;babbage-002;114;36;78;200;2023-12-01T08:11:02.000Z;2023-12-01T08:11:05.000Z OpenAI;babbage-002;28;28;0;301;2023-12-01T08:12:07.000Z;2023-12-01T08:12:08.000Z OpenAI;gpt-4-0613;875;89;786;200;2023-12-04T18:17:07.000Z;2023-12-04T18:17:22.940Z Anthropic;claude-2.0;975;65;910;200;2023-12-05T19:19:09.000Z;2023-12-05T19:19:25.740Z Azure OpenAI;gpt-35-turbo-0613;61;37;24;200;2023-12-09T23:35:08.000Z;2023-12-09T23:35:08.967Z Azure OpenAI;gpt-35-turbo-0613;155;88;67;200;2023-12-09T23:45:09.000Z;2023-12-09T23:45:10.000Z Azure OpenAI;gpt-35-turbo-0613;300;91;209;200;2023-12-13T21:32:54.000Z;2023-12-13T21:32:56.900Z Anthropic;claude-2.0;805;37;768;200;2023-12-16T20:39:53.000Z;2023-12-16T20:40:59.999Z OpenAI;babbage-002;81;81;0;301;2023-12-21T08:30:53.000Z;2023-12-21T08:30:53.930Z Azure OpenAI;gpt-35-turbo-0613;72;72;0;308;2023-12-21T08:33:55.000Z;2023-12-21T08:33:55.480Z Azure OpenAI;gpt-35-turbo-0613;63;63;0;301;2023-12-21T09:37:50.000Z;2023-12-21T09:37:51.000Z Azure OpenAI;gpt-35-turbo-0613;86;53;33;200;2023-12-31T00:31:51.000Z;2023-12-31T00:31:53.000Z Azure OpenAI;gpt-35-turbo-0613;139;41;98;200;2023-12-31T23:59:59.000Z;2023-12-31T23:59:59.993Z Anthropic;claude-2.0;71;12;59;200;2024-01-03T23:59:59.000Z;2024-01-04T00:00:00.000Z Anthropic;claude-2.0;163;7;156;200;2024-01-04T01:50:59.000Z;2024-01-04T01:51:59.991Z Anthropic;claude-2.0;59;4;55;200;2024-01-05T02:34:29.000Z;2024-01-05T02:34:30.000Z OpenAI;gpt-4-0613;22;22;0;404;2024-01-06T03:30:21.000Z;2024-01-06T03:30:21.005Z OpenAI;gpt-4-0613;59;59;0;502;2024-01-07T04:20:22.000Z;2024-01-07T04:20:22.009Z OpenAI;gpt-4-0613;101;101;0;301;2024-01-08T06:23:25.000Z;2024-01-08T06:23:25.011Z OpenAI;babbage-002;548;151;397;200;2024-01-16T09:26:27.000Z;2024-01-16T09:26:50.000Z Azure OpenAI;gpt-35-turbo-0613;420;44;376;200;2024-01-19T10:27:01.000Z;2024-01-19T10:27:33.000Z OpenAI;gpt-4-0613;1065;78;987;200;2024-01-25T12:29:01.000Z;2024-01-25T12:29:15.111Z OpenAI;gpt-4-0613;65;65;0;301;2024-03-01T00:00:00.000Z;2024-03-01T00:00:01.000Z OpenAI;gpt-4-0613;44;44;0;301;2024-03-01T00:01:00.000Z;2024-03-01T00:01:02.000Z Anthropic;claude-2.0;108;20;88;200;2024-03-06T00:56:09.000Z;2024-03-06T00:56:10.297Z OpenAI;gpt-4-0613;200;30;170;200;2024-03-09T09:57:07.000Z;2024-03-09T09:57:09.800Z OpenAI;gpt-4-0613;57;57;0;404;2024-03-15T06:50:03.000Z;2024-03-15T06:50:03.540Z OpenAI;gpt-4-0613;92;92;0;404;2024-03-15T07:50:03.000Z;2024-03-15T07:50:03.980Z Azure OpenAI;gpt-35-turbo-0613;11;11;0;404;2024-03-15T09:58:05.000Z;2024-03-15T09:58:06.000Z Azure OpenAI;gpt-35-turbo-0613;99;99;0;404;2024-03-15T10:59:55.000Z;2024-03-15T10:59:55.780Z OpenAI;gpt-4-0613;26002;2000;24002;200;2024-03-15T14:19:20.000Z;2024-03-15T14:23:26.000Z OpenAI;gpt-4-0613;878;366;512;200;2024-03-21T14:19:25.000Z;2024-03-21T14:19:46.000Z Anthropic;claude-2.0;283;66;217;200;2024-03-28T19:19:25.000Z;2024-03-28T19:19:29.000Z Anthropic;claude-2.0;78;78;0;308;2024-03-28T20:14:21.000Z;2024-03-28T20:14:21.590Z Anthropic;claude-2.0;20;20;0;308;2024-03-28T20:15:20.000Z;2024-03-28T20:15:20.729Z Anthropic;claude-2.0;55;55;0;500;2024-03-28T21:15:23.000Z;2024-03-28T21:15:23.198Z Anthropic;claude-2.0;45;20;25;200;2024-03-28T22:11:23.000Z;2024-03-28T22:11:23.960Z Anthropic;claude-2.0;175;21;154;200;2024-03-28T23:19:21.000Z;2024-03-28T23:19:23.053Z Anthropic;claude-2.0;483;225;258;200;2024-04-13T10:03:21.565Z;2024-04-13T10:03:30.370Z diff --git a/backend/tests/test_speed_statistics.py b/backend/tests/test_speed_statistics.py new file mode 100644 index 00000000..8c3276ca --- /dev/null +++ b/backend/tests/test_speed_statistics.py @@ -0,0 +1,972 @@ +from utils import read_transactions_from_csv, truncate_float + +header = { + "Authorization": "Bearer eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyMTAyODc3OTUzNDg0MzUyNDI3IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiYm54OW9WT1o4U3FJOTcyczBHYjd4dyIsIm5hbWUiOiJUZXN0IFVzZXIiLCJwaWN0dXJlIjoiIiwiZ2l2ZW5fbmFtZSI6IlRlc3QiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJpYXQiOjE3MTM3MzQ0NjEsImV4cCI6OTk5OTk5OTk5OX0.eZYMQzcSRzkAq4Me8C6SNU3wduS7EIu_o5XGAbsDmU05GtyipQEb5iNJ1QiLg-11RbZFL3dvi8xKd3mpuw8b-5l6u8hwSpZg6wNPLY0zPX-EOwxeHLtev_2X5pUf1_IWAnso9K_knsK8CcmJoVsCyNNjlw3hrkChacJHGNzg0TTT1rh3oe6KCpbLvYlV6tUPfm5k3AMFZIT7Jntr38CZvs6gac6L_DhItJc3TNNUUHie2zgA29_r9YFlaEr_nGoSmBhIi-i0i0h34TL4JAb4qJkVM2YI2eTTv2HjEGtkx4mE5JvNQ0VxzHSJcCNOHh1gCiFD5c6rhvvxVeEqMkGGbCZKHX_vCgnIp0iE_OWyICjVTFPitQJ00fXLhyHyPb7q5J605tuK2iTHp2NCRJEXIAl9e0F_qASBBAfyL0C4FCBtvbnEMwtpoV1VWinkKgkI7JVH0AsyTugjXyAjxxsJxBTJT9qwZLxVBoaxgqNTOFfxvwstyq1VfCl3iBbpt71D" +} + + +def test_transaction_speed_min_5min(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=5minutes&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:04:59", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([31.77272037])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_min_30min(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=5minutes&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:29:59", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([0, 31.77272037, 0, 0]), + tuple([0, 2.94217308, 7.700000136, 16.00000129]), + tuple([0, 0, 0, 0]), + tuple([65.13026753, 0, 0, 0]), + tuple([0, 0, 82.41802468, 0]), + tuple([92.09210559, 89.41178679, 0, 20.00000035]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 6 + assert temp == validation + + +def test_transaction_speed_min_1h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=5minutes&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:59:59", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([0, 31.77272037, 0, 0]), + tuple([0, 2.94217308, 7.700000136, 16.00000129]), + tuple([0, 0, 0, 0]), + tuple([65.13026753, 0, 0, 0]), + tuple([0, 0, 82.41802468, 0]), + tuple([92.09210559, 89.41178679, 0, 20.00000035]), + tuple([0, 0, 0, 0]), + tuple([0, 98.46880746, 0, 0]), + tuple([0, 67.43749926, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 38.45918092, 0, 0]), + tuple([0, 0, 80.65413879, 55.3845944]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 12 + assert temp == validation + + +def test_transaction_speed_min_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=5minutes&date_from=2023-10-01T12:00:00&date_to=2023-10-31T12:30:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_speed_hour_30min(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=hour&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:30:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([78.61118656, 38.97485015, 57.5120165, 18.00000082])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_hour_1h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=hour&date_from=2023-11-01T12:00:00&date_to=2023-11-01T13:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([78.61118656, 48.46159236, 69.08307764, 30.46153202]), + tuple([0, 0, 0, 0]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert temp == validation + + +def test_transaction_speed_hour_24h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=hour&date_from=2023-11-01T12:00:00&date_to=2023-11-02T11:59:59", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([78.61118656, 48.46159236, 69.08307764, 30.46153202]), + tuple([0, 4.124278, 0, 0]), + tuple([0, 41.66667439, 0, 0]), + tuple([97.51860847, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([52.25000421, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([53.2270015, 23.00000908, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 5.988021267, 44.1666656]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([31.50684656, 0, 47.52925421, 0]), + tuple([0, 0, 0, 0]), + tuple([57.48084108, 0, 0, 0]), + tuple([0, 0, 32.00001264, 0]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 24 + assert temp == validation + + +def test_transaction_speed_hour_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=hour&date_from=2023-10-01T12:00:00&date_to=2023-10-31T18:00:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_speed_day_6h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=day&date_from=2023-11-01T12:00:00&date_to=2023-11-01T18:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([76.74774645, 44.80929007, 69.08307764, 30.46153202])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_day_24h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=day&date_from=2023-11-01&date_to=2023-11-01", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([72.04359746, 43.355338, 69.08307764, 30.46153202])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_day_1d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=day&date_from=2023-11-01T13:00:00&date_to=2023-11-02T13:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([67.66520472, 22.93032058, 0, 0]), + tuple([54.33745498, 0, 36.11515931, 44.1666656]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert temp == validation + + +def test_transaction_speed_day_7d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=day&date_from=2023-11-01T12:00:00&date_to=2023-11-07T11:59:59", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([72.04359746, 43.355338, 69.08307764, 30.46153202]), + tuple([49.37809126, 0, 36.11515931, 44.1666656]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([23.46938859, 0, 0, 0]), + tuple([0, 0, 0, 0]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 7 + assert temp == validation + + +def test_transaction_speed_day_1mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=day&date_from=2023-11-01T12:00:00&date_to=2023-11-30T12:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([72.04359746, 43.355338, 69.08307764, 30.46153202]), + tuple([49.37809126, 0, 36.11515931, 44.1666656]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([23.46938859, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 73.48172621]), + tuple([0, 0, 86.68341626, 5.546371285]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 30.14705791, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 30.77519425, 0, 0]), + tuple([0, 79.79794645, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 14.50000117]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 83.11703415]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 73.73913033, 0]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 30 + assert temp == validation + + +def test_transaction_speed_day_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=day&date_from=2023-11-03T12:00:00&date_to=2023-11-04T12:00:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_speed_week_3d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=week&date_from=2023-11-01T12:00:00&date_to=2023-11-03T12:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([61.97003915, 43.355338, 54.09766022, 33.88781541])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_week_7d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=week&date_from=2023-11-01T12:00:00&date_to=2023-11-07T12:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([58.11997409, 43.355338, 54.09766022, 33.88781541]), + tuple([0, 0, 0, 0]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert temp == validation + + +def test_transaction_speed_week_2mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=week&date_from=2023-11-01T12:00:00&date_to=2023-12-31T00:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([58.11997409, 43.355338, 54.09766022, 33.88781541]), + tuple([0, 0, 86.68341626, 50.8366079]), + tuple([0, 55.28657035, 30.14705791, 0]), + tuple([0, 0, 0, 14.50000117]), + tuple([0, 0, 49.86956757, 57.39410621]), + tuple([54.36081291, 45.90951084, 0, 0]), + tuple([11.46285769, 72.06896586, 0, 0]), + tuple([0, 0, 0, 0]), + tuple([0, 0, 0, 0]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 9 + assert temp == validation + + +def test_transaction_speed_week_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=week&date_from=2024-03-29T00:00:00&date_to=2024-04-12T00:00:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_speed_month_7d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=month&date_from=2023-11-01T12:00:00&date_to=2023-11-07T12:00:00", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([58.119971, 43.355338, 54.09766022, 33.88781541])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_month_1mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=month&date_from=2023-11-01&date_to=2023-11-30", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([58.11997409, 44.75901239, 56.11741906, 42.85312452])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_month_6mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=month&date_from=2023-10-01&date_to=2024-03-31", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([0, 0, 0, 0]), + tuple([58.11997409, 44.75901239, 56.11741906, 42.85312452]), + tuple([32.9118353, 55.815765, 26.00000482, 44.53264224]), + tuple([38.85257592, 11.75, 17.26086978, 69.94543473]), + tuple([0, 0, 0, 0]), + tuple([55.78817778, 0, 0, 60.88811508]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 6 + assert temp == validation + + +def test_transaction_speed_month_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=month&date_from=2024-05-01&date_to=2024-06-01", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_speed_year_2mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=year&date_from=2023-11-01&date_to=2023-12-31", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [tuple([53.91861763, 44.82339021, 54.10959145, 43.15849138])] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert temp == validation + + +def test_transaction_speed_year_2y(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=year&date_from=2023-11-01&date_to=2024-03-31", + headers=header, + ) + temp = list( + [ + tuple( + [ + truncate_float(record["tokens_per_second"], 3) + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + validation = [ + tuple([53.91861763, 47.27191158, 54.10959145, 43.15849138]), + tuple([48.5300627, 11.75, 17.26086978, 63.15244499]), + ] + validation = list( + [tuple(map(lambda x: truncate_float(x, 3), list(val))) for val in validation] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert temp == validation + + +def test_transaction_speed_year_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_speed?project_id=project-test&period=year&date_from=2024-05-31&date_to=2024-12-31", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 diff --git a/backend/tests/test_tokens_and_cost_statistics.py b/backend/tests/test_tokens_and_cost_statistics.py new file mode 100644 index 00000000..598259e1 --- /dev/null +++ b/backend/tests/test_tokens_and_cost_statistics.py @@ -0,0 +1,1333 @@ +from utils import read_transactions_from_csv + +header = { + "Authorization": "Bearer eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyMTAyODc3OTUzNDg0MzUyNDI3IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiYm54OW9WT1o4U3FJOTcyczBHYjd4dyIsIm5hbWUiOiJUZXN0IFVzZXIiLCJwaWN0dXJlIjoiIiwiZ2l2ZW5fbmFtZSI6IlRlc3QiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJpYXQiOjE3MTM3MzQ0NjEsImV4cCI6OTk5OTk5OTk5OX0.eZYMQzcSRzkAq4Me8C6SNU3wduS7EIu_o5XGAbsDmU05GtyipQEb5iNJ1QiLg-11RbZFL3dvi8xKd3mpuw8b-5l6u8hwSpZg6wNPLY0zPX-EOwxeHLtev_2X5pUf1_IWAnso9K_knsK8CcmJoVsCyNNjlw3hrkChacJHGNzg0TTT1rh3oe6KCpbLvYlV6tUPfm5k3AMFZIT7Jntr38CZvs6gac6L_DhItJc3TNNUUHie2zgA29_r9YFlaEr_nGoSmBhIi-i0i0h34TL4JAb4qJkVM2YI2eTTv2HjEGtkx4mE5JvNQ0VxzHSJcCNOHh1gCiFD5c6rhvvxVeEqMkGGbCZKHX_vCgnIp0iE_OWyICjVTFPitQJ00fXLhyHyPb7q5J605tuK2iTHp2NCRJEXIAl9e0F_qASBBAfyL0C4FCBtvbnEMwtpoV1VWinkKgkI7JVH0AsyTugjXyAjxxsJxBTJT9qwZLxVBoaxgqNTOFfxvwstyq1VfCl3iBbpt71D" +} + + +def test_transaction_tokens_and_cost_min_5min(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=5minutes&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:04:59", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + costs = list( + [ + tuple([record["total_cost"] for record in date["records"]]) + for date in response.json() + ] + ) + tokens_validation = [tuple([174])] + costs_validation = [tuple([0.0003155])] + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_min_30min(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=5minutes&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:29:59", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([0, 174, 0, 0]), + tuple([0, 730, 142, 332]), + tuple([0, 730, 142, 332]), + tuple([107, 730, 142, 332]), + tuple([107, 730, 1816, 332]), + tuple([419, 835, 1816, 497]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0, 0.0003155, 0, 0]), + tuple([0, 0.0013275, 0.0000568, 0.01092]), + tuple([0, 0.0013275, 0.0000568, 0.01092]), + tuple([0.001896, 0.0013275, 0.0000568, 0.01092]), + tuple([0.001896, 0.0013275, 0.0007264, 0.01092]), + tuple([0.008808, 0.001523, 0.0007264, 0.01887]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 6 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_min_1h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=5minutes&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:59:59", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([0, 174, 0, 0]), + tuple([0, 730, 142, 332]), + tuple([0, 730, 142, 332]), + tuple([107, 730, 142, 332]), + tuple([107, 730, 1816, 332]), + tuple([419, 835, 1816, 497]), + tuple([419, 835, 1816, 497]), + tuple([419, 2847, 1816, 497]), + tuple([419, 3713, 1816, 497]), + tuple([419, 3713, 1816, 497]), + tuple([419, 7904, 1816, 497]), + tuple([419, 7904, 2778, 578]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0, 0.0003155, 0, 0]), + tuple([0, 0.0013275, 0.0000568, 0.01092]), + tuple([0, 0.0013275, 0.0000568, 0.01092]), + tuple([0.001896, 0.0013275, 0.0000568, 0.01092]), + tuple([0.001896, 0.0013275, 0.0007264, 0.01092]), + tuple([0.008808, 0.001523, 0.0007264, 0.01887]), + tuple([0.008808, 0.001523, 0.0007264, 0.01887]), + tuple([0.008808, 0.005541, 0.0007264, 0.01887]), + tuple([0.008808, 0.007212, 0.0007264, 0.01887]), + tuple([0.008808, 0.007212, 0.0007264, 0.01887]), + tuple([0.008808, 0.0151335, 0.0007264, 0.01887]), + tuple([0.008808, 0.0151335, 0.0011112, 0.02292]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 12 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_min_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=5minutes&date_from=2023-10-01T12:00:00&date_to=2023-10-31T12:30:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_tokens_and_cost_hour_30min(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=hour&date_from=2023-11-01T12:00:00&date_to=2023-11-01T12:30:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([419, 835, 1816, 497])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [tuple([0.008808, 0.001523, 0.0007264, 0.01887])] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_hour_1h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=hour&date_from=2023-11-01T12:00:00&date_to=2023-11-01T13:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([419, 7904, 2778, 578]), tuple([419, 7904, 2778, 578])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.008808, 0.0151335, 0.0011112, 0.02292]), + tuple([0.008808, 0.0151335, 0.0011112, 0.02292]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_hour_24h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=hour&date_from=2023-11-01T12:00:00&date_to=2023-11-02T11:59:59", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([419, 7904, 2778, 578]), + tuple([419, 7953, 2778, 578]), + tuple([419, 8045, 2778, 578]), + tuple([1231, 8045, 2778, 578]), + tuple([1231, 8045, 2778, 578]), + tuple([1468, 8045, 2778, 578]), + tuple([1468, 8045, 2778, 578]), + tuple([1468, 8045, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2778, 578]), + tuple([1781, 8079, 2787, 941]), + tuple([1781, 8079, 2787, 941]), + tuple([1781, 8079, 2787, 941]), + tuple([1781, 8079, 2787, 941]), + tuple([1871, 8079, 4429, 941]), + tuple([1871, 8079, 4429, 941]), + tuple([2253, 8079, 4429, 941]), + tuple([2253, 8079, 5337, 941]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.008808, 0.0151335, 0.0011112, 0.02292]), + tuple([0.008808, 0.0152295, 0.0011112, 0.02292]), + tuple([0.008808, 0.01538, 0.0011112, 0.02292]), + tuple([0.02788, 0.01538, 0.0011112, 0.02292]), + tuple([0.02788, 0.01538, 0.0011112, 0.02292]), + tuple([0.03312, 0.01538, 0.0011112, 0.02292]), + tuple([0.03312, 0.01538, 0.0011112, 0.02292]), + tuple([0.03312, 0.01538, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.040216, 0.0154425, 0.0011148, 0.04176]), + tuple([0.040216, 0.0154425, 0.0011148, 0.04176]), + tuple([0.040216, 0.0154425, 0.0011148, 0.04176]), + tuple([0.040216, 0.0154425, 0.0011148, 0.04176]), + tuple([0.041304, 0.0154425, 0.0017716, 0.04176]), + tuple([0.041304, 0.0154425, 0.0017716, 0.04176]), + tuple([0.04988, 0.0154425, 0.0017716, 0.04176]), + tuple([0.04988, 0.0154425, 0.0021348, 0.04176]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 24 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_hour_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=hour&date_from=2023-10-01T12:00:00&date_to=2023-10-31T18:00:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_tokens_and_cost_day_6h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=day&date_from=2023-11-01T12:00:00&date_to=2023-11-01T18:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([1468, 8045, 2778, 578])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [tuple([0.03312, 0.01538, 0.0011112, 0.02292])] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_day_24h(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=day&date_from=2023-11-01&date_to=2023-11-01", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([1781, 8079, 2778, 578])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [tuple([0.040216, 0.0154425, 0.0011112, 0.02292])] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_day_1d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=day&date_from=2023-11-01T13:00:00&date_to=2023-11-02T13:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([1362, 175, 0, 0]), tuple([2085, 175, 2559, 363])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.031408, 0.000309, 0.0, 0.0]), + tuple([0.046632, 0.000309, 0.0010236, 0.01884]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_day_7d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=day&date_from=2023-11-01T12:00:00&date_to=2023-11-07T11:59:59", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([1781, 8079, 2778, 578]), + tuple([2805, 8079, 5337, 941]), + tuple([2805, 8079, 5337, 941]), + tuple([2805, 8079, 5337, 941]), + tuple([2805, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 7 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_day_1mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=day&date_from=2023-11-01T12:00:00&date_to=2023-11-30T12:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([1781, 8079, 2778, 578]), + tuple([2805, 8079, 5337, 941]), + tuple([2805, 8079, 5337, 941]), + tuple([2805, 8079, 5337, 941]), + tuple([2805, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 5337, 1390]), + tuple([2902, 8079, 6348, 2090]), + tuple([2902, 8079, 6348, 2090]), + tuple([2902, 8079, 6348, 2090]), + tuple([2902, 8079, 6348, 2090]), + tuple([2902, 8079, 6631, 2090]), + tuple([2902, 8079, 6631, 2090]), + tuple([2902, 8498, 6631, 2090]), + tuple([2902, 8643, 6631, 2090]), + tuple([2902, 8643, 6631, 2090]), + tuple([2902, 8643, 6631, 2090]), + tuple([2902, 8643, 6631, 2090]), + tuple([2902, 8643, 6631, 2090]), + tuple([2902, 8643, 6631, 2152]), + tuple([2902, 8643, 6631, 2152]), + tuple([2902, 8643, 6631, 2152]), + tuple([2902, 8643, 6631, 32357]), + tuple([2902, 8643, 6631, 32357]), + tuple([2902, 8643, 13616, 32357]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.040216, 0.0154425, 0.0011112, 0.02292]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.062264, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.06414]), + tuple([0.063408, 0.0154425, 0.0025392, 0.10527]), + tuple([0.063408, 0.0154425, 0.0025392, 0.10527]), + tuple([0.063408, 0.0154425, 0.0025392, 0.10527]), + tuple([0.063408, 0.0154425, 0.0025392, 0.10527]), + tuple([0.063408, 0.0154425, 0.0026524, 0.10527]), + tuple([0.063408, 0.0154425, 0.0026524, 0.10527]), + tuple([0.063408, 0.0162695, 0.0026524, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.108]), + tuple([0.063408, 0.0165265, 0.0026524, 0.108]), + tuple([0.063408, 0.0165265, 0.0026524, 0.108]), + tuple([0.063408, 0.0165265, 0.0026524, 1.9143]), + tuple([0.063408, 0.0165265, 0.0026524, 1.9143]), + tuple([0.063408, 0.0165265, 0.0054464, 1.9143]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 30 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_day_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=day&date_from=2023-11-03T12:00:00&date_to=2023-11-04T12:00:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_tokens_and_cost_week_3d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=week&date_from=2023-11-01T12:00:00&date_to=2023-11-03T12:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([2805, 8079, 5337, 941])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [tuple([0.062264, 0.0154425, 0.0021348, 0.04176])] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_week_7d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=week&date_from=2023-11-01T12:00:00&date_to=2023-11-07T12:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([2902, 8079, 5337, 941]), tuple([2902, 8079, 5337, 941])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_week_2mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=week&date_from=2023-11-01T12:00:00&date_to=2023-12-31T00:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([2902, 8079, 5337, 941]), + tuple([2902, 8079, 6348, 2090]), + tuple([2902, 8643, 6631, 2090]), + tuple([2902, 8643, 6631, 2152]), + tuple([2902, 8643, 13730, 33279]), + tuple([3877, 8859, 13730, 33279]), + tuple([4682, 9159, 13730, 33279]), + tuple([4682, 9159, 13730, 33279]), + tuple([4682, 9159, 13730, 33279]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.063408, 0.0154425, 0.0021348, 0.04176]), + tuple([0.063408, 0.0154425, 0.0025392, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.10527]), + tuple([0.063408, 0.0165265, 0.0026524, 0.108]), + tuple([0.063408, 0.0165265, 0.005492, 1.96632]), + tuple([0.085768, 0.016896, 0.005492, 1.96632]), + tuple([0.104496, 0.0174505, 0.005492, 1.96632]), + tuple([0.104496, 0.0174505, 0.005492, 1.96632]), + tuple([0.104496, 0.0174505, 0.005492, 1.96632]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 9 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_week_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=week&date_from=2024-03-29T00:00:00&date_to=2024-04-12T00:00:00", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_tokens_and_cost_month_7d(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=month&date_from=2023-11-01T12:00:00&date_to=2023-11-07T12:00:00", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([2902, 8079, 5337, 941])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [tuple([0.063408, 0.0154425, 0.0021348, 0.04176])] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_month_1mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=month&date_from=2023-11-01&date_to=2023-11-30", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([2902, 8643, 13616, 32357])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [tuple([0.063408, 0.0165265, 0.0054464, 1.9143])] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_month_6mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=month&date_from=2023-10-01&date_to=2024-03-31", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([0, 0, 0, 0]), + tuple([2902, 8643, 13616, 32357]), + tuple([4682, 9384, 13730, 33279]), + tuple([4975, 9804, 14278, 34344]), + tuple([4975, 9804, 14278, 34344]), + tuple([5586, 9804, 14278, 61424]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.0, 0.0, 0.0, 0.0]), + tuple([0.063408, 0.0165265, 0.0054464, 1.9143]), + tuple([0.104496, 0.0178535, 0.005492, 1.96632]), + tuple([0.11116, 0.0186715, 0.0057112, 2.02788]), + tuple([0.11116, 0.0186715, 0.0057112, 2.02788]), + tuple([0.123792, 0.0186715, 0.0057112, 3.5808]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 6 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_month_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=month&date_from=2024-05-01&date_to=2024-06-01", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 + + +def test_transaction_tokens_and_cost_year_2mo(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=year&date_from=2023-11-01&date_to=2023-12-31", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [tuple([4682, 9245, 13730, 33279])] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [tuple([0.104496, 0.017596, 0.005492, 1.96632])] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 1 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_year_2y(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=year&date_from=2023-11-01&date_to=2024-03-31", + headers=header, + ) + tokens = list( + [ + tuple( + [ + record["input_cumulative_total"] + record["output_cumulative_total"] + for record in date["records"] + ] + ) + for date in response.json() + ] + ) + tokens_validation = [ + tuple([4682, 9384, 13730, 33279]), + tuple([5586, 9804, 14278, 61424]), + ] + + costs = list( + [ + tuple( + [round(record["total_cost"] * 1000000, 1) for record in date["records"]] + ) + for date in response.json() + ] + ) + costs_validation = [ + tuple([0.104496, 0.0178535, 0.005492, 1.96632]), + tuple([0.123792, 0.0186715, 0.0057112, 3.5808]), + ] + costs_validation = list( + [ + tuple(map(lambda x: round(x * 1000000, 1), list(val))) + for val in costs_validation + ] + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 2 + assert tokens == tokens_validation + assert costs == costs_validation + + +def test_transaction_tokens_and_cost_year_empty(client, application): + # arrange + with application.transaction_context() as ctx: + repo = ctx["transaction_repository"] + repo.delete_cascade(project_id="project-test") + transactions = read_transactions_from_csv( + "../test_transactions_tokens_cost_speed.csv" + ) + for transaction in transactions: + repo.add(transaction) + + # act + response = client.get( + "/api/statistics/transactions_cost?project_id=project-test&period=year&date_from=2024-05-31&date_to=2024-12-31", + headers=header, + ) + + # assert + assert response.status_code == 200 + assert len(response.json()) == 0 diff --git a/backend/tests/test_pricing.py b/backend/tests/test_transactions_pricing.py similarity index 100% rename from backend/tests/test_pricing.py rename to backend/tests/test_transactions_pricing.py index 8682f7be..e7c78a07 100644 --- a/backend/tests/test_pricing.py +++ b/backend/tests/test_transactions_pricing.py @@ -1,10 +1,10 @@ from utils import read_transactions_from_csv - header = { "Authorization": "Bearer eyJhbGciOiJSUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyMTAyODc3OTUzNDg0MzUyNDI3IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiYm54OW9WT1o4U3FJOTcyczBHYjd4dyIsIm5hbWUiOiJUZXN0IFVzZXIiLCJwaWN0dXJlIjoiIiwiZ2l2ZW5fbmFtZSI6IlRlc3QiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJpYXQiOjE3MTM3MzQ0NjEsImV4cCI6OTk5OTk5OTk5OX0.eZYMQzcSRzkAq4Me8C6SNU3wduS7EIu_o5XGAbsDmU05GtyipQEb5iNJ1QiLg-11RbZFL3dvi8xKd3mpuw8b-5l6u8hwSpZg6wNPLY0zPX-EOwxeHLtev_2X5pUf1_IWAnso9K_knsK8CcmJoVsCyNNjlw3hrkChacJHGNzg0TTT1rh3oe6KCpbLvYlV6tUPfm5k3AMFZIT7Jntr38CZvs6gac6L_DhItJc3TNNUUHie2zgA29_r9YFlaEr_nGoSmBhIi-i0i0h34TL4JAb4qJkVM2YI2eTTv2HjEGtkx4mE5JvNQ0VxzHSJcCNOHh1gCiFD5c6rhvvxVeEqMkGGbCZKHX_vCgnIp0iE_OWyICjVTFPitQJ00fXLhyHyPb7q5J605tuK2iTHp2NCRJEXIAl9e0F_qASBBAfyL0C4FCBtvbnEMwtpoV1VWinkKgkI7JVH0AsyTugjXyAjxxsJxBTJT9qwZLxVBoaxgqNTOFfxvwstyq1VfCl3iBbpt71D" } + def test_openai_costs(client, application): # arrange with application.transaction_context() as ctx: diff --git a/backend/tests/test_transactions.py b/backend/tests/test_transactions_statuses.py similarity index 100% rename from backend/tests/test_transactions.py rename to backend/tests/test_transactions_statuses.py diff --git a/docker-compose-build.yml b/docker-compose-build.yml index e89784f1..fd28aa90 100644 --- a/docker-compose-build.yml +++ b/docker-compose-build.yml @@ -41,7 +41,7 @@ services: BASE_URL: "http://localhost:8000" STATIC_DIRECTORY: "/static" MONGO_URL: "mongodb://root:password@mongodb:27017" - ORGANIZATION_NAME: "Default" + ORGANIZATION_NAME: "PromptSail" ADMIN_PASSWORD: "password" SSO_AUTH: "False" depends_on: diff --git a/docker-compose-try-promptsail.yml b/docker-compose-try-promptsail.yml new file mode 100644 index 00000000..bf5efd6c --- /dev/null +++ b/docker-compose-try-promptsail.yml @@ -0,0 +1,47 @@ +version: "3.8" +services: + mongodb: + image: mongo:latest + ports: + - "27017:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: password + healthcheck: + test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + interval: 10s + timeout: 10s + retries: 5 + start_period: 40s + promptsail-backend: + image: ghcr.io/promptsail/promptsail-backend:dev-release + container_name: promptsail-backend + ports: + - "8000:8000" + environment: + BASE_URL: "http://localhost:8000" + MONGO_URL: "mongodb://root:password@mongodb:27017" + ORGANIZATION_NAME: "PromptSail" + ADMIN_PASSWORD: "password" + SSO_AUTH: "False" + depends_on: + mongodb: + condition: service_healthy + promptsail-ui: + image: ghcr.io/promptsail/promptsail-ui:dev-release + container_name: promptsail-ui + ports: + - "80:80" + environment: + PORT: 80 + PROMPT_SAIL_ENV_PLACEHOLDER_BACKEND_URL: 'http://promptsail-backend:8000' + PROMPT_SAIL_ENV_PLACEHOLDER_PROXY_URL_HOST: 'https://try-promptsail.azurewebsites.net/api' + depends_on: + mongodb: + condition: service_started + promptsail-backend: + condition: service_started +networks: + internal-network: + internal: true + external-network: diff --git a/docker-compose.yml b/docker-compose.yml index 302f4945..8f89853f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,7 @@ services: BASE_URL: "http://localhost:8000" STATIC_DIRECTORY: "/static" MONGO_URL: "mongodb://root:password@mongodb:27017" - ORGANIZATION_NAME: "Default" + ORGANIZATION_NAME: "PromptSail" ADMIN_PASSWORD: "password" SSO_AUTH: "False" depends_on: diff --git a/docs/_docs/50-deploy-cookbook-introduction.md b/docs/_docs/50-deploy-cookbook-introduction.md index d6b0dc78..4cfdda18 100644 --- a/docs/_docs/50-deploy-cookbook-introduction.md +++ b/docs/_docs/50-deploy-cookbook-introduction.md @@ -11,22 +11,25 @@ toc_sticky: true ## Deployment Cookbook +Promt Sail cloud deployment documentation, how to properly set the `environment variables`, configure SSO and container-based deployment +### SSO Configuration -Is a self-hosted application that captures and logs all interactions with LLM APIs such as OpenAI, Anthropic, Google Gemini and others. It is a proxy between your framework of choice (LangChain, OpenAI etc) and LLM provider API. +[How to configure SSO authorisation in your organisation](/docs/sso-configuration/), currently we support: +* Microsoft Azure +* Google -For **developers**, it offers a way to analyze and optimize API prompts. +### Environment variables -For **Project managers** can gain insights into project and experiment costs. +[List of Prompt Sail deployment env's](/docs/env-variables) -For **Business owners** can ensure compliance with regulations and maintain governance over prompts and responses. -### Deployment Options +### Cloud Deployment recipes There are several ways to deploy Prompt Sail: -* [Local deployment](/docs/deploy-promptsail-local/ -* [Azure deployment](/docs/deploy-promptsail-azure) (help needed #good-first-issue) +* [Local deployment](/docs/deploy-promptsail-local/) +* [Azure deployment](/docs/deploy-promptsail-azure) * [AWS deployment](/docs/deploy-promptsail-aws) (help needed #good-first-issue) * [GCP deployment](/docs/deploy-promptsail-gcp) (help needed #good-first-issue) diff --git a/examples/.env.template b/examples/.env.template index 83cfac22..c179766f 100644 --- a/examples/.env.template +++ b/examples/.env.template @@ -5,4 +5,5 @@ AZURE_OPENAI_ENDPOINT=https://XXXXXXX.openai.azure.com/ AZURE_LLM_DEPLOYMENT_NAME=gpt-35-XXX AZURE_OPENAI_API_VERSION=2023-07-01-preview AZURE_EMBEDDING_DEPLOYMENT_NAME=xxx-embeddings-change-me -ANTHROPIC_API_KEY=sk-ant-api03-xxx \ No newline at end of file +ANTHROPIC_API_KEY=sk-ant-api03-xxx +VERTEXAI_BEARER=ya29.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file diff --git a/examples/awsboto3_bedrock.py b/examples/awsboto3_bedrock.py index 9ae19048..4788c3f8 100644 --- a/examples/awsboto3_bedrock.py +++ b/examples/awsboto3_bedrock.py @@ -2,9 +2,10 @@ # https://aws.amazon.com/blogs/aws/amazon-bedrock-is-now-generally-available-build-and-scale-generative-ai-applications-with-foundation-models/?trk=55dc591c-143b-4ecb-898e-fa3dcd67469e&sc_channel=el -import boto3 import json +import boto3 + # bedrock = boto3.client(service_name="bedrock", region_name="us-east-1") # bedrock.list_foundation_models() @@ -22,11 +23,11 @@ "inputText": "Knock, knock!", } ) -#https://bedrock-runtime.us-east-1.amazonaws.com/+/model/{modelId}/invoke +# https://bedrock-runtime.us-east-1.amazonaws.com/+/model/{modelId}/invoke response = bedrock_runtime.invoke_model( body=body, modelId=modelId, accept=accept, contentType=contentType ) response_body = json.loads(response.get("body").read()) # %% -print(response_body) \ No newline at end of file +print(response_body) diff --git a/examples/example_image.png b/examples/example_image.png new file mode 100644 index 00000000..c53f45ce Binary files /dev/null and b/examples/example_image.png differ diff --git a/examples/example_mask.png b/examples/example_mask.png new file mode 100644 index 00000000..7431450e Binary files /dev/null and b/examples/example_mask.png differ diff --git a/examples/openai_sdk_oai_embeddings.ipynb b/examples/openai_sdk_oai_embeddings.ipynb new file mode 100644 index 00000000..d6bd0a99 --- /dev/null +++ b/examples/openai_sdk_oai_embeddings.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2024-06-12T06:17:24.074219Z", + "start_time": "2024-06-12T06:17:23.627459Z" + } + }, + "source": [ + "from openai import OpenAI\n", + "import os\n", + "from dotenv import load_dotenv, dotenv_values\n", + "from pprint import pprint\n", + "\n", + "config = dotenv_values(\".env\")\n", + "\n", + "openai_key = config[\"OPENAI_API_KEY\"]\n", + "openai_org_id = config[\"OPENAI_ORG_ID\"]\n", + "print(\n", + " f\"OpenAI api key={openai_key[0:3]}...{openai_key[-3:]}\"\n", + ")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI api key=sk-...fRh\n" + ] + } + ], + "execution_count": 1 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-12T06:22:14.211065Z", + "start_time": "2024-06-12T06:22:14.206557Z" + } + }, + "cell_type": "code", + "source": [ + "example_input = \"Hello. What's your name?\"\n", + "api_base_url = \"http://localhost:8000/project1/openai\"\n", + "model_name = \"text-embedding-3-small\"" + ], + "id": "a40ca168fda39aa1", + "outputs": [], + "execution_count": 7 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-12T06:30:05.470229Z", + "start_time": "2024-06-12T06:30:03.130796Z" + } + }, + "cell_type": "code", + "source": [ + "oai_client = OpenAI(base_url=api_base_url, api_key=openai_key, max_retries=0)\n", + "\n", + "response = oai_client.embeddings.create(\n", + " model=\"text-embedding-3-small\",\n", + " input=example_input\n", + ")\n", + "\n", + "print(response.data[0].embedding)" + ], + "id": "63b642d959c7707a", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.04137829691171646, 0.00026294522103853524, -0.02692558988928795, 0.0028102484066039324, -0.012560876086354256, -0.009239173494279385, -0.04316013678908348, 0.030049310997128487, 0.01460669282823801, -0.048439666628837585, -0.010152091272175312, 0.009772625751793385, -0.01608056016266346, -0.04610787332057953, 0.0137157728895545, 0.0303792804479599, -0.056710924953222275, 0.046063877642154694, -0.005636995658278465, -0.008672723546624184, 0.04654783383011818, -0.00497430469840765, 0.016234546899795532, 0.039112500846385956, 0.008672723546624184, -0.020931126549839973, 0.0380345955491066, 0.0031292198691517115, -0.016586516052484512, -0.04415005072951317, 0.061242520809173584, -0.039200492203235626, 0.011043012142181396, 0.039464469999074936, 0.01096601877361536, -0.00357468007132411, 0.00019781042647082359, -0.04041038453578949, -0.007022871635854244, -0.023823868483304977, 0.016102559864521027, -0.018676329404115677, 0.022426992654800415, 0.01589357852935791, -0.022855954244732857, -0.011037512682378292, -0.03623075783252716, -0.011317986994981766, 0.04474399611353874, 0.02380187064409256, -0.009426156058907509, 0.025913681834936142, 0.015211638994514942, 0.09309566766023636, 0.035394832491874695, -0.04291816055774689, 0.05055147781968117, 0.038870520889759064, -0.011471973732113838, 0.005680991802364588, -0.04291816055774689, -0.028355462476611137, 0.007627817336469889, 0.025429723784327507, -0.024307824671268463, -0.0065939100459218025, -0.03187514841556549, 0.008535236120223999, -0.018676329404115677, 0.027915501967072487, -0.030225295573472977, 0.04698779433965683, 0.024923769757151604, 0.012780856341123581, -0.06898582726716995, 0.0029999816324561834, 4.223450014251284e-05, -0.022317003458738327, 0.03631874918937683, 0.020766140893101692, -0.03596678003668785, 0.0054005165584385395, -0.017708415165543556, -0.012186909094452858, 0.00884320866316557, -0.005977964960038662, -0.07083366066217422, -0.006731397472321987, 0.02789350412786007, -0.029719339683651924, -0.044282037764787674, 0.022855954244732857, -0.0023482898250222206, 0.014089738950133324, -0.005274028051644564, -0.025737697258591652, -0.0016429779352620244, 0.03596678003668785, 0.01890730857849121, 0.023383907973766327, 0.021833045408129692, -0.015343626961112022, 0.0009452279191464186, 0.017301451414823532, 0.016399532556533813, 0.012703862972557545, 0.03994842618703842, -0.004715827759355307, -0.027607528492808342, -0.007583821192383766, -0.12195708602666855, -0.02965334616601467, 0.027409547939896584, -0.008991695009171963, -0.017455438151955605, 0.011812943033874035, 0.025891682133078575, -0.03466889634728432, -0.009558144956827164, -0.039464469999074936, 0.033459004014730453, 0.018610334023833275, 0.03359099477529526, -0.04562391713261604, -0.07043769955635071, -0.007231852971017361, -0.01775241084396839, 0.024483809247612953, -0.024813778698444366, 0.00137075234670192, 0.043380118906497955, -0.050507478415966034, -0.0024294075556099415, 0.01972123421728611, -0.04395206645131111, -0.024263828992843628, -0.01954525150358677, 0.030841240659356117, -0.0074353343807160854, -0.007253850810229778, 0.04993553087115288, -0.04575590416789055, 0.005818479228764772, -0.0026177656836807728, 0.016179552301764488, -0.060186613351106644, -0.013176821172237396, 0.037176672369241714, -0.0008641101885586977, -0.0010813407134264708, 0.06317834556102753, 0.01083403080701828, -0.046855807304382324, 0.017543429508805275, 0.0444360226392746, -0.03394296392798424, 0.0330190435051918, -0.005983464419841766, -0.03266707807779312, 0.0444360226392746, 0.027915501967072487, 0.019787229597568512, -0.02131609246134758, 0.00892570149153471, -0.032997045665979385, 0.008073277771472931, -0.017422441393136978, 0.047735728323459625, 0.008612229488790035, 0.0061099533922970295, -0.020755141973495483, 0.06009862199425697, -0.025891682133078575, -4.571465979097411e-05, -0.036714714020490646, -0.008645226247608662, 0.023911859840154648, -0.009860617108643055, -0.024461811408400536, 0.005499507766216993, 0.0031127214897423983, 0.05336722359061241, 0.004127380438148975, 0.011125504039227962, -0.004495847504585981, -0.02043617144227028, -0.010135592892765999, -0.010179588571190834, -0.0010730915237218142, 0.011482972651720047, -0.0017570927739143372, 0.07633316516876221, 0.03011530451476574, -0.007248351350426674, -0.018863311037421227, -0.040454380214214325, 0.0312592014670372, 0.02121710032224655, -0.039662450551986694, -0.011922933161258698, 0.03821058198809624, -0.022613976150751114, -0.010328075848519802, 0.004039388615638018, 0.002771751955151558, -0.012802854180335999, -0.02086513303220272, 0.017455438151955605, 0.0342729315161705, -0.023207923397421837, 0.05345521494746208, -0.0058844732120633125, 0.026309644803404808, 0.005037548951804638, -0.03933247923851013, -0.017543429508805275, 0.013044832274317741, 0.021602066233754158, 0.01733444817364216, 0.03431692719459534, -0.015332628041505814, 0.018797317519783974, 0.06546614319086075, -0.02263597398996353, 0.016894487664103508, 0.0029284879565238953, 0.02646363154053688, 0.017631422728300095, 0.005752485245466232, 0.011768946424126625, 0.015222637914121151, 0.04190624877810478, 0.02265797182917595, -0.022679969668388367, -0.0369126982986927, -0.009118184447288513, -0.015244635753333569, 0.005636995658278465, 0.03499886766076088, -0.013825763016939163, -0.0022149267606437206, 0.012417888268828392, 0.006511417217552662, 0.05006751790642738, 0.03695669397711754, 0.01810437999665737, 0.010427067056298256, -0.03902450576424599, 0.03141319006681442, 0.0012635119492188096, -0.01792839542031288, -0.008320755325257778, -0.0018739573424682021, -0.060714565217494965, -0.00327495695091784, 0.008865206502377987, -0.00914018228650093, -0.020491166040301323, 0.0063904281705617905, -0.05090344324707985, -0.007831298746168613, -0.019336270168423653, -0.030599260702729225, -0.03970644623041153, -0.026595620438456535, -0.033612992614507675, -0.016300540417432785, -0.022075025364756584, -0.041070323437452316, -0.039574459195137024, -0.02912539429962635, -0.01731245033442974, 0.029257381334900856, 0.025913681834936142, -0.004259368870407343, 0.012461884878575802, -0.0030329786241054535, -0.0710536390542984, 0.01429872028529644, 0.014815674163401127, -0.039486467838287354, 0.03915649652481079, 0.01784040406346321, 0.0012394515797495842, -0.07985285669565201, 0.028267471119761467, -0.003305204212665558, -0.026507627218961716, -0.01416673231869936, 0.02087613195180893, 0.05055147781968117, -0.006912881508469582, 0.007160359062254429, -0.01918228343129158, 0.040014419704675674, -0.009805622510612011, 0.04496397450566292, -0.02734355255961418, 0.014276722446084023, -0.008326254785060883, 0.0411803163588047, 0.04111431911587715, 0.002238299697637558, 0.036010775715112686, -0.0147496797144413, 0.03222711756825447, 0.023955855518579483, 0.0445680096745491, -0.005592999514192343, -0.04892362281680107, 0.015024655498564243, 0.03889251872897148, 0.05640295147895813, -0.038166582584381104, 0.020942125469446182, -0.02263597398996353, 0.050771456211805344, -0.0006052895914763212, -0.004358360078185797, 0.004388607107102871, 0.02903740108013153, -0.022789960727095604, -0.027409547939896584, -0.02468179166316986, -0.031149212270975113, 0.037792619317770004, 0.04786771535873413, 0.011339984834194183, -0.037000689655542374, 0.006929379887878895, -0.02219601348042488, -0.04997952654957771, 0.022185014560818672, 0.0020141948480159044, 0.012549877166748047, 0.012879847548902035, -0.04069635644555092, 0.0440620556473732, 0.022899949923157692, 0.04269817844033241, 0.0047515747137367725, -0.02141508273780346, -0.031127214431762695, -0.01309982780367136, -0.019160285592079163, -0.03724266588687897, 0.037088681012392044, -0.019930215552449226, -0.012186909094452858, 0.004528844729065895, 0.036824703216552734, 0.0024280326906591654, 0.006555413361638784, -0.008210765197873116, -0.04857165366411209, 0.020557159557938576, 0.060274604707956314, -0.03913449868559837, -0.04049837589263916, -0.009486651048064232, 0.018082382157444954, 0.013033833354711533, -0.01340780034661293, -0.04557992145419121, 0.028091486543416977, 0.027761515229940414, -0.037704624235630035, 0.013704773969948292, 0.008155769668519497, 0.015596603974699974, 0.015948573127388954, -0.012395890429615974, -0.014650688506662846, 0.03541683033108711, -0.037088681012392044, 0.04689980298280716, -0.043204132467508316, -0.01820337027311325, 0.02415383793413639, 0.004328112583607435, 0.08407647907733917, 0.03418494015932083, -0.028091486543416977, -0.02487977407872677, 0.013451796025037766, 0.009717630222439766, 0.03986043110489845, -0.030885236337780952, -0.009052189998328686, -0.004680081270635128, 0.05033149570226669, -0.015596603974699974, 0.047471750527620316, -0.012846849858760834, -0.023955855518579483, -0.02211902104318142, 0.06779792904853821, 0.03174315765500069, -0.041334301233291626, -0.058646753430366516, 0.058294784277677536, -0.020293183624744415, 0.0010971517767757177, 0.0021338090300559998, 0.0033162031322717667, 0.019853223115205765, 0.0002215270505985245, -0.01786240190267563, 0.05103543400764465, -0.0007644316065125167, 0.03997042402625084, -0.04914360120892525, 0.03702268749475479, 0.029345374554395676, -0.00821626465767622, -0.03488887846469879, -0.03198513761162758, -0.005763484165072441, -0.012263902463018894, -0.023691879585385323, 0.021360088139772415, 0.005477509927004576, -0.00936566200107336, 0.00661590788513422, -0.031655166298151016, 0.02461579628288746, 0.04247820004820824, -0.06005462631583214, 0.03064325824379921, -0.055918995290994644, -0.0030962228775024414, 0.005422514863312244, 0.030049310997128487, -0.005510506685823202, -0.02380187064409256, -0.01936926692724228, -0.03649473562836647, 0.05464310944080353, 0.037066683173179626, 0.014100737869739532, -0.027673523873090744, -0.014221726916730404, 0.0027978746220469475, -0.0179503932595253, -0.021789049729704857, -0.01998521201312542, 0.06951377540826797, -0.009431655518710613, -0.047383759170770645, 0.02941136807203293, -0.01625654473900795, 0.01128499023616314, 0.015882577747106552, 0.030885236337780952, 0.05037549138069153, 0.03328302130103111, 0.04984753951430321, 0.01021808572113514, -0.01873132400214672, -0.006725898012518883, -0.030621260404586792, 0.031325194984674454, -0.011185999028384686, 0.036098770797252655, -0.011757947504520416, -0.027211565524339676, -0.015431619249284267, -0.029257381334900856, -0.0218440443277359, -0.010108095593750477, 0.0010758412536233664, 0.013011835515499115, 0.008491240441799164, -0.007704810705035925, 0.013242814689874649, -0.005367519799619913, -0.042940158396959305, 0.02503375895321369, -0.03224911540746689, -0.023119930177927017, -0.02479178085923195, 0.015288631431758404, 0.030049310997128487, 0.04360009729862213, -0.00918967742472887, 0.011328985914587975, 0.011647957377135754, 0.047823719680309296, 0.025363730266690254, 0.00870022177696228, 0.02512175217270851, 0.01615755446255207, 0.04340211674571037, -0.00825476087629795, -0.03693469613790512, 0.032469093799591064, -0.019380265846848488, -0.03341500833630562, -0.009134682826697826, -0.07752106338739395, -0.051739368587732315, -0.020282184705138206, 0.025077756494283676, 0.03286505863070488, 0.05433513596653938, 0.0005860413075424731, -0.03315103426575661, -0.03686869889497757, -0.010311577469110489, -0.0066214073449373245, 0.008095275610685349, -0.0015426119789481163, -0.03198513761162758, -0.014331717044115067, -0.019919216632843018, 0.0115819638594985, 0.0014999908162280917, 0.006544414442032576, 0.005265778861939907, -0.035394832491874695, 0.04441402480006218, 0.024395816028118134, 0.02149207703769207, 0.0011294614523649216, -0.020568158477544785, -0.04375408589839935, -0.07052569091320038, 0.013781766407191753, -0.011834940873086452, 0.0346468985080719, 0.016047563403844833, -0.023317912593483925, -0.04122431203722954, 0.02068914845585823, 0.008518737740814686, 0.004619586747139692, 0.010410568676888943, -0.03821058198809624, 0.012417888268828392, -0.026595620438456535, 0.001152146840468049, 0.02256998047232628, 0.03684670105576515, -0.010691042989492416, 0.024395816028118134, 0.020986121147871017, 0.02815748006105423, -0.005232781637459993, -0.008414247073233128, 0.013803764246404171, -0.014716682955622673, -0.0027772514149546623, 0.024439813569188118, -0.03233710676431656, 0.010933022014796734, -0.026243651285767555, -0.03000531531870365, -0.009750626981258392, 0.002104936633259058, 0.017994388937950134, -0.010141092352569103, -0.011933932080864906, -0.007534325588494539, 0.003005481092259288, -0.014804675243794918, 0.037880610674619675, 0.006544414442032576, 0.009272170253098011, 0.030863238498568535, -0.023427903652191162, -0.027739517390727997, -0.029873326420783997, -0.0220310278236866, -0.025605708360671997, 0.010839530266821384, 0.007517827209085226, 0.023581890389323235, 0.07250551134347916, -0.041070323437452316, -0.01624554581940174, -0.011845939792692661, -0.017092470079660416, -0.004952306859195232, 0.005977964960038662, -0.018808316439390182, 0.030687253922224045, -0.005287776701152325, 0.006527915596961975, 0.014155733399093151, 0.02787150628864765, 0.05037549138069153, -0.0009211675496771932, 0.004614087287336588, 0.03554881736636162, 0.046063877642154694, -0.062210433185100555, -0.02441781386733055, -0.05979064851999283, -0.014155733399093151, 0.0045205955393612385, 0.026617618277668953, 0.01069654244929552, -0.01891830749809742, -0.038760531693696976, 0.017092470079660416, -0.003456440754234791, 0.026859596371650696, -1.8421202184981667e-05, 0.02272396720945835, -0.02487977407872677, -0.011361983604729176, 0.005713988561183214, -0.0037039185408502817, -0.04023439809679985, -0.018049385398626328, 0.01865432970225811, -0.017290452495217323, 0.0020966874435544014, -0.026045668870210648, -0.005557252559810877, -0.025275738909840584, -0.0008022406836971641, 0.012736859731376171, 0.030423277989029884, 0.03730865940451622, 0.012670866213738918, -0.0007142485701479018, 0.00034543784568086267, 0.0062254429794847965, 0.016410531476140022, -0.018005387857556343, 0.013968749903142452, 0.0003241272352170199, -0.0012236405164003372, -0.0029614849481731653, 0.005636995658278465, -0.008364751003682613, -0.003203463274985552, 0.023493897169828415, -0.02292194776237011, -0.0025503968354314566, 0.030489271506667137, 0.010003604926168919, 0.04269817844033241, 0.05807480216026306, -0.010405069217085838, -0.011394980363547802, 0.0389145165681839, -0.028531447052955627, -0.005067796446382999, 0.00021259035565890372, -0.04540393501520157, -0.028091486543416977, -0.021954035386443138, 0.00216680602170527, 0.024395816028118134, -0.05407116189599037, 0.003981643822044134, -0.012890846468508244, 0.046855807304382324, 0.02514375001192093, -0.004982553888112307, 0.021558070555329323, -0.03330501914024353, 0.03297504782676697, -0.008249261416494846, 0.029323376715183258, 0.01626754365861416, 0.002103561768308282, -0.0059724655002355576, 0.03216112032532692, -0.01216491125524044, 0.037352658808231354, -0.007303346414119005, 0.04663582518696785, -0.003522434737533331, -0.004097133409231901, 0.0015756089705973864, -0.0005544191226363182, 0.007292347494512796, -0.019050294533371925, 0.006401427090167999, -0.02967534400522709, 0.009090686216950417, 0.02017219550907612, 0.04014640673995018, 0.03810058906674385, -0.010828531347215176, 0.002709882566705346, 0.002955985488370061, 0.00334095093421638, 0.0040778848342597485, 0.03020329773426056, 0.01589357852935791, 0.01599256880581379, -0.012318897992372513, 0.032007135450839996, 0.03284306079149246, 0.023317912593483925, -0.015739591792225838, -0.004039388615638018, -0.012802854180335999, -0.005867974832653999, 0.023955855518579483, -0.0048533156514167786, 0.014947662129998207, -0.024109842255711555, 0.01309982780367136, 0.016993479803204536, 0.041708268225193024, -0.03620875999331474, 0.021899040788412094, -0.0036489234771579504, -0.018346358090639114, -0.014771678484976292, 0.0013123200042173266, -0.02362588606774807, -0.030423277989029884, 0.010911023244261742, -0.010064098984003067, 0.013187820091843605, 0.0038029097486287355, -0.0006774706416763365, -0.007363840937614441, -0.003789160866290331, -0.026221653446555138, -0.024219833314418793, -0.010102596133947372, 0.01043256651610136, 0.04601988196372986, 0.021173104643821716, 0.016542520374059677, -0.016388533636927605, 0.033085040748119354, 0.05996663495898247, 0.01069654244929552, 0.00608245562762022, -0.023603888228535652, -0.0037809116765856743, -0.016476524993777275, -0.006208944134414196, 0.02496776543557644, 0.021602066233754158, 0.013440797105431557, 0.013704773969948292, 0.0039019007235765457, 0.009580142796039581, 0.03598877787590027, -0.05666692927479744, 0.0032502091489732265, 0.031303197145462036, 0.00474882498383522, 0.01074053905904293, 0.043468110263347626, 0.01402374543249607, -0.020447170361876488, 0.03572480380535126, 0.04131230339407921, -0.0018560838652774692, 0.03616476431488991, 0.006885383743792772, 0.03647273778915405, -0.011955929920077324, 0.016278542578220367, -0.01865432970225811, 0.022701967507600784, -0.02121710032224655, 0.013451796025037766, -0.01980922743678093, 0.014045743271708488, 0.04993553087115288, -0.03647273778915405, -0.022767962887883186, -0.009574643336236477, 0.002627389971166849, 0.01990821771323681, -0.02041417360305786, 0.046503838151693344, -0.017972391098737717, -0.019688237458467484, 0.0037176671903580427, 0.016113558784127235, -0.005403266288340092, -0.005977964960038662, -0.021558070555329323, -0.026771605014801025, 0.006472920533269644, 0.005070546176284552, -0.0022932947613298893, -0.0063904281705617905, -0.003676421009004116, -0.018434349447488785, 0.004608587361872196, 0.005147539544850588, 0.028993405401706696, 0.017521431669592857, 0.03306304290890694, -0.028817420825362206, 0.002568270079791546, -0.01327581238001585, -0.02505575679242611, -0.011801944114267826, 0.028179477900266647, 0.02540772594511509, 0.0033327017445117235, 0.010493060573935509, -0.026529625058174133, -0.01492566429078579, 0.021250098943710327, 0.020403174683451653, 0.0005932594067417085, 0.027013583108782768, -0.005845976993441582, 0.0010091597214341164, 0.02283395640552044, -0.04129030555486679, 0.011010014452040195, -0.00817776843905449, 0.025253739207983017, -0.010850529186427593, 0.028729429468512535, 0.02023818902671337, -0.02947736158967018, -0.020370177924633026, 0.004218122456222773, 0.030841240659356117, -0.0027470041532069445, -0.02531973458826542, -0.006285937502980232, -0.042412202805280685, 0.02097512222826481, -0.013264812529087067, 0.00812277290970087, -0.011394980363547802, -0.006676402408629656, 0.012417888268828392, -0.038672540336847305, -0.017257455736398697, -0.041774261742830276, 0.03634074702858925, 0.011768946424126625, 0.00745733268558979, 0.006753395777195692, -0.0026108913589268923, 0.025539714843034744, 0.031017223373055458, 0.02496776543557644, -0.021382085978984833, -0.004116381518542767, 0.014760678634047508, 0.007264849729835987, -0.016322540119290352, 0.02674960531294346, -0.013143823482096195, 0.0044326032511889935, 0.016201550140976906, -0.01337480265647173, -0.0037011688109487295, 0.021470079198479652, -0.0023991602938622236, 0.00460033817216754, 0.02344990149140358, 0.008183267898857594, 0.00860673002898693, 0.036714714020490646, -0.02175605297088623, -0.018687328323721886, -0.004809319507330656, 0.009068688377737999, 0.01564059965312481, 0.012219906784594059, 0.025869684293866158, -0.0004908310947939754, 0.0070778666995465755, 0.02754153497517109, 0.016487523913383484, -0.0444360226392746, -0.005056797526776791, -0.006890883203595877, -0.010377570986747742, 0.03702268749475479, -0.0008455493371002376, -0.009470152668654919, -0.0115489661693573, -0.010724040679633617, 0.008909203112125397, 0.044721998274326324, 0.009679134003818035, -0.010460063815116882, 0.03475688770413399, 0.017972391098737717, -0.005114542320370674, 0.0022630474995821714, -0.026001673191785812, 0.011120004579424858, -0.018060384318232536, -0.0022809209767729044, 0.015376623719930649, -0.007231852971017361, -0.012153912335634232, 0.006494918838143349, -0.0012607622193172574, -0.005513256415724754, -0.010460063815116882, 0.0010263456497341394, -0.028223473578691483, -0.006742396391928196, 0.00424287049099803, 0.04340211674571037, -0.0023771622218191624, -0.03579079732298851, -0.06287037581205368, 0.026441633701324463, 0.03196313977241516, -0.019699236378073692, 0.046943798661231995, 0.017609424889087677, 0.0025613957550376654, 0.02619965560734272, 0.004262118600308895, 0.006852386984974146, -0.008381250314414501, -0.002525649033486843, 0.022492986172437668, 0.00023304164642468095, 0.012505880557000637, -0.006467421073466539, 0.044018059968948364, -0.04166427254676819, -0.01714746467769146, 0.021360088139772415, 0.016102559864521027, -0.006747896317392588, 0.0168834887444973, 0.05024350434541702, 0.002302918815985322, -0.021789049729704857, 0.00579098192974925, -0.007831298746168613, -0.08742017298936844, 0.01572859287261963, -0.0200842022895813, 0.0445680096745491, 0.01670750416815281, 0.018522342666983604, 0.017466437071561813, -0.016916485503315926, 0.00377541221678257, 0.008425245992839336, -0.04751574620604515, 0.010757037438452244, 0.026331642642617226, -0.007160359062254429, 0.033349014818668365, -0.00023544767464045435, -0.03647273778915405, 0.06031860038638115, 0.04298415407538414, 0.005669992417097092, 0.004935808479785919, -0.03856254741549492, -0.015772588551044464, -0.059482675045728683, -0.02007320336997509, 0.012417888268828392, 0.010064098984003067, -0.008276759646832943, -0.04377608373761177, 0.005455511622130871, 0.016223547980189323, -0.018500344827771187, 0.005917470436543226, 0.0076113189570605755, -0.027673523873090744, -0.0033492003567516804, -0.033810973167419434, 0.012010925449430943, 0.021833045408129692, 0.014364714734256268, -0.015145644545555115, 0.022141018882393837, 0.03711067885160446, 0.057854823768138885, -0.005779982544481754, -0.02505575679242611, -0.01564059965312481, -0.04698779433965683, 0.031193207949399948, 0.0038771529216319323, -0.010916522704064846, 0.0013061331119388342, -3.276074130553752e-05, 0.00264113862067461, 0.04430403560400009, -0.0022424242924898863, -0.025605708360671997, 0.017455438151955605, 0.00043789829942397773, 0.0035306839272379875, -0.008243761956691742, 0.010443565435707569, -0.054863091558218, 0.04012440890073776, 0.015530610457062721, -0.0017419691430404782, -0.002104936633259058, 0.03689069673418999, -0.025275738909840584, 0.035570815205574036, -0.014144734479486942, 0.03913449868559837, -0.01837935484945774, 0.0008146145846694708, 0.032029133290052414, 0.017279453575611115, -0.0433141253888607, 0.013781766407191753, 0.02628764696419239, -0.008738717995584011, 0.02122810110449791, 0.014463705942034721, 0.021811047568917274, 0.027387548238039017, -0.019413262605667114, 0.005724987480789423, -0.01549761276692152, 0.018940305337309837, 0.008777214214205742, 0.01792839542031288, 0.05336722359061241, -0.02965334616601467, -0.00510904286056757, 0.02112910896539688, 0.03359099477529526, -0.0136827751994133, 0.03429492935538292, -0.018489345908164978, -0.03977243974804878, -0.023251919075846672, -0.002609516493976116, -0.004911060445010662, -0.029455363750457764, 0.0016512272413820028, 0.03055526502430439, -0.0020801888313144445, 0.002494026906788349, -0.0019564498215913773, 0.010075097903609276, -0.01061955001205206, -0.04283016547560692, -0.02780551090836525, -0.04236820712685585, -0.020293183624744415, -0.023493897169828415, 0.034866880625486374, 0.0027497538831084967, 0.004251119680702686, -0.01678449846804142, -0.009277669712901115, 0.034404922276735306, -0.015332628041505814, 0.007589320652186871, 0.017367444932460785, 0.024483809247612953, 0.013836761936545372, -0.052707284688949585, 0.012846849858760834, 0.00023115119256544858, -0.030357282608747482, 0.00384140620008111, 0.0243518203496933, 0.035042863339185715, 0.021899040788412094, 0.020546160638332367, 0.03216112032532692, -0.01732344925403595, -0.00739133870229125, -0.02947736158967018, 0.02052416279911995, 0.04102632775902748, 0.04181825742125511, 0.005977964960038662, -0.024131840094923973, -0.024901771917939186, -0.013209817931056023, 0.007402337621897459, -0.003558181459084153, 0.01786240190267563, 0.02804749086499214, -0.04164227470755577, 0.01403474435210228, 0.019655240699648857, -0.026881594210863113, 0.0286854337900877, -0.017994388937950134, -0.010801033116877079, 0.01309982780367136, 0.04522795230150223, 0.036538731306791306, -0.011131003499031067, 0.010185088962316513, -0.005463760811835527, 0.047383759170770645, 0.010531557723879814, 0.008265760727226734, 0.0019798227585852146, 0.015739591792225838, -0.004006391391158104, 0.04256619140505791, 0.021008120849728584, 0.041708268225193024, -0.008639726787805557, 0.024461811408400536, -0.026221653446555138, 0.03763863071799278, 0.010377570986747742, -0.009541645646095276, -0.02485777623951435, 0.01751043274998665, -0.00016240733384620398, 0.010146591812372208, -0.00193720159586519, 0.00808427669107914, 0.006219943519681692, -0.023515895009040833, -0.01713646575808525, 0.018313361331820488, -0.004119131248444319, -0.032271113246679306, 0.007528826128691435, 0.03411894664168358, -0.025715699419379234, 0.012769857421517372, -0.01198892667889595, 0.05002352222800255, 0.026265649124979973, 0.003571930341422558, -0.02584768645465374, -0.0265516247600317, 0.0337669774889946, 0.01714746467769146, -0.010449064895510674, -0.008507738821208477, -0.006351931486278772, -0.012824852019548416, 0.012186909094452858, -0.026683611795306206, 0.08266860246658325, -0.01679549738764763, -0.002755253342911601, -0.0019110789289698005, 0.017180463299155235, -0.01021808572113514, 0.03242509812116623, -0.004462850745767355, -0.023515895009040833, 0.018049385398626328, -0.008342753164470196, -0.020810138434171677, -0.007715809624642134, -0.0055902497842907906, 0.01580558530986309, 0.026881594210863113, 0.011911934241652489, -0.024043848738074303, -0.0023936608340591192, 0.0012147037778049707, 0.005961466580629349, -0.042060237377882004, -0.013836761936545372, -0.018676329404115677, 0.013220816850662231, -0.028729429468512535, -0.03517485037446022, 0.01279185526072979, 0.029631348326802254, 0.041708268225193024, 0.011735949665307999, 0.005955967120826244, 0.032469093799591064, -0.0069623771123588085, -0.028531447052955627, 0.009217174723744392, -0.03249109163880348, 0.016718503087759018, 0.03647273778915405, -0.011372982524335384, 0.020799139514565468, -0.003646173747256398, 0.04421604424715042, -0.034690894186496735, 0.017444439232349396, -0.010509559884667397, -0.0029807332903146744, 0.010757037438452244, -0.007655315101146698, 0.011202497407793999, 0.019556250423192978, 0.01857733726501465, 0.010812032036483288, -0.022086024284362793, 0.023493897169828415, -0.026441633701324463, -0.009503149427473545, 0.028553444892168045, 0.007737807463854551, -0.0136827751994133, 0.016399532556533813, 0.02947736158967018, -0.006984374951571226, 0.000233729078900069, 0.0376606285572052, 0.0055655017495155334, -0.013033833354711533, 0.02993931993842125, 0.020711146295070648, 0.024043848738074303, 0.01052605826407671, -0.006071456708014011, 0.025077756494283676, 0.010091597214341164, -0.016839493066072464, 0.013803764246404171, -0.009514148347079754, 0.006115452852100134, -0.006676402408629656, 0.0048670643009245396, -0.02452780492603779, -0.02754153497517109, 0.02727755904197693, -0.0007417461019940674, -0.005521506071090698, 0.02210802212357521, -0.01749943383038044, 0.017477435991168022, 0.009266670793294907, 0.02015019580721855, 0.03128119930624962, 0.009574643336236477, 0.01479367632418871, 0.020271185785531998, 0.005928469356149435, 0.013451796025037766, -0.03988243266940117, 0.03249109163880348, -0.01927027478814125, 0.008876205421984196, -0.010542556643486023, -0.023493897169828415, 0.02220701240003109, 0.009695632383227348, -0.007462832145392895, -0.02166806161403656, 0.048175688832998276, -0.007572822272777557, 0.007506828289479017, -0.03508685901761055, 0.009690132923424244, -0.007567322812974453, 0.0034976869355887175, -0.005122791510075331, 0.024043848738074303, -0.04245620220899582, -0.01873132400214672, -0.008672723546624184, -0.018984301015734673, -0.01840135268867016, 0.005461011081933975, 0.030335284769535065, -0.015827583149075508, -0.02239399589598179, 0.0273655503988266, -0.021349089220166206, 0.014397711493074894, -0.002701633144170046, 0.035130854696035385, 0.0005839790101163089, -0.007534325588494539, 0.0031979638151824474, -0.001821712008677423, -0.0009424781310372055, 0.03587878867983818, 0.018687328323721886, -0.034140944480895996, -0.00945915374904871, 0.023317912593483925, -0.005719488020986319, -0.018412351608276367, -0.0035636811517179012, 0.01935826800763607, 0.0042538694106042385, -0.006516916677355766, 0.017114467918872833, 0.010421567596495152, -0.004858815111219883, -0.002104936633259058, 0.0023675381671637297, -0.015860579907894135, -0.017653420567512512, 0.015926575288176537, 0.0077268085442483425, -0.0002658668381627649, -0.031831152737140656, 0.0252317413687706, 0.020194193348288536, 0.046503838151693344, 0.013308809138834476, -0.007825799286365509, -0.014716682955622673, -0.03838656470179558, -0.004578340332955122, 0.0207771398127079, 0.015002657659351826, -0.005023800302296877, 0.0015659847995266318, 0.004735076334327459, -0.003654422936961055, 0.039486467838287354, -0.0389145165681839, -0.03719867020845413, -0.0062529402785003185, 0.008117273449897766, 0.00860673002898693, 0.008672723546624184, 0.02211902104318142, 0.033525001257658005, 0.0027621279004961252, -0.04025639593601227, -0.012010925449430943, 0.001686974079348147, 0.006120952311903238, -0.02380187064409256, 0.005680991802364588, 0.009805622510612011, -0.017389444634318352, -0.024989763274788857, -0.029191387817263603, -0.0009301042882725596, -0.015211638994514942, -0.0016814745031297207, 0.019589247182011604, -0.014331717044115067, -0.031193207949399948, -0.07329744100570679, -0.023735875263810158, 0.017543429508805275, -0.0011390855070203543, -0.03442692011594772, -0.009228174574673176, 0.0021998032461851835, 0.017972391098737717, -0.002668636152520776, -0.0031869648955762386, -0.038232579827308655, 0.016982480883598328, -0.001083403010852635, -0.007781803607940674, -0.013440797105431557, 0.015783587470650673, -0.019083291292190552, 0.0063409325666725636, 0.033612992614507675, 0.00661590788513422, 0.017268454656004906, 0.03889251872897148, 0.009998105466365814, -0.01074053905904293, -0.014243725687265396, 0.009822120890021324, 0.003324452554807067, -0.026485629379749298, -0.026309644803404808, 0.011966928839683533, -0.016388533636927605, -0.02406584657728672, -0.01327581238001585, 0.025627706199884415, 0.02298794314265251, -0.010471062734723091, 0.0016484775114804506, 0.017994388937950134, -0.001755717908963561, 0.04296215623617172, -0.0573708675801754, -0.0025985173415392637, -0.05187135934829712, -0.028817420825362206, -0.00013207411393523216, -0.00027806885191239417, 0.010674544610083103, 0.013946752063930035, -0.03471289202570915, -0.0028817420825362206, -0.008645226247608662, 0.004033889155834913, 0.013704773969948292, 0.014881668612360954, 0.018621332943439484, 0.02256998047232628, -0.009063188917934895, -0.025781692937016487, 0.0044710999354720116, -0.014496702700853348, 0.009646136313676834, -0.019776230677962303, 0.000852423720061779, 0.00040936961886473, -0.007594820111989975, 0.015651598572731018, 0.02798149548470974, -0.023339910432696342, -0.016223547980189323, -0.0034179440699517727, 0.0007286847685463727, 0.02452780492603779, -0.01460669282823801, -0.008056779392063618, -0.014573696069419384, 0.010630548931658268, -0.0010139717487618327, 0.013935753144323826, 0.006973376031965017, 0.02894940972328186, -0.012362893670797348, -0.03807859122753143, 0.016003567725419998, -0.01016309019178152, 0.014683686196804047, 0.0312592014670372, 0.0147496797144413, 0.01172495074570179, -0.003742414992302656, -0.005436263512820005, 0.006329933647066355, 0.0147496797144413, -0.0070393700152635574, -0.0041796257719397545, 0.04335812106728554, 0.04553592577576637, 0.01355078723281622, 0.04716378077864647, -0.0001012253196677193, 0.01608056016266346, 0.028223473578691483, -0.0008909202879294753, -0.03607677295804024, 0.015838582068681717, 0.00039665200165472925, -0.028839418664574623, 0.014276722446084023, -0.028113484382629395, -0.016179552301764488, 0.047471750527620316, -0.007556323893368244, 0.009772625751793385, 0.009371161460876465, 0.03594478219747543, -0.02210802212357521, 0.02476978302001953, -0.009772625751793385, 0.022404994815587997, -0.03330501914024353, 0.02452780492603779, 0.00915118120610714, -0.012230905704200268, 0.006285937502980232, 0.0415542796254158, 0.03783661499619484, -0.014672687277197838, 0.004561841953545809, 0.006379429250955582, -0.024219833314418793, 0.022613976150751114, 0.009217174723744392, 0.01355078723281622, 0.0547311007976532, -0.008991695009171963, -0.011691953986883163, -0.0007891793502494693, -0.009811121970415115, -0.0017185962060466409, 0.017015477642416954, 0.01025658193975687, -0.023053936660289764, -0.02121710032224655, 0.05534704774618149, -0.013286811299622059, -0.0239778533577919, -0.022789960727095604, -0.003101722337305546, 0.021723056212067604, -0.023999853059649467, 0.008502239361405373, 0.008271260187029839, -0.02690359205007553, 0.015046653337776661, 0.012186909094452858, -0.02237199805676937, 0.007061367854475975, 0.017422441393136978, -3.9570677472511306e-05, -0.053147245198488235, 0.049319587647914886, 0.012054921127855778, 0.0031347195617854595, 0.03684670105576515, -0.025605708360671997, 0.03134719282388687, -0.03482288494706154, -0.013462794944643974, 0.0461958646774292, -0.03064325824379921, 0.053147245198488235, -0.057326868176460266, 0.018698327243328094, 0.010036601684987545, 0.02619965560734272, -0.009178678505122662, -0.060186613351106644, -0.002249298617243767, -0.0036516732070595026, -0.034250933676958084, 0.02318592555820942, 0.011449974961578846, -0.02353789284825325, 0.005353770684450865, 0.009211675263941288, 0.004699329379945993, -0.03136919438838959, -0.004633335396647453, 0.012230905704200268, 0.01786240190267563, -0.003338201204314828, -0.016949482262134552, 0.0027758765500038862, -0.013528789393603802]\n" + ] + } + ], + "execution_count": 12 + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "", + "id": "abf4c54d8fc8123f" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/openai_sdk_oai_images.ipynb b/examples/openai_sdk_oai_images.ipynb new file mode 100644 index 00000000..8753c1d9 --- /dev/null +++ b/examples/openai_sdk_oai_images.ipynb @@ -0,0 +1,270 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2024-06-13T06:27:45.019572Z", + "start_time": "2024-06-13T06:27:44.939556Z" + } + }, + "source": [ + "from openai import OpenAI\n", + "from dotenv import load_dotenv, dotenv_values\n", + "from pprint import pprint\n", + "from io import BytesIO\n", + "import requests\n", + "from PIL import Image\n", + "\n", + "config = dotenv_values(\".env\")\n", + "\n", + "openai_key = config[\"OPENAI_API_KEY\"]\n", + "openai_org_id = config[\"OPENAI_ORG_ID\"]\n", + "print(\n", + " f\"OpenAI api key={openai_key[0:3]}...{openai_key[-3:]}\"\n", + ")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI api key=sk-...fRh\n" + ] + } + ], + "execution_count": 18 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-13T05:29:24.989685Z", + "start_time": "2024-06-13T05:29:24.487824Z" + } + }, + "cell_type": "code", + "source": [ + "client_org = OpenAI(\n", + " organization=openai_org_id,\n", + " api_key=openai_key,\n", + ")\n", + "models = client_org.models.list().data\n", + "models = [m for m in models if \"dall\" in m.id.lower()]\n", + "\n", + "# sort models by model id\n", + "models = sorted(models, key=lambda x: x.id)\n", + "\n", + "# print models which contains gpt in the name\n", + "pprint(models)" + ], + "id": "f30c642a59ead37d", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Model(id='dall-e-2', created=1698798177, object='model', owned_by='system'),\n", + " Model(id='dall-e-3', created=1698785189, object='model', owned_by='system')]\n" + ] + } + ], + "execution_count": 2 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "# Making image variations (DALL-E 2 ONLY)", + "id": "47febd7086348341" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-13T05:29:27.407633Z", + "start_time": "2024-06-13T05:29:27.330497Z" + } + }, + "cell_type": "code", + "source": [ + "api_base_url = \"http://localhost:8000/project1/openai\"\n", + "\n", + "image = Image.open(\"example_image.png\")\n", + "width, height = 256, 256\n", + "image = image.resize((width, height))\n", + "\n", + "byte_stream = BytesIO()\n", + "image.save(byte_stream, format='PNG')\n", + "byte_array = byte_stream.getvalue()" + ], + "id": "1de3932fb7cba99a", + "outputs": [], + "execution_count": 3 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-12T08:54:21.608975Z", + "start_time": "2024-06-12T08:54:09.171591Z" + } + }, + "cell_type": "code", + "source": [ + "oai_client = OpenAI(base_url=api_base_url, api_key=openai_key, max_retries=0)\n", + "\n", + "response = oai_client.images.create_variation(\n", + " image=byte_array,\n", + " n=1,\n", + " model=\"dall-e-2\",\n", + " size=\"1024x1024\"\n", + ")\n", + "\n", + "variation_image_url = response.data[0].url\n", + "print(variation_image_url)\n", + "Image.open(BytesIO(requests.get(variation_image_url).content)).show()" + ], + "id": "319dc04a8a1f9e32", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "https://oaidalleapiprodscus.blob.core.windows.net/private/org-LMkPwoHO0aS4h2JCKfjTaAu8/user-jNPs7HWxxgOXHrqHH3wlCVWQ/img-Kfwwst46ijAFpWGKZoCwwjGX.png?st=2024-06-12T07%3A54%3A22Z&se=2024-06-12T09%3A54%3A22Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-06-11T18%3A46%3A34Z&ske=2024-06-12T18%3A46%3A34Z&sks=b&skv=2023-11-03&sig=LUM7Kf6HcE1cNgpX2k49lyh4Qo%2BycB6/fjN/7%2B9sza0%3D\n" + ] + } + ], + "execution_count": 4 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "# Generating image", + "id": "45ba3cf8c130b4e8" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-13T05:54:48.091049Z", + "start_time": "2024-06-13T05:54:28.043818Z" + } + }, + "cell_type": "code", + "source": [ + "oai_client = OpenAI(base_url=api_base_url, api_key=openai_key, max_retries=0)\n", + "\n", + "response = oai_client.images.generate(\n", + " model=\"dall-e-3\",\n", + " prompt=\"happy corgi dancing with friends\",\n", + " size=\"1024x1024\",\n", + " quality=\"standard\",\n", + " n=1 \n", + ")\n", + "\n", + "generated_image_url = response.data[0].url\n", + "print(generated_image_url)\n", + "Image.open(BytesIO(requests.get(generated_image_url).content)).show()" + ], + "id": "e28a3f4bac0b2f61", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "https://oaidalleapiprodscus.blob.core.windows.net/private/org-LMkPwoHO0aS4h2JCKfjTaAu8/user-jNPs7HWxxgOXHrqHH3wlCVWQ/img-FMx1tO9xGp79hBs6yO4XxL5n.png?st=2024-06-13T04%3A54%3A49Z&se=2024-06-13T06%3A54%3A49Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-06-12T18%3A56%3A52Z&ske=2024-06-13T18%3A56%3A52Z&sks=b&skv=2023-11-03&sig=18RpLUPPbPFTSJYrRsQKtWSqv/O5llIkubJJGr0oF/U%3D\n" + ] + } + ], + "execution_count": 9 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "# Editing image (DALL-E 2 ONLY)", + "id": "f3693630d9529aef" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-13T05:59:11.958702Z", + "start_time": "2024-06-13T05:59:11.871932Z" + } + }, + "cell_type": "code", + "source": [ + "image = Image.open(\"example_image.png\")\n", + "mask = Image.open(\"example_mask.png\")\n", + "width, height = 256, 256\n", + "image = image.resize((width, height))\n", + "mask = mask.resize((width, height))\n", + "\n", + "image_byte_stream = BytesIO()\n", + "image.save(image_byte_stream, format='PNG')\n", + "image_byte_array = image_byte_stream.getvalue()\n", + "\n", + "mask_byte_stream = BytesIO()\n", + "mask.save(mask_byte_stream, format='PNG')\n", + "mask_byte_array = mask_byte_stream.getvalue()" + ], + "id": "e31e708d774751b7", + "outputs": [], + "execution_count": 11 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-13T06:19:09.600486Z", + "start_time": "2024-06-13T06:18:52.620286Z" + } + }, + "cell_type": "code", + "source": [ + "oai_client = OpenAI(base_url=api_base_url, api_key=openai_key, max_retries=0)\n", + "\n", + "response = oai_client.images.edit(\n", + " model=\"dall-e-2\",\n", + " image=image_byte_array,\n", + " mask=mask_byte_array,\n", + " prompt=\"cute little corgi sitting on the grass in a clearing\",\n", + " size=\"1024x1024\",\n", + " n=1 \n", + ")\n", + "\n", + "edited_image_url = response.data[0].url\n", + "print(edited_image_url)\n", + "Image.open(BytesIO(requests.get(edited_image_url).content)).show()" + ], + "id": "bf4df2e95d42dc61", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "https://oaidalleapiprodscus.blob.core.windows.net/private/org-LMkPwoHO0aS4h2JCKfjTaAu8/user-jNPs7HWxxgOXHrqHH3wlCVWQ/img-cwanzdCLUq5FFuIkwz8Ci7LB.png?st=2024-06-13T05%3A19%3A11Z&se=2024-06-13T07%3A19%3A11Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-06-12T18%3A56%3A25Z&ske=2024-06-13T18%3A56%3A25Z&sks=b&skv=2023-11-03&sig=k8n%2Bzp88YjM7tTI6FXfgRwRTRrTFMufp1jsVXU%2BTFik%3D\n" + ] + } + ], + "execution_count": 17 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/openai_sdk_oai_multimodal.ipynb b/examples/openai_sdk_oai_multimodal.ipynb new file mode 100644 index 00000000..4c5a8ce3 --- /dev/null +++ b/examples/openai_sdk_oai_multimodal.ipynb @@ -0,0 +1,191 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2024-06-14T06:17:10.335341Z", + "start_time": "2024-06-14T06:17:09.774330Z" + } + }, + "source": [ + "from openai import OpenAI\n", + "from dotenv import load_dotenv, dotenv_values\n", + "from pprint import pprint\n", + "from io import BytesIO\n", + "import requests\n", + "from PIL import Image\n", + "\n", + "config = dotenv_values(\".env\")\n", + "\n", + "openai_key = config[\"OPENAI_API_KEY\"]\n", + "openai_org_id = config[\"OPENAI_ORG_ID\"]\n", + "print(\n", + " f\"OpenAI api key={openai_key[0:3]}...{openai_key[-3:]}\"\n", + ")" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenAI api key=sk-...fRh\n" + ] + } + ], + "execution_count": 1 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T06:20:03.679940Z", + "start_time": "2024-06-14T06:20:03.660917Z" + } + }, + "cell_type": "code", + "source": [ + "api_base_url = \"http://localhost:8000/project1/openai\"\n", + "model_name = \"gpt-4o\"\n", + "\n", + "oai_client = OpenAI(base_url=api_base_url, api_key=openai_key, max_retries=0)" + ], + "id": "58d26e13439642a6", + "outputs": [], + "execution_count": 2 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "# Analyze image", + "id": "f749cf2912a9b340" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T06:20:05.183887Z", + "start_time": "2024-06-14T06:20:05.176367Z" + } + }, + "cell_type": "code", + "source": [ + "import base64\n", + "\n", + "def encode_image(image_path):\n", + " with open(image_path, \"rb\") as image_file:\n", + " return base64.b64encode(image_file.read()).decode(\"utf-8\")" + ], + "id": "400b43dff031f747", + "outputs": [], + "execution_count": 3 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T06:20:12.193328Z", + "start_time": "2024-06-14T06:20:12.176790Z" + } + }, + "cell_type": "code", + "source": [ + "IMAGE_PATH = \"example_image.png\"\n", + "base64_image = encode_image(IMAGE_PATH)" + ], + "id": "d52065c3eb8a5dcd", + "outputs": [], + "execution_count": 5 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T06:36:49.705444Z", + "start_time": "2024-06-14T06:36:41.797825Z" + } + }, + "cell_type": "code", + "source": [ + "response = oai_client.chat.completions.create(\n", + " model=model_name,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful assistant that responds in Markdown. Help me with my math homework!\"},\n", + " {\"role\": \"user\", \"content\": [\n", + " {\"type\": \"text\", \"text\": \"What is the breed of dog in this photo?\"},\n", + " {\"type\": \"image_url\", \"image_url\": {\n", + " \"url\": f\"data:image/png;base64,{base64_image}\"}\n", + " }\n", + " ]}\n", + " ],\n", + " temperature=0.0,\n", + ")\n", + "print(response.choices[0].message.content)" + ], + "id": "f6d6bdeb5a5f79a", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The dog in the photo appears to be a Pembroke Welsh Corgi. This breed is known for its short legs, long body, and large ears. They are often seen with a coat that is a mix of white and shades of brown or red.\n" + ] + } + ], + "execution_count": 12 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-14T06:26:14.011525Z", + "start_time": "2024-06-14T06:26:10.513372Z" + } + }, + "cell_type": "code", + "source": [ + "completion = oai_client.chat.completions.create(\n", + " model=model_name,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a helpful assistant that helps me with my math homework!\"},\n", + " {\"role\": \"user\", \"content\": \"Hello! Could you solve 20 x 5?\"}\n", + " ]\n", + ")\n", + "print(\"Assistant: \" + completion.choices[0].message.content)" + ], + "id": "aea461cc97de94ab", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Assistant: Of course! The product of 20 and 5 is:\n", + "\n", + "\\[ 20 \\times 5 = 100 \\]\n", + "\n", + "So, \\( 20 \\times 5 = 100 \\).\n" + ] + } + ], + "execution_count": 8 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/openai_sdk_openai.py b/examples/openai_sdk_openai.py index 039c6a36..fce4822d 100644 --- a/examples/openai_sdk_openai.py +++ b/examples/openai_sdk_openai.py @@ -1,34 +1,30 @@ # %% -from openai import OpenAI -import os -from dotenv import load_dotenv, dotenv_values from pprint import pprint +from dotenv import dotenv_values +from openai import OpenAI + config = dotenv_values(".env") openai_key = config["OPENAI_API_KEY"] openai_org_id = config["OPENAI_ORG_ID"] -print( - f"OpenAI api key={openai_key[0:3]}...{openai_key[-3:]}" -) +print(f"OpenAI api key={openai_key[0:3]}...{openai_key[-3:]}") # %% ps_api_base = "http://localhost:8000/project1/openai" -#ps_api_base = "http://promptsail.local:8000/project1/?model=model1&tags=tag1,tag2&experiment=exp1&target_path=" +# ps_api_base = "http://promptsail.local:8000/project1/?model=model1&tags=tag1,tag2&experiment=exp1&target_path=" -#ps_api_base = "https://api.openai.com/v1/model1/" +# ps_api_base = "https://api.openai.com/v1/model1/" -ps_client = OpenAI( - base_url=ps_api_base, - api_key=openai_key, max_retries=0) +ps_client = OpenAI(base_url=ps_api_base, api_key=openai_key, max_retries=0) response = ps_client.chat.completions.create( model="gpt-3.5-turbo", - timeout=1000, - extra_headers={ "accept-encoding": "deflate"}, + timeout=1000, + extra_headers={"accept-encoding": "deflate"}, messages=[ { "role": "system", diff --git a/examples/pyproject.toml b/examples/pyproject.toml new file mode 100644 index 00000000..d21df741 --- /dev/null +++ b/examples/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "PromptSail Examples" +version = "0.1.0" +description = "A collection of packages required for the Prompt Sail project proxy use examples to work correctly." +authors = ["Krzysztof Sopyła "] + +[tool.poetry.dependencies] +ipykernel = "^6.25.2" +langchain = "^0.0" +openai = "^1" +boto3 = "^1.34.13" +anthropic = "^0.25.6" +rich = "^13.7.1" +langchain-anthropic = "^0.1.11" +pillow = "^10.3.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/examples/request_google_vertexai.ipynb b/examples/request_google_vertexai.ipynb new file mode 100644 index 00000000..764a5b45 --- /dev/null +++ b/examples/request_google_vertexai.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "1. Install Google Cloud SDK:\n", + "\n", + "```bash\n", + "(New-Object Net.WebClient).DownloadFile(\"https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe\", \"$env:Temp\\GoogleCloudSDKInstaller.exe\")\n", + "& $env:Temp\\GoogleCloudSDKInstaller.exe\n", + "```\n", + "\n", + "2. Login to Google Cloud SDK and get credentials:\n", + "\n", + "```bash\n", + "gcloud init\n", + "gcloud auth application-default login\n", + "```\n", + "\n", + "3. Get access token using the following command:\n", + "\n", + "```bash\n", + "gcloud auth application-default print-access-token\n", + "```\n", + "\n", + "4. Set `VERTEXAI_BEARER` in `.env`\n", + "\n", + "5. Add vertexai as provider in promptsail:\n", + "\n", + "```json\n", + " {\n", + " deployment_name: 'vertexai',\n", + " slug: 'vertexai',\n", + " api_base: 'https://europe-west3-aiplatform.googleapis.com/v1',\n", + " description: '',\n", + " provider_name: 'Google VertexAI'\n", + " }\n", + "```\n" + ], + "id": "36e201b4c05b8b26" + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "# Example chat content\n", + "chat_content = {\n", + " \"contents\": [\n", + " {\n", + " \"role\": \"USER\",\n", + " \"parts\": {\n", + " \"text\": \"Hello!\"\n", + " }\n", + " },\n", + " {\n", + " \"role\": \"MODEL\",\n", + " \"parts\": {\n", + " \"text\": \"Argh! What brings ye to my ship?\"\n", + " }\n", + " },\n", + " {\n", + " \"role\": \"USER\",\n", + " \"parts\": {\n", + " \"text\": \"Wow! You are a real-life priate!\"\n", + " }\n", + " }\n", + " ],\n", + " \"generation_config\": {\n", + " \"temperature\": 0.2,\n", + " \"topP\": 0.8,\n", + " \"topK\": 40,\n", + " \"maxOutputTokens\": 200\n", + " }\n", + "}" + ], + "id": "eab9c81b28c9d2c7" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-25T02:53:05.872887Z", + "start_time": "2024-05-25T02:53:02.240696Z" + } + }, + "cell_type": "code", + "source": [ + "import requests\n", + "from dotenv import load_dotenv, dotenv_values\n", + "load_dotenv()\n", + "\n", + "config = dotenv_values(\".env\")\n", + "\n", + "bearer = \"Bearer \" + config[\"VERTEXAI_BEARER\"]\n", + "\n", + "base_url = \"http://localhost:8000/project1/vertexai\"\n", + "project = \"ai-services-409611\"\n", + "location = \"europe-west3\"\n", + "model = \"gemini-1.5-pro-preview-0514\"\n", + "\n", + "target_path = f\"/projects/{project}/locations/{location}/publishers/google/models/{model}:generateContent\"\n", + "\n", + "headers = {\n", + " 'Authorization': bearer,\n", + " 'Content-Type': 'application/json'\n", + "}\n", + "\n", + "response = requests.post(base_url + target_path, headers=headers, json=chat_content)\n", + "\n", + "print(response.json())" + ], + "id": "f24a217a443635af", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'candidates': [{'content': {'role': 'model', 'parts': [{'text': \"Shiver me timbers! A real-life pirate, ye say? Aye, that I be! What be yer business with ol' Cap'n AI? 🦜 \\n\"}]}, 'finishReason': 'STOP', 'safetyRatings': [{'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'probabilityScore': 0.26683465, 'severity': 'HARM_SEVERITY_LOW', 'severityScore': 0.23934932}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'probabilityScore': 0.44432363, 'severity': 'HARM_SEVERITY_LOW', 'severityScore': 0.38329497}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'probabilityScore': 0.4141286, 'severity': 'HARM_SEVERITY_LOW', 'severityScore': 0.30239108}, {'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'probabilityScore': 0.23440665, 'severity': 'HARM_SEVERITY_LOW', 'severityScore': 0.21386933}]}], 'usageMetadata': {'promptTokenCount': 23, 'candidatesTokenCount': 37, 'totalTokenCount': 60}}\n" + ] + } + ], + "execution_count": 10 + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": "", + "id": "4135d02e156cf80a" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/ui/Dockerfile b/ui/Dockerfile index a03b8672..e80fb70a 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -16,20 +16,6 @@ ARG NODE_ENV=production ENV NODE_ENV $NODE_ENV -# Those are the environment variables that need to be set to placeholders in the ui/.env.production file -# at runtime the docker_env_replacement.sh script will replace the placeholders with the actual values from the environment variables - -# ENV PORT=80 -# ENV BACKEND_URL="PROMPT_SAIL_ENV_PLACEHOLDER_BACKEND_URLxxxxxx" -# ENV PROXY_URL_HOST="http://localhost:8000" -# ENV SSO_GOOGLE_CLIENT_ID='******.apps.googleusercontent.com' -# ENV SSO_AZURE_CLIENT_ID='6fe*******' -# ENV SSO_AZURE_TENANT='4a1******' -# ENV SSO_AZURE_SCOPES='user.read' -# ENV SSO_AZURE_AUTHORITY='https://login.microsoftonline.com/4a1*****' - - - COPY . ./ RUN npm run build --omit=dev @@ -47,39 +33,4 @@ COPY /deployment/docker_env_replacement.sh /docker-entrypoint.d/docker_env_repla RUN chmod +x /docker-entrypoint.d/docker_env_replacement.sh -#Old Dockerfile -# --- Release with Alpine ---- -# FROM node:20-alpine AS release - - -# ENV PORT=80 -# # ENV PROMPT_SAIL_ENV_PLACEHOLDER_BACKEND_URL='http://promptsail-backend:8000' -# # ENV PROMPT_SAIL_ENV_PLACEHOLDER_PROXY_URL_HOST='http://localhost:8000' -# ENV PROMPT_SAIL_ENV_PLACEHOLDER_SSO_GOOGLE_CLIENT_ID='change_me_docker.apps.googleusercontent.com' - -# WORKDIR /app - -# COPY --from=build /app/package*.json ./ -# COPY --from=build /app/postcss.config.js ./ -# COPY --from=build /app/tailwind.config.js ./ -# COPY --from=build /app/tsconfig*.json ./ -# COPY --from=build /app/vite.config.ts ./ - -# COPY --from=build /app/dist ./dist -# COPY --from=dependencies /app/node_modules ./node_modules - - -# COPY --from=build /app/deployment/docker_env_replacement.sh ./ -# RUN chmod +x docker_env_replacement.sh -# RUN ./docker_env_replacement.sh - -# # RUN ls -la /app/ # Debugging line -# # RUN ls -la /app/dist # Debugging line -# # RUN ls -la /app/dist/assets # Debugging line - - -# EXPOSE 80 -# CMD [ "npm","run","preview" ] - - diff --git a/ui/package-lock.json b/ui/package-lock.json index 491a0064..6e333928 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -12,7 +12,7 @@ "@ant-design/icons": "^5.3.1", "@azure/msal-browser": "^3.13.0", "@react-oauth/google": "^0.12.1", - "antd": "^5.14.2", + "antd": "^5.17.3", "axios": "^1.6.2", "d3-scale-chromatic": "^3.1.0", "dotenv": "^16.3.1", @@ -53,15 +53,6 @@ "vite": "^5.0.0" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -96,9 +87,9 @@ } }, "node_modules/@ant-design/compatible": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@ant-design/compatible/-/compatible-5.1.2.tgz", - "integrity": "sha512-PcNV6hCrlx1JnKBjC148BjJNM3XLVSEER422SOk/HRVD8rm8ZovItlZrQOoO3WH5L4jqwj5gxQap2cqSnmQaTg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/compatible/-/compatible-5.1.3.tgz", + "integrity": "sha512-uLvjwENnpY/1gxYb5m8L2k+WmBpBHqH45fO76+4J+WUDKTUKpmwbEW3OG/in2mSO2HI6h3Mcp57+tAMr18ZkgQ==", "dependencies": { "classnames": "^2.2.6", "dayjs": "^1.11.4", @@ -120,9 +111,9 @@ "integrity": "sha512-LrX0OGZtW+W6iLnTAqnTaoIsRelYeuLZWsrmBJFUXDALQphPsN8cE5DCsmoSlL0QYb94BQxINiuS70Ar/8BNgA==" }, "node_modules/@ant-design/cssinjs": { - "version": "1.18.4", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.4.tgz", - "integrity": "sha512-IrUAOj5TYuMG556C9gdbFuOrigyhzhU5ZYpWb3gYTxAwymVqRbvLzFCZg6OsjLBR6GhzcxYF3AhxKmjB+rA2xA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.20.0.tgz", + "integrity": "sha512-uG3iWzJxgNkADdZmc6W0Ci3iQAUOvLMcM8SnnmWq3r6JeocACft4ChnY/YWvI2Y+rG/68QBla/O+udke1yH3vg==", "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", @@ -138,9 +129,9 @@ } }, "node_modules/@ant-design/icons": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.1.tgz", - "integrity": "sha512-85zROTJCCApQn0Ee6L9561+Vd7yVKtSWNm2TpmOsYMrumchbzaRK83x1WWHv2VG+Y1ZAaKkDwcnnSPS/eSwNHA==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.7.tgz", + "integrity": "sha512-bCPXTAg66f5bdccM4TT21SQBDO1Ek2gho9h3nO9DAKXJP4sq+5VBjrQMSxMVXSB3HyEz+cUbHQ5+6ogxCOpaew==", "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", @@ -162,9 +153,9 @@ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" }, "node_modules/@ant-design/react-slick": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.0.2.tgz", - "integrity": "sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", "dependencies": { "@babel/runtime": "^7.10.4", "classnames": "^2.2.5", @@ -177,62 +168,62 @@ } }, "node_modules/@azure/msal-browser": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.13.0.tgz", - "integrity": "sha512-fD906nmJei3yE7la6DZTdUtXKvpwzJURkfsiz9747Icv4pit77cegSm6prJTKLQ1fw4iiZzrrWwxnhMLrTf5gQ==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.15.0.tgz", + "integrity": "sha512-jqngIR0zGLtEHCAhgXLl+VZTFcU/9DmRSjGj5RbrLnFPL/0L9Hr68k8grvLrTIq7tjhTM5Xgh6Xc0l7JlViHQQ==", "dependencies": { - "@azure/msal-common": "14.9.0" + "@azure/msal-common": "14.10.0" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "14.9.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.9.0.tgz", - "integrity": "sha512-yzBPRlWPnTBeixxLNI3BBIgF5/bHpbhoRVuuDBnYjCyWRavaPUsKAHUDYLqpGkBLDciA6TCc6GOxN4/S3WiSxg==", + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", + "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.6", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz", + "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", + "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-compilation-targets": "^7.24.6", + "@babel/helper-module-transforms": "^7.24.6", + "@babel/helpers": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/template": "^7.24.6", + "@babel/traverse": "^7.24.6", + "@babel/types": "^7.24.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -257,14 +248,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz", + "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -272,13 +263,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", + "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.6", + "@babel/helper-validator-option": "^7.24.6", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -297,62 +288,62 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", + "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", + "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", + "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", + "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", + "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-module-imports": "^7.24.6", + "@babel/helper-simple-access": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -362,97 +353,97 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz", + "integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", + "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", + "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", + "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", + "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz", + "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.6", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -462,12 +453,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.6.tgz", + "integrity": "sha512-FfZfHXtQ5jYPQsCRyLpOv2GeLIIJhs8aydpNh39vRDjhD411XcfWDni5i7OjP/Rs8GAtTn7sWFFELJSHqkIxYg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -477,12 +468,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.6.tgz", + "integrity": "sha512-BQTBCXmFRreU3oTUXcGKuPOfXAGb1liNY4AvvFKsOBAJ89RKcTsIrSsnMYkj59fNa66OFKnSa4AJZfy5Y4B9WA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -492,9 +483,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", + "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -503,33 +494,33 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", + "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz", + "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-function-name": "^7.24.6", + "@babel/helper-hoist-variables": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -538,13 +529,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", + "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1092,9 +1083,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1234,6 +1225,17 @@ "node": ">=14" } }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, "node_modules/@rc-component/color-picker": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-1.5.3.tgz", @@ -1308,9 +1310,9 @@ } }, "node_modules/@rc-component/tour": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.14.2.tgz", - "integrity": "sha512-A75DZ8LVvahBIvxooj3Gvf2sxe+CGOkmzPNX7ek0i0AJHyKZ1HXe5ieIGo3m0FMdZfVOlbCJ952Duq8VKAHk6g==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.0.tgz", + "integrity": "sha512-h6hyILDwL+In9GAgRobwRWihLqqsD7Uft3fZGrJ7L4EiyCoxbnNYwzPXDfz7vNDhWeVyvAWQJj9fJCzpI4+b4g==", "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/portal": "^1.0.0-9", @@ -1327,9 +1329,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.0.0.tgz", - "integrity": "sha512-niwKADPdY5dhdIblV6uwSayVivwo2uUISfJqri+/ovYQcH/omxDYBJKo755QKeoIIsWptxnRpgr7reEnNEZGFg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.0.tgz", + "integrity": "sha512-QarBCji02YE9aRFhZgRZmOpXBj0IZutRippsVBv85sxvG4FGk/vRxwAlkn3MS9zK5mwbETd86mAVg2tKqTkdJA==", "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", @@ -1356,17 +1358,17 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", + "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", "engines": { "node": ">=14.0.0" } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], @@ -1377,9 +1379,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], @@ -1390,9 +1392,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], @@ -1403,9 +1405,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], @@ -1416,9 +1418,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], @@ -1429,9 +1444,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], @@ -1442,9 +1457,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], @@ -1454,10 +1469,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", "cpu": [ "riscv64" ], @@ -1467,10 +1495,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], @@ -1481,9 +1522,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], @@ -1494,9 +1535,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], @@ -1507,9 +1548,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], @@ -1520,9 +1561,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -1575,9 +1616,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -1673,52 +1714,46 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", - "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", + "version": "20.12.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz", + "integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { - "version": "18.2.65", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.65.tgz", - "integrity": "sha512-98TsY0aW4jqx/3RqsUXwMDZSWR1Z4CUlJNue8ueS2/wcxZOsz4xmW1X8ieaWVRHcmmQM3R8xVA4XWB3dJnWwDQ==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.22", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", - "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-syntax-highlighter": { - "version": "15.5.11", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.11.tgz", - "integrity": "sha512-ZqIJl+Pg8kD+47kxUjvrlElrraSUrYa4h0dauY/U/FTUuprSCqvUj+9PNQNQzVc6AJgIWUUxn87/gqsMHNbRjw==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", "dev": true, "dependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -1927,16 +1962,16 @@ "dev": true }, "node_modules/@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.0.tgz", + "integrity": "sha512-KcEbMsn4Dpk+LIbHMj7gDPRKaTMStxxWRkRmxsg/jVdFdJCZWt1SchZcf0M4t8lIKdwwMsEyzhrcOXRrDPtOBw==", "dev": true, "dependencies": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" + "react-refresh": "^0.14.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -2012,56 +2047,56 @@ } }, "node_modules/antd": { - "version": "5.15.2", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.15.2.tgz", - "integrity": "sha512-EByEiCQknPKJVYfD+zneXwEvjdFzvMw8CZrsxw9nq19ftC4uMcMkZ2irasW7RQQGg9i7XsAZpAwYz3anuFX+EA==", + "version": "5.17.4", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.17.4.tgz", + "integrity": "sha512-oDWrcibe1s72223vpvA3/dNBEotGkggyWQVX1+GVrhuVXt/QYE3oU3Tsg3PeMurohvO8kjxambqG/zbmsMG34g==", "dependencies": { "@ant-design/colors": "^7.0.2", - "@ant-design/cssinjs": "^1.18.4", - "@ant-design/icons": "^5.3.1", - "@ant-design/react-slick": "~1.0.2", - "@babel/runtime": "^7.24.0", + "@ant-design/cssinjs": "^1.19.1", + "@ant-design/icons": "^5.3.7", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.24.5", "@ctrl/tinycolor": "^3.6.1", - "@rc-component/color-picker": "~1.5.2", + "@rc-component/color-picker": "~1.5.3", "@rc-component/mutate-observer": "^1.1.0", - "@rc-component/tour": "~1.14.2", - "@rc-component/trigger": "^2.0.0", + "@rc-component/tour": "~1.15.0", + "@rc-component/trigger": "^2.1.1", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.10", "qrcode.react": "^3.1.0", - "rc-cascader": "~3.24.0", - "rc-checkbox": "~3.2.0", - "rc-collapse": "~3.7.2", + "rc-cascader": "~3.26.0", + "rc-checkbox": "~3.3.0", + "rc-collapse": "~3.7.3", "rc-dialog": "~9.4.0", "rc-drawer": "~7.1.0", "rc-dropdown": "~4.2.0", - "rc-field-form": "~1.42.1", + "rc-field-form": "~2.0.1", "rc-image": "~7.6.0", - "rc-input": "~1.4.3", - "rc-input-number": "~9.0.0", - "rc-mentions": "~2.11.1", - "rc-menu": "~9.13.0", - "rc-motion": "^2.9.0", - "rc-notification": "~5.3.0", + "rc-input": "~1.5.1", + "rc-input-number": "~9.1.0", + "rc-mentions": "~2.13.1", + "rc-menu": "~9.14.0", + "rc-motion": "^2.9.1", + "rc-notification": "~5.4.0", "rc-pagination": "~4.0.4", - "rc-picker": "~4.3.0", - "rc-progress": "~3.5.1", + "rc-picker": "~4.5.0", + "rc-progress": "~4.0.0", "rc-rate": "~2.12.0", "rc-resize-observer": "^1.4.0", "rc-segmented": "~2.3.0", - "rc-select": "~14.13.0", - "rc-slider": "~10.5.0", + "rc-select": "~14.14.0", + "rc-slider": "~10.6.2", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.42.0", - "rc-tabs": "~14.1.1", - "rc-textarea": "~1.6.3", + "rc-table": "~7.45.6", + "rc-tabs": "~15.1.0", + "rc-textarea": "~1.7.0", "rc-tooltip": "~6.2.0", - "rc-tree": "~5.8.5", - "rc-tree-select": "~5.19.0", + "rc-tree": "~5.8.7", + "rc-tree-select": "~5.21.0", "rc-upload": "~4.5.2", - "rc-util": "^5.39.1", + "rc-util": "^5.41.0", "scroll-into-view-if-needed": "^3.1.0", "throttle-debounce": "^5.0.0" }, @@ -2119,9 +2154,9 @@ } }, "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.11.5.tgz", + "integrity": "sha512-XNtCsMAeAH1pdLMEg1z8/Bb3a8cdCbui9QbJATRFHHHW5kT6+NPI3zSVQUXgikTFITzsg+kYY5NTWhM2Orwt9w==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -2129,9 +2164,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/autoprefixer": { - "version": "10.4.18", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", - "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "funding": [ { @@ -2149,7 +2184,7 @@ ], "dependencies": { "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001591", + "caniuse-lite": "^1.0.30001599", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -2166,11 +2201,11 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -2203,11 +2238,14 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { @@ -2220,11 +2258,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2296,9 +2334,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001597", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", - "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", + "version": "1.0.30001625", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz", + "integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==", "dev": true, "funding": [ { @@ -2396,9 +2434,9 @@ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } @@ -2660,9 +2698,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" }, "node_modules/debug": { "version": "4.3.4", @@ -2781,9 +2819,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.704", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.704.tgz", - "integrity": "sha512-OK01+86Qvby1V6cTiowVbhp25aX4DLZnwar+NocAOXdzKAByd+jq5156bmo4kHwevWMknznW18Y/Svfk2dU91A==", + "version": "1.4.787", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.787.tgz", + "integrity": "sha512-d0EFmtLPjctczO3LogReyM2pbBiiZbnsKnGF+cdZhsYzHm/A0GV7W94kqzLD8SN4O3f3iHlgLUChqghgyznvCQ==", "dev": true }, "node_modules/emoji-regex": { @@ -2916,9 +2954,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "engines": { "node": ">=10" @@ -2928,9 +2966,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.6.tgz", - "integrity": "sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz", + "integrity": "sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -3247,9 +3285,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3350,9 +3388,9 @@ } }, "node_modules/formik": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz", - "integrity": "sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", + "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==", "funding": [ { "type": "individual", @@ -3444,6 +3482,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3627,9 +3666,9 @@ } }, "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==" + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -3660,6 +3699,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3798,9 +3838,9 @@ "dev": true }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", + "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -4092,12 +4132,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4156,9 +4196,9 @@ } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -4283,17 +4323,17 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -4408,25 +4448,25 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -4447,9 +4487,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -4612,9 +4652,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4762,14 +4802,14 @@ } }, "node_modules/rc-cascader": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.24.0.tgz", - "integrity": "sha512-NwkYsVULA61S085jbOYbq8Z7leyIxVmLwf+71mWLjA3kCfUf/rAKC0WfjQbqBDaLGlU9d4z1EzyPaHBKLYWv6A==", + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.26.0.tgz", + "integrity": "sha512-L1dml383TPSJD1I11YwxuVbmqaJY64psZqFp1ETlgl3LEOwDu76Cyl11fw5dmjJhMlUWwM5dECQfqJgfebhUjg==", "dependencies": { "@babel/runtime": "^7.12.5", "array-tree-filter": "^2.1.0", "classnames": "^2.3.1", - "rc-select": "~14.13.0", + "rc-select": "~14.14.0", "rc-tree": "~5.8.1", "rc-util": "^5.37.0" }, @@ -4779,9 +4819,9 @@ } }, "node_modules/rc-checkbox": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.2.0.tgz", - "integrity": "sha512-8inzw4y9dAhZmv/Ydl59Qdy5tdp9CKg4oPVcRigi+ga/yKPZS5m5SyyQPtYSgbcqHRYOdUhiPSeKfktc76du1A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.3.0.tgz", + "integrity": "sha512-Ih3ZaAcoAiFKJjifzwsGiT/f/quIkxJoklW4yKGho14Olulwn8gN7hOBve0/WGDg5o/l/5mL0w7ff7/YGvefVw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.3.2", @@ -4793,9 +4833,9 @@ } }, "node_modules/rc-collapse": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.7.2.tgz", - "integrity": "sha512-ZRw6ipDyOnfLFySxAiCMdbHtb5ePAsB9mT17PA6y1mRD/W6KHRaZeb5qK/X9xDV1CqgyxMpzw0VdS74PCcUk4A==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.7.3.tgz", + "integrity": "sha512-60FJcdTRn0X5sELF18TANwtVi7FtModq649H11mYF1jh83DniMoM4MqY627sEKRCTm4+WXfGDcB7hY5oW6xhyw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -4855,12 +4895,12 @@ } }, "node_modules/rc-field-form": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.42.1.tgz", - "integrity": "sha512-SqiEmWNP+I61Lt80+ofPvT+3l8Ij6vb35IS+x14gheVnCJN0SRnOwEgsqCEB5FslT7xqjUqDnU845hRZ1jzlAA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.0.1.tgz", + "integrity": "sha512-3WK/POHBcfMFKrzScrkmgMIXqoVQ0KgVwcVnej/ukwuQG4ZHCJaTi2KhM+tWTK4WODBXbmjKg5pKHj2IVmSg4A==", "dependencies": { "@babel/runtime": "^7.18.0", - "async-validator": "^4.1.0", + "@rc-component/async-validator": "^5.0.3", "rc-util": "^5.32.2" }, "engines": { @@ -4890,11 +4930,6 @@ "prop-types": "^15.0" } }, - "node_modules/rc-form/node_modules/async-validator": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.11.5.tgz", - "integrity": "sha512-XNtCsMAeAH1pdLMEg1z8/Bb3a8cdCbui9QbJATRFHHHW5kT6+NPI3zSVQUXgikTFITzsg+kYY5NTWhM2Orwt9w==" - }, "node_modules/rc-form/node_modules/rc-util": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", @@ -4925,9 +4960,9 @@ } }, "node_modules/rc-input": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.4.5.tgz", - "integrity": "sha512-AjzykhwnwYTRSwwgCu70CGKBIAv6bP2nqnFptnNTprph/TF1BAs0Qxl91mie/BR6n827WIJB6ZjaRf9iiMwAfw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.5.1.tgz", + "integrity": "sha512-+nOzQJDeIfIpNP/SgY45LXSKbuMlp4Yap2y8c+ZpU7XbLmNzUd6+d5/S75sA/52jsVE6S/AkhkkDEAOjIu7i6g==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -4939,15 +4974,15 @@ } }, "node_modules/rc-input-number": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.0.0.tgz", - "integrity": "sha512-RfcDBDdWFFetouWFXBA+WPEC8LzBXyngr9b+yTLVIygfFu7HiLRGn/s/v9wwno94X7KFvnb28FNynMGj9XJlDQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.1.0.tgz", + "integrity": "sha512-NqJ6i25Xn/AgYfVxynlevIhX3FuKlMwIFpucGG1h98SlK32wQwDK0zhN9VY32McOmuaqzftduNYWWooWz8pXQA==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/mini-decimal": "^1.0.1", "classnames": "^2.2.5", - "rc-input": "~1.4.0", - "rc-util": "^5.28.0" + "rc-input": "~1.5.0", + "rc-util": "^5.40.1" }, "peerDependencies": { "react": ">=16.9.0", @@ -4955,16 +4990,16 @@ } }, "node_modules/rc-mentions": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.11.1.tgz", - "integrity": "sha512-upb4AK1SRFql7qGnbLEvJqLMugVVIyjmwBJW9L0eLoN9po4JmJZaBzmKA4089fNtsU8k6l/tdZiVafyooeKnLw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.13.1.tgz", + "integrity": "sha512-DSyUDq/PPCleUX1eghIn371lTSRQsIuCs1N7xR9nZcHP9R1NkE7JjpWUP8Gy4EGVPu0JN0qIcokxYJaoGPnofg==", "dependencies": { "@babel/runtime": "^7.22.5", "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", - "rc-input": "~1.4.0", - "rc-menu": "~9.13.0", - "rc-textarea": "~1.6.1", + "rc-input": "~1.5.0", + "rc-menu": "~9.14.0", + "rc-textarea": "~1.7.0", "rc-util": "^5.34.1" }, "peerDependencies": { @@ -4973,9 +5008,9 @@ } }, "node_modules/rc-menu": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.13.0.tgz", - "integrity": "sha512-1l8ooCB3HcYJKCltC/s7OxRKRjgymdl9htrCeGZcXNaMct0RxZRK6OPV3lPhVksIvAGMgzPd54ClpZ5J4b8cZA==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.14.0.tgz", + "integrity": "sha512-La3LBCDMLMs9Q/8mTGbnscb+ZeJ26ebkLz9xJFHd2SD8vfsCKl1Z/k3mwbxyKL01lB40fel1s9Nn9LAv/nmVJQ==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^2.0.0", @@ -4990,13 +5025,13 @@ } }, "node_modules/rc-motion": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.0.tgz", - "integrity": "sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.1.tgz", + "integrity": "sha512-QD4bUqByjVQs7PhUT1d4bNxvtTcK9ETwtg7psbDfo6TmYalH/1hhjj4r2hbhW7g5OOEqYHhfwfj4noIvuOVRtQ==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", - "rc-util": "^5.21.0" + "rc-util": "^5.39.3" }, "peerDependencies": { "react": ">=16.9.0", @@ -5004,9 +5039,9 @@ } }, "node_modules/rc-notification": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.3.0.tgz", - "integrity": "sha512-WCf0uCOkZ3HGfF0p1H4Sgt7aWfipxORWTPp7o6prA3vxwtWhtug3GfpYls1pnBp4WA+j8vGIi5c2/hQRpGzPcQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.4.0.tgz", + "integrity": "sha512-li19y9RoYJciF3WRFvD+DvWS70jdL8Fr+Gfb/OshK+iY6iTkwzoigmSIp76/kWh5tF5i/i9im12X3nsF85GYdA==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -5051,9 +5086,9 @@ } }, "node_modules/rc-picker": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.3.0.tgz", - "integrity": "sha512-bQNB/+NdW55jlQ5lPnNqF5J90Tq4SihLbAF7tzPBvGDJyoYmDgwLm4FN0ZB3Ot9i1v6vJY/1mgqZZTT9jbYc5w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.5.0.tgz", + "integrity": "sha512-suqz9bzuhBQlf7u+bZd1bJLPzhXpk12w6AjQ9BTPTiFwexVZgUKViG1KNLyfFvW6tCUZZK0HmCCX7JAyM+JnCg==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^2.0.0", @@ -5089,9 +5124,9 @@ } }, "node_modules/rc-progress": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.5.1.tgz", - "integrity": "sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.6", @@ -5150,12 +5185,12 @@ } }, "node_modules/rc-select": { - "version": "14.13.0", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.13.0.tgz", - "integrity": "sha512-ew34FsaqHokK4dxVrcIxSYrgWJ2XJYlkk32eiOIiEo3GkHUExdCzmozMYaUc2P67c5QJRUvvY0uqCs3QG67h5A==", + "version": "14.14.0", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.14.0.tgz", + "integrity": "sha512-Uo2wulrjoPPRLCPd7zlK4ZFVJxlTN//yp1xWP/U+TUOQCyXrT+Duvq/Si5OzVcmQyWAUSbsplc2OwNNhvbOeKQ==", "dependencies": { "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^2.0.0", + "@rc-component/trigger": "^2.1.1", "classnames": "2.x", "rc-motion": "^2.0.1", "rc-overflow": "^1.3.1", @@ -5171,13 +5206,13 @@ } }, "node_modules/rc-slider": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.5.0.tgz", - "integrity": "sha512-xiYght50cvoODZYI43v3Ylsqiw14+D7ELsgzR40boDZaya1HFa1Etnv9MDkQE8X/UrXAffwv2AcNAhslgYuDTw==", + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.6.2.tgz", + "integrity": "sha512-FjkoFjyvUQWcBo1F3RgSglky3ar0+qHLM41PlFVYB4Bj3RD8E/Mv7kqMouLFBU+3aFglMzzctAIWRwajEuueSw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", - "rc-util": "^5.27.0" + "rc-util": "^5.36.0" }, "engines": { "node": ">=8.x" @@ -5219,16 +5254,16 @@ } }, "node_modules/rc-table": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.42.0.tgz", - "integrity": "sha512-GwHV9Zs3HvWxBkoXatO/IeKoElzy3Ojf3dcyw1Rj3cyQVb+ZHtexslKdyzsrKRPJ0mUa62BoX+ZAg3zgTEql8w==", + "version": "7.45.7", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.45.7.tgz", + "integrity": "sha512-wi9LetBL1t1csxyGkMB2p3mCiMt+NDexMlPbXHvQFmBBAsMxrgNSAPwUci2zDLUq9m8QdWc1Nh8suvrpy9mXrg==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/context": "^1.4.0", "classnames": "^2.2.5", "rc-resize-observer": "^1.1.0", "rc-util": "^5.37.0", - "rc-virtual-list": "^3.11.1" + "rc-virtual-list": "^3.14.2" }, "engines": { "node": ">=8.x" @@ -5239,14 +5274,14 @@ } }, "node_modules/rc-tabs": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-14.1.1.tgz", - "integrity": "sha512-5nOr9PVpJy2SWHTLgv1+kESDOb0tFzl0cYU9r9d8LfL0Wg9i/n1B558rmkxdQHgBwMqxmwoyPSAbQROxMQe8nw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.1.0.tgz", + "integrity": "sha512-xTNz4Km1025emtkv1q7xKhjPwAtXr/wycuXVTAcFJg+DKhnPDDbnwbA9KRW0SawAVOGvVEj8ZrBlU0u0FGLrbg==", "dependencies": { "@babel/runtime": "^7.11.2", "classnames": "2.x", "rc-dropdown": "~4.2.0", - "rc-menu": "~9.13.0", + "rc-menu": "~9.14.0", "rc-motion": "^2.6.2", "rc-resize-observer": "^1.0.0", "rc-util": "^5.34.1" @@ -5260,13 +5295,13 @@ } }, "node_modules/rc-textarea": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.6.3.tgz", - "integrity": "sha512-8k7+8Y2GJ/cQLiClFMg8kUXOOdvcFQrnGeSchOvI2ZMIVvX5a3zQpLxoODL0HTrvU63fPkRmMuqaEcOF9dQemA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.7.0.tgz", + "integrity": "sha512-UxizYJkWkmxP3zofXgc487QiGyDmhhheDLLjIWbFtDmiru1ls30KpO8odDaPyqNUIy9ugj5djxTEuezIn6t3Jg==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", - "rc-input": "~1.4.0", + "rc-input": "~1.5.0", "rc-resize-observer": "^1.0.0", "rc-util": "^5.27.0" }, @@ -5290,9 +5325,9 @@ } }, "node_modules/rc-tree": { - "version": "5.8.5", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.8.5.tgz", - "integrity": "sha512-PRfcZtVDNkR7oh26RuNe1hpw11c1wfgzwmPFL0lnxGnYefe9lDAO6cg5wJKIAwyXFVt5zHgpjYmaz0CPy1ZtKg==", + "version": "5.8.7", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.8.7.tgz", + "integrity": "sha512-cpsIQZ4nNYwpj6cqPRt52e/69URuNdgQF9wZ10InmEf8W3+i0A41OVmZWwHuX9gegQSqj+DPmaDkZFKQZ+ZV1w==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -5309,13 +5344,13 @@ } }, "node_modules/rc-tree-select": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.19.0.tgz", - "integrity": "sha512-f4l5EsmSGF3ggj76YTzKNPY9SnXfFaer7ZccTSGb3urUf54L+cCqyT+UsPr+S5TAr8mZSxJ7g3CgkCe+cVQ6sw==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.21.0.tgz", + "integrity": "sha512-w+9qEu6zh0G3wt9N/hzWNSnqYH1i9mH1Nqxo0caxLRRFXF5yZWYmpCDoDTMdQM1Y4z3Q5yj08qyrPH/d4AtumA==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", - "rc-select": "~14.13.0", + "rc-select": "~14.14.0", "rc-tree": "~5.8.1", "rc-util": "^5.16.1" }, @@ -5339,9 +5374,9 @@ } }, "node_modules/rc-util": { - "version": "5.39.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.39.1.tgz", - "integrity": "sha512-OW/ERynNDgNr4y0oiFmtes3rbEamXw7GHGbkbNd9iRr7kgT03T6fT0b9WpJ3mbxKhyOcAHnGcIoh5u/cjrC2OQ==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.41.0.tgz", + "integrity": "sha512-xtlCim9RpmVv0Ar2Nnc3WfJCxjQkTf3xHPWoFdjp1fSs2NirQwqiQrfqdU9HUe0kdfb168M/T8Dq0IaX50xeKg==", "dependencies": { "@babel/runtime": "^7.18.3", "react-is": "^18.2.0" @@ -5352,14 +5387,14 @@ } }, "node_modules/rc-util/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/rc-virtual-list": { - "version": "3.11.4", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.11.4.tgz", - "integrity": "sha512-NbBi0fvyIu26gP69nQBiWgUMTPX3mr4FcuBQiVqagU0BnuX8WQkiivnMs105JROeuUIFczLrlgUhLQwTWV1XDA==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.14.2.tgz", + "integrity": "sha512-rA+W5xryhklJAcmswNyuKB3ZGeB855io+yOFQK5u/RXhjdshGblfKpNkQr4/9fBhZns0+uiL/0/s6IP2krtSmg==", "dependencies": { "@babel/runtime": "^7.20.0", "classnames": "^2.2.6", @@ -5375,9 +5410,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -5386,15 +5421,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-fast-compare": { @@ -5438,20 +5473,20 @@ } }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", + "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", "dependencies": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.16.1" }, "engines": { "node": ">=14.0.0" @@ -5461,12 +5496,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz", + "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==", "dependencies": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" + "@remix-run/router": "1.16.1", + "react-router": "6.23.1" }, "engines": { "node": ">=14.0.0" @@ -5477,9 +5512,9 @@ } }, "node_modules/react-smooth": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.0.tgz", - "integrity": "sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", @@ -5491,13 +5526,13 @@ } }, "node_modules/react-svg": { - "version": "16.1.33", - "resolved": "https://registry.npmjs.org/react-svg/-/react-svg-16.1.33.tgz", - "integrity": "sha512-XpKC3G1yZ+ay+lBy1KtJWKGEZGMI+291jEfHdyFfm6X3vMVg/mly2+JjPPCr4ihPElxaZI2z32n2RVV7+PFKVw==", + "version": "16.1.34", + "resolved": "https://registry.npmjs.org/react-svg/-/react-svg-16.1.34.tgz", + "integrity": "sha512-L4ak1qNFLgzVbHm0xQEpHoIOqb3um/B0ybahd3U2TKoGZxb0JaPVI5lsAhvSng2P1kcsYEok2Z7RpcKx7arJGw==", "dependencies": { - "@babel/runtime": "^7.23.9", + "@babel/runtime": "^7.24.1", "@tanem/svg-injector": "^10.1.68", - "@types/prop-types": "^15.7.11", + "@types/prop-types": "^15.7.12", "prop-types": "^15.8.1" }, "peerDependencies": { @@ -5565,9 +5600,9 @@ } }, "node_modules/recharts": { - "version": "2.12.3", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.3.tgz", - "integrity": "sha512-vE/F7wTlokf5mtCqVDJlVKelCjliLSJ+DJxj79XlMREm7gpV7ljwbrwE3CfeaoDlOaLX+6iwHaVRn9587YkwIg==", + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", @@ -5671,6 +5706,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dependencies": { "glob": "^7.1.3" }, @@ -5682,9 +5718,9 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -5697,19 +5733,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, @@ -5743,9 +5782,9 @@ "optional": true }, "node_modules/sass": { - "version": "1.72.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz", - "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==", + "version": "1.77.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.4.tgz", + "integrity": "sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -5759,15 +5798,15 @@ } }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "optional": true }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -5781,13 +5820,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -5795,24 +5831,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -6002,9 +6020,9 @@ } }, "node_modules/stylis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", - "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" }, "node_modules/sucrase": { "version": "3.35.0", @@ -6029,20 +6047,35 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { "node": ">=16 || 14 >=14.17" }, @@ -6075,9 +6108,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -6088,7 +6121,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -6243,9 +6276,9 @@ } }, "node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6279,9 +6312,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -6298,8 +6331,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -6358,9 +6391,9 @@ } }, "node_modules/vite": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", - "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", + "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", @@ -6435,6 +6468,15 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -6582,9 +6624,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", - "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", + "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", "dev": true, "bin": { "yaml": "bin.mjs" diff --git a/ui/package.json b/ui/package.json index 6c57ecda..26b70bd6 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,7 +15,7 @@ "@ant-design/icons": "^5.3.1", "@azure/msal-browser": "^3.13.0", "@react-oauth/google": "^0.12.1", - "antd": "^5.14.2", + "antd": "^5.17.3", "axios": "^1.6.2", "d3-scale-chromatic": "^3.1.0", "dotenv": "^16.3.1", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index a6877b87..5a053ee6 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -4,46 +4,72 @@ import Project from './pages/Project/Project'; import Transactions from './pages/Transactions'; import Sidebar from './components/Sidebar/Sidebar'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Signin from './pages/Signin/Signin'; import { checkLogin } from './storage/login'; -import { Layout } from 'antd'; +import { ConfigProvider, Layout, Modal, notification } from 'antd'; import Transaction from './pages/Transaction/Transaction'; +import theme from './theme-light'; +import { Context } from './context/Context'; +import { useGetConfig } from './api/queries'; +import { getConfig } from './api/interfaces'; const App = () => { const [isLogged, setLoginState] = useState(checkLogin()); - if (isLogged) { - return ( - <> - - - + const [noteApi, noteContextHolder] = notification.useNotification(); + const [modalApi, modalContextHolder] = Modal.useModal(); + const configApi = useGetConfig(); + const [config, setConfig] = useState(null); + useEffect(() => { + if (configApi.isSuccess) { + setConfig(configApi.data.data); + } + }, [configApi.status]); + + return ( + + + {noteContextHolder} + {modalContextHolder} + {isLogged && ( + + + + + } /> + } + /> + } /> + } + /> + } /> + } + /> + } /> + + + + )} + {!isLogged && ( + - } /> } + path="/signin" + element={} /> - } /> - } /> - } /> - } /> - } /> + } /> - - - ); - } else - return ( - - - } /> - - } /> - - - ); + )} + + + ); }; export default App; diff --git a/ui/src/api/api.ts b/ui/src/api/api.ts index 900fed1e..9ab70e3a 100644 --- a/ui/src/api/api.ts +++ b/ui/src/api/api.ts @@ -4,18 +4,20 @@ import { addProjectRequest, getAllProjects, getAllTransactionResponse, + getModels, getProjectResponse, getProviders, getStatisticsTransactionsCost, getStatisticsTransactionsCount, getStatisticsTransactionsSpeed, getTransactionResponse, + getUsers, updateProjectRequest } from './interfaces'; const api = { whoami: (): Promise => { - return client.get('/api/whoami'); + return client.get('/api/auth/whoami'); }, config: (): Promise => { return client.get('/api/config'); @@ -58,6 +60,12 @@ const api = { params: string ): Promise> => { return client.get(`/api/statistics/transactions_speed${params}`); + }, + getUsers: (): Promise> => { + return client.get('/api/users'); + }, + getModels: (): Promise> => { + return client.get('/api/statistics/pricelist'); } }; diff --git a/ui/src/api/formSchemas.tsx b/ui/src/api/formSchemas.tsx index e2e59619..ad424e8d 100644 --- a/ui/src/api/formSchemas.tsx +++ b/ui/src/api/formSchemas.tsx @@ -12,7 +12,7 @@ export const projectSchema = yup.object().shape({ .required('This field is required'), description: yup.string().max(280, 'Maximum length is 280 characters'), tags: yup.array().of(yup.string()), - ai_providers: yup.array().min(1, 'You need to add at least one AI Provider') + owner: yup.string().required('This field is required') }); export const providerSchema = yup.object().shape({ diff --git a/ui/src/api/interfaces.tsx b/ui/src/api/interfaces.tsx index dfd369f5..92c3b5a4 100644 --- a/ui/src/api/interfaces.tsx +++ b/ui/src/api/interfaces.tsx @@ -11,24 +11,13 @@ export interface addProjectRequest { }[]; tags: string[]; org_id: string; + owner: string; } export interface updateProjectRequest extends addProjectRequest {} -export interface getAllProjects { +export interface getAllProjects extends addProjectRequest { id: string; - name: string; - slug: string; - description: string; - ai_providers: { - deployment_name: string; - slug: string; - api_base: string; - description: string; - provider_name: string; - }[]; - tags: string[]; - org_id: string | undefined; total_cost: number; total_transactions: number; } @@ -73,6 +62,7 @@ export interface getTransactionResponse { os: string | null; input_tokens: number | null; output_tokens: number | null; + total_tokens: number | null; library: string; status_code: number; messages: @@ -134,3 +124,33 @@ export interface getStatisticsTransactionsSpeed { total_transactions: number; }[]; } +export interface getLoggedUser { + external_id: string; + email: string; + organization: string; + given_name: string; + family_name: string; + picture: string; + issuer: string; +} +export interface getConfig { + organization: string; + authorization: boolean; + azure_auth: boolean; + google_auth: boolean; +} +export interface getUsers { + id: string; + email: string; + full_name: string; + picture: string | null; +} +[]; +export interface getModels { + model_name: string; + start_date: null | string; + match_pattern: string; + input_price: number; + output_price: number; + total_price: number; +} diff --git a/ui/src/api/queries.ts b/ui/src/api/queries.ts index 91766862..a4eb982c 100644 --- a/ui/src/api/queries.ts +++ b/ui/src/api/queries.ts @@ -1,7 +1,6 @@ import { UseMutationResult, UseQueryResult, useMutation, useQuery } from 'react-query'; import api from './api'; import { AxiosError, AxiosResponse } from 'axios'; -import { useNavigate } from 'react-router-dom'; import { getAllTransactionResponse, addProjectRequest, @@ -12,10 +11,13 @@ import { getProviders, getStatisticsTransactionsCount, getStatisticsTransactionsCost, - getStatisticsTransactionsSpeed + getStatisticsTransactionsSpeed, + getLoggedUser, + getUsers, + getConfig, + getModels } from './interfaces'; import { StatisticsParams, TransactionsFilters } from './types'; -import { notification } from 'antd'; const linkParamsParser = (params: T): string => { let paramsStr = '?'; @@ -105,14 +107,6 @@ export const useAddProject = (): UseMutationResult< return await api.addProject(data); }, { - onSuccess: () => { - notification.success({ - message: 'Success', - description: 'Project successfully added', - placement: 'bottomRight', - duration: 5 - }); - }, onError: (err) => { console.error(`${err.code}: ${err.message}`); } @@ -130,14 +124,6 @@ export const useUpdateProject = (): UseMutationResult< return await api.updateProject(id, data); }, { - onSuccess: () => { - notification.success({ - message: 'Success', - description: 'Project successfully edited', - placement: 'bottomRight', - duration: 5 - }); - }, onError: (err) => { console.error(`${err.code}: ${err.message}`); } @@ -146,32 +132,22 @@ export const useUpdateProject = (): UseMutationResult< }; export const useDeleteProject = (): UseMutationResult => { - const navigate = useNavigate(); return useMutation( async (id) => { return await api.deleteProject(id); }, { - onSuccess: () => { - notification.warning({ - message: 'Success', - description: 'Project successfully deleted', - placement: 'bottomRight', - duration: 5 - }); - navigate('/'); - }, onError: (err) => { console.error(`${err.code}: ${err.message}`); } } ); }; -export const useGetProviders = (): UseQueryResult, AxiosError> => { +export const useGetProviders = (): UseQueryResult => { return useQuery( 'providers', async () => { - return await api.getProviders(); + return (await api.getProviders()).data; }, { staleTime: Infinity, @@ -232,15 +208,7 @@ export const useGetStatistics_TransactionsSpeed = ( } ); }; -export const useGetConfig = (): UseQueryResult< - AxiosResponse<{ - organization: string; - authorization: boolean; - azure_auth: boolean; - google_auth: boolean; - }>, - AxiosError -> => { +export const useGetConfig = (): UseQueryResult, AxiosError> => { return useQuery( 'config', async () => { @@ -254,3 +222,27 @@ export const useGetConfig = (): UseQueryResult< } ); }; +export const useWhoami = (): UseQueryResult, AxiosError> => { + return useQuery('whoami', async () => await api.whoami(), { + staleTime: Infinity, + retry: false, + cacheTime: 0, + refetchOnWindowFocus: false + }); +}; +export const useGetUsers = (): UseQueryResult, AxiosError> => { + return useQuery('users', async () => await api.getUsers(), { + staleTime: Infinity, + retry: false, + cacheTime: 0, + refetchOnWindowFocus: false + }); +}; +export const useGetModels = (): UseQueryResult => { + return useQuery('models', async () => (await api.getModels()).data, { + staleTime: Infinity, + retry: false, + cacheTime: 0, + refetchOnWindowFocus: false + }); +}; diff --git a/ui/src/api/types.tsx b/ui/src/api/types.tsx index 2433a873..78883a99 100644 --- a/ui/src/api/types.tsx +++ b/ui/src/api/types.tsx @@ -10,6 +10,9 @@ export type TransactionsFilters = { project_id?: string; sort_field?: keyof getTransactionResponse | ''; sort_type?: 'asc' | ''; + status_codes?: string; + providers?: string; + models?: string; }; export type StatisticsParams = { diff --git a/ui/src/assets/logo/symbol-teal-outline.svg b/ui/src/assets/logo/symbol-teal-outline.svg new file mode 100644 index 00000000..2fba0b89 --- /dev/null +++ b/ui/src/assets/logo/symbol-teal-outline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/src/assets/loupe.svg b/ui/src/assets/loupe.svg new file mode 100644 index 00000000..ab6094bb --- /dev/null +++ b/ui/src/assets/loupe.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/src/assets/paper_boat.svg b/ui/src/assets/paper_boat.svg new file mode 100644 index 00000000..fe89c155 --- /dev/null +++ b/ui/src/assets/paper_boat.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/ui/src/assets/signin.png b/ui/src/assets/signin.png new file mode 100644 index 00000000..26cbb32d Binary files /dev/null and b/ui/src/assets/signin.png differ diff --git a/ui/src/components/Container/Container.tsx b/ui/src/components/Container/Container.tsx new file mode 100644 index 00000000..6624c6e9 --- /dev/null +++ b/ui/src/components/Container/Container.tsx @@ -0,0 +1,35 @@ +import { Flex } from 'antd'; +import { ReactElement, Children, cloneElement } from 'react'; + +interface Props { + children: React.ReactNode; + classname?: string; +} + +const Container: React.FC = ({ children, classname }) => { + const renderChildren = () => { + return Children.map(children, (child, id) => { + let className = 'py-[16px]'; + if (id === 0) className = 'pb-[16px]'; + if (id === Children.count(children) - 1) { + className = id === 0 ? '' : 'pt-[16px]'; + } + return cloneElement(child as ReactElement, { + className: `${className} px-[24px] border-0 ${ + (child as ReactElement).props.className || '' + }` + }); + }); + }; + return ( + + {renderChildren()} + + ); +}; +export default Container; diff --git a/ui/src/components/DefaultAvatar/DefaultAvatar.tsx b/ui/src/components/DefaultAvatar/DefaultAvatar.tsx new file mode 100644 index 00000000..79459253 --- /dev/null +++ b/ui/src/components/DefaultAvatar/DefaultAvatar.tsx @@ -0,0 +1,8 @@ +import { UserOutlined } from '@ant-design/icons'; + +const DefaultAvatar: React.FC = () => ( +
+ +
+); +export default DefaultAvatar; diff --git a/ui/src/components/HeaderContainer/HeaderContainer.tsx b/ui/src/components/HeaderContainer/HeaderContainer.tsx new file mode 100644 index 00000000..29674a98 --- /dev/null +++ b/ui/src/components/HeaderContainer/HeaderContainer.tsx @@ -0,0 +1,29 @@ +import { Flex, Layout } from 'antd'; +import outline from '../../assets/logo/symbol-teal-outline.svg'; + +interface Props { + children: React.ReactNode; + height?: number; +} + +const { Header } = Layout; + +const HeaderContainer: React.FC = ({ children, height = 80 }) => { + return ( +
+ + {children} + + +
+ ); +}; + +export default HeaderContainer; diff --git a/ui/src/components/ProjectForms/DeleteProject.tsx b/ui/src/components/ProjectForms/DeleteProject.tsx index e272f8be..523f681e 100644 --- a/ui/src/components/ProjectForms/DeleteProject.tsx +++ b/ui/src/components/ProjectForms/DeleteProject.tsx @@ -1,7 +1,11 @@ import { useDeleteProject } from '../../api/queries'; -import { Button, Modal } from 'antd'; -import { ExclamationCircleFilled } from '@ant-design/icons'; -const { confirm } = Modal; +import { Button, Typography } from 'antd'; +import { DeleteOutlined } from '@ant-design/icons'; +import { Context } from '../../context/Context'; +import { useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; + +const { Paragraph } = Typography; interface Props { name: string; @@ -9,25 +13,50 @@ interface Props { } const DeleteProject: React.FC = ({ name, projectId }) => { + const { notification, modal } = useContext(Context); + const navigate = useNavigate(); const deleteProject = useDeleteProject(); return ( - <> - - + deleteProject.mutateAsync(projectId).then(() => { + if (notification) + notification.success({ + message: 'Project deleted!', + placement: 'topRight', + duration: 5 + }); + }); + navigate('/'); + }, + okButtonProps: { + danger: true, + icon: + }, + okText: 'Delete', + closable: true + }); + }} + > + Delete + ); }; export default DeleteProject; diff --git a/ui/src/components/ProjectForms/ProjectDetails.tsx b/ui/src/components/ProjectForms/ProjectDetails.tsx new file mode 100644 index 00000000..c84a6e51 --- /dev/null +++ b/ui/src/components/ProjectForms/ProjectDetails.tsx @@ -0,0 +1,178 @@ +import { Form, Input, Select, Tag, Typography } from 'antd'; +import { FormikProps, FormikProvider } from 'formik'; +import { toSlug } from '../../helpers/aiProvider'; +import { useContext, useState } from 'react'; +import { FormikValuesTemplate } from './types'; +import UserSelect from './UserSelect'; +import { Context } from '../../context/Context'; + +const { Title, Paragraph, Text } = Typography; + +interface Props { + formik: FormikProps; +} + +const ProjectDetails: React.FC = ({ formik }) => { + const config = useContext(Context).config; + const [isSlugGenerated, setSlugGenerate] = useState(true); + return ( +
+ + Project details + + +
console.log(formik.errors)} + autoComplete="on" + noValidate={true} + > + + rules={[{ required: true }]} + className="mb-[10px]" + > + Owner: + + You have been assigned automatically as owner.{' '} + {config?.authorization + ? 'However, you can change it at any time' + : 'However, you cannot change it when authorization is disabled'} + + + + + rules={[{ required: true }]} + help={formik.errors.name} + validateStatus={formik.errors.name ? 'error' : ''} + className="mb-[10px]" + > + Name: + + Give a unique name for your project. + + { + const val = e.currentTarget.value; + if (isSlugGenerated) + formik.setValues((old) => ({ + ...old, + slug: toSlug(val) + })); + }} + onChange={formik.handleChange} + /> + + + help={formik.errors.slug} + validateStatus={formik.errors.slug ? 'error' : ''} + className="mb-[10px]" + > + Slug: + + A slug used to identify project is unique and utilized as a part of the + proxy URL + + setSlugGenerate(false)} + value={formik.values.slug} + onBlur={(e) => { + const val = e.currentTarget.value; + if (val.length < 1) setSlugGenerate(true); + e.currentTarget.value = toSlug(val); + }} + onChange={formik.handleChange} + /> + + + help={formik.errors.description} + validateStatus={formik.errors.description ? 'error' : ''} + className="mb-[10px]" + > + + Description:{' '} + (optional) + + + + + validateStatus={formik.errors.tags ? 'error' : ''} + help={formik.errors.tags} + className="mb-[10px]" + > + + Tags: (optional) + + + Add tags to easily search and categorize the project + + { - const val = e.currentTarget.value; - if (isSlugGenerated) - formik.setValues((old) => ({ - ...old, - slug: toSlug(val) - })); - }} - onChange={formik.handleChange} - /> - - - - - label="slug" - rules={[{ required: true }]} - help={formik.errors.slug} - validateStatus={formik.errors.slug ? 'error' : ''} - > - setSlugGenerate(false)} - value={formik.values.slug} - onBlur={(e) => { - const val = e.currentTarget.value; - if (val.length < 1) setSlugGenerate(true); - e.currentTarget.value = toSlug(val); - }} - onChange={formik.handleChange} - /> - - - - - label="Description" - help={formik.errors.description} - validateStatus={formik.errors.description ? 'error' : ''} - > - - - - label="Tags" - validateStatus={formik.errors.tags ? 'error' : ''} - help={formik.errors.tags} - > - + + + rules={[{ required: true }]} + help={formik.errors.api_base} + validateStatus={formik.errors.api_base ? 'error' : ''} + className="mb-[10px]" + > + API Base URL: + + Enter the base URL for the LLM endpoint you want to connect with + + + + + rules={[{ required: true }]} + help={formik.errors.slug} + validateStatus={formik.errors.slug ? 'error' : ''} + className="mb-[10px]" + > + API Base URL: + + Enter the base URL for the LLM endpoint you want to connect with + + + + + ); +}; + +export default ProviderForm; diff --git a/ui/src/components/ProjectForms/ProviderDetails/ProviderSelect.tsx b/ui/src/components/ProjectForms/ProviderDetails/ProviderSelect.tsx new file mode 100644 index 00000000..a5201403 --- /dev/null +++ b/ui/src/components/ProjectForms/ProviderDetails/ProviderSelect.tsx @@ -0,0 +1,43 @@ +import { Select } from 'antd'; +import { useGetProviders } from '../../../api/queries'; +import { SizeType } from 'antd/es/config-provider/SizeContext'; + +interface Props { + className?: string; + value: string; + size?: SizeType; + onChange?: ( + value: string, + option: + | { + label: string; + value: string; + } + | { + label: string; + value: string; + }[] + ) => void; +} + +const ProviderSelect: React.FC = ({ className, value, size, onChange }) => { + const Providers = useGetProviders(); + return ( + + + + rules={[{ required: true }]} + help={formik.errors.api_base} + validateStatus={formik.errors.api_base ? 'error' : ''} + className="mb-[10px]" + > + API Base URL: + + Enter the base URL for the LLM endpoint you want to connect with + + + + + rules={[{ required: true }]} + help={formik.errors.slug} + validateStatus={formik.errors.slug ? 'error' : ''} + className="mb-[10px]" + > + API Base URL: + + Enter the base URL for the LLM endpoint you want to connect with + + + + + {showSubmitButton && ( + + )} + + ); +}; + +export default ProviderEditableElement; diff --git a/ui/src/components/ProjectForms/ProviderFormAndList/ProviderForm.tsx b/ui/src/components/ProjectForms/ProviderFormAndList/ProviderForm.tsx deleted file mode 100644 index 6f0feb3c..00000000 --- a/ui/src/components/ProjectForms/ProviderFormAndList/ProviderForm.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useFormik } from 'formik'; -import { useGetProviders } from '../../../api/queries'; -import { FormikValues } from '../types'; -import { makeUrl, toSlug } from '../../../helpers/aiProvider'; -import { SetStateAction, useState } from 'react'; -import { providerSchema } from '../../../api/formSchemas'; -import { Button, Col, Form, Input, Row, Select, Typography } from 'antd'; - -const { Paragraph } = Typography; - -export interface Props { - EditedProvider: number | null; - ProvidersList: typeof FormikValues.ai_providers; - setProvidersList: (list: typeof FormikValues.ai_providers) => void; - setEditedProvider: (arg: SetStateAction) => void; - setFormShow: (arg: SetStateAction) => void; - slug: string; -} - -const ProviderForm: React.FC = ({ - EditedProvider, - ProvidersList, - setProvidersList, - setEditedProvider, - setFormShow, - slug -}) => { - const providers = useGetProviders(); - const formik = useFormik({ - initialValues: - EditedProvider != null - ? { - deployment_name: ProvidersList[EditedProvider].deployment_name, - slug: ProvidersList[EditedProvider].slug, - description: ProvidersList[EditedProvider].description, - api_base: ProvidersList[EditedProvider].api_base, - provider_name: ProvidersList[EditedProvider].provider_name - } - : { - deployment_name: '', - slug: '', - description: '', - api_base: '', - provider_name: '' - }, - onSubmit: async (values) => { - const { deployment_name, provider_name, api_base, description } = values; - if ( - ProvidersList.filter( - (e, idx) => - toSlug(e.deployment_name) === toSlug(deployment_name) && - idx != EditedProvider - ).length > 0 - ) { - formik.setErrors({ deployment_name: 'Name must be unique' }); - } else { - formik.setErrors({}); - if (EditedProvider != null) { - const newList = ProvidersList.map((el, idx) => { - if (idx === EditedProvider) - return { ...formik.values, slug: toSlug(deployment_name) }; - else return el; - }); - setProvidersList(newList); - setEditedProvider(null); - } else - setProvidersList([ - ...ProvidersList, - { - api_base, - slug: toSlug(deployment_name), - provider_name, - deployment_name, - description - } - ]); - setFormShow(false); - } - }, - validateOnChange: false, - validationSchema: providerSchema - }); - const [apiBasePlaceholder, setApiBasePlaceholder] = useState('https://ai-provider.url'); - return ( -
console.log(formik.errors)} - autoComplete="on" - noValidate={true} - > - - - - label="AI Providers" - name="provider_name" - validateStatus={formik.errors.provider_name ? 'error' : ''} - help={formik.errors.provider_name} - rules={[{ required: true }]} - > - - - - - - - label="Api base URL" - name="api_base" - validateStatus={formik.errors.api_base ? 'error' : ''} - help={formik.errors.api_base} - rules={[{ required: true }]} - > - - - Proxy URL - - {makeUrl(slug, formik.values.deployment_name)} - - - - ); -}; - -export default ProviderForm; diff --git a/ui/src/components/ProjectForms/ProviderFormAndList/ProviderFormAndList.tsx b/ui/src/components/ProjectForms/ProviderFormAndList/ProviderFormAndList.tsx deleted file mode 100644 index dba5ecbe..00000000 --- a/ui/src/components/ProjectForms/ProviderFormAndList/ProviderFormAndList.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { FormikValues } from '../types'; -import { useState } from 'react'; - -import ProvidersListCollapse from './ProvidersList'; -import ProviderForm from './ProviderForm'; -import Container from '../../../pages/Project/Container'; -import { Button, Tooltip } from 'antd'; - -interface Props { - ProvidersList: typeof FormikValues.ai_providers; - setProvidersList: (list: typeof FormikValues.ai_providers) => void; - projectSlug: string; - isProjects: boolean; - isError: boolean; -} - -const ProviderFormAndList: React.FC = ({ - ProvidersList, - setProvidersList, - projectSlug, - isProjects, - isError -}) => { - const [EditedProvider, setEditedProvider] = useState(null); - const [FormShowed, setFormShow] = useState(!isProjects); - return ( - <> - {ProvidersList.length > 0 && ( - - )} - {FormShowed && ( - - - Add at least one AI provider - - , to use PromptSail as a proxy server for collecting{' '} - - transactions - {' '} - in the project - - } - > - - - )} - {!FormShowed && !EditedProvider && ( - - )} - - ); -}; -export default ProviderFormAndList; diff --git a/ui/src/components/ProjectForms/ProviderFormAndList/ProvidersList.tsx b/ui/src/components/ProjectForms/ProviderFormAndList/ProvidersList.tsx deleted file mode 100644 index 0b625e3a..00000000 --- a/ui/src/components/ProjectForms/ProviderFormAndList/ProvidersList.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { Button, Collapse, Descriptions, Space, Typography } from 'antd'; -import { FormikValues } from '../types'; -import { makeUrl } from '../../../helpers/aiProvider'; -import { SetStateAction } from 'react'; -import ProviderForm from './ProviderForm'; -const { Paragraph } = Typography; - -interface Props { - ProvidersList: typeof FormikValues.ai_providers; - projectSlug: string; - EditedProvider: number | null; - setEditedProvider: (arg: SetStateAction) => void; - setProvidersList: (list: typeof FormikValues.ai_providers) => void; - setFormShow: (arg: SetStateAction) => void; -} - -const ProvidersListCollapse: React.FC = ({ - ProvidersList, - projectSlug, - EditedProvider, - setEditedProvider, - setProvidersList, - setFormShow -}) => { - return ( - <> - ({ - key: id, - label: ( - - {el.deployment_name} - - {makeUrl(projectSlug, el.deployment_name)} - - - ), - children: ( - <> - - - - - - {EditedProvider == id && ( - - )} - - ) - }))} - /> - - ); -}; - -export default ProvidersListCollapse; diff --git a/ui/src/components/ProjectForms/UserSelect.tsx b/ui/src/components/ProjectForms/UserSelect.tsx new file mode 100644 index 00000000..79dfc423 --- /dev/null +++ b/ui/src/components/ProjectForms/UserSelect.tsx @@ -0,0 +1,100 @@ +import { Flex, Select, SelectProps, Typography } from 'antd'; +import { useContext, useEffect, useState } from 'react'; +import { Context } from '../../context/Context'; +import api from '../../api/api'; +import { useFormikContext } from 'formik'; +import { FormikValuesTemplate } from './types'; +import DefaultAvatar from '../DefaultAvatar/DefaultAvatar'; +const { Text } = Typography; + +interface Props extends SelectProps {} + +const UserSelect: React.FC = ({ ...rest }) => { + const config = useContext(Context).config; + const [options, setOptions] = useState<{ label: JSX.Element | string; value: string }[]>([]); + const { setFieldValue, values, handleChange } = useFormikContext(); + useEffect(() => { + if (config !== null) { + if (config.authorization) { + if (values.owner !== '__UPDATE__') + api.getUsers().then((data) => { + setOptions(() => { + const list = data.data.map((user) => ({ + label: , + value: user.email + })); + if (values.owner.length > 0) { + if (list.map((item) => item.value).includes(values.owner)) { + const owner = list.filter( + (item) => item.value === values.owner + )[0]; + const rest = list.filter((item) => item.value !== owner.value); + list.splice(0, list.length); + list.push(owner, ...rest); + } else + list.unshift({ + label: , + value: values.owner + }); + } + return list; + }); + setFieldValue( + 'owner', + !values.owner.length ? data.data[0].email : values.owner + ); + }); + } else { + api.whoami().then((data) => { + setOptions(() => [ + { + label: ( + + ), + value: data.data.email + } + ]); + setFieldValue('owner', data.data.email); + }); + } + } + }, [config, values]); + return ( + setSearch(e.target.value)} + /> +
+ ({ + key: el.id || el[query.label] || '', + label: el[query.label] + })) + .filter(({ label }) => + search.length > 0 + ? label.toLowerCase().includes(search.toLowerCase()) + : true + ) + } + selectable + multiple={multiselect} + onSelect={(val) => { + setSelectedKeys(val.selectedKeys); + }} + onDeselect={(val) => { + setSelectedKeys(val.selectedKeys); + }} + selectedKeys={selectedKeys as string[]} + /> +
+ + + + + + + ); + } +}; +export default FilterStringSelectMenu; diff --git a/ui/src/components/tables/filters/FilterTags.tsx b/ui/src/components/tables/filters/FilterTags.tsx index ae1be075..227a0483 100644 --- a/ui/src/components/tables/filters/FilterTags.tsx +++ b/ui/src/components/tables/filters/FilterTags.tsx @@ -1,14 +1,20 @@ -import { SetStateAction } from 'react'; +import { SetStateAction, useEffect } from 'react'; import { TransactionsFilters } from '../../../api/types'; -import { Select, Tag } from 'antd'; +import { Button, Divider, Flex, Select, Tag } from 'antd'; +import { FilterDropdownProps } from 'antd/es/table/interface'; interface Props { - defaultValue: string; + filters: TransactionsFilters; setFilters: (args: SetStateAction) => void; - setTags: (tags: string) => void; } -const FilterTags: React.FC = ({ defaultValue, setFilters, setTags }) => { +const FilterTags: React.FC = ({ + setSelectedKeys, + selectedKeys, + confirm, + setFilters, + filters +}) => { const options = [ { value: 'tag1' @@ -29,32 +35,55 @@ const FilterTags: React.FC = ({ defaultValue, setFilters, setTags }) => { value: 'tag6' } ]; - const defaults: typeof options = []; - if (defaultValue.length > 0) - defaultValue.split(',').map((el) => { - defaults.push({ value: el }); - }); + useEffect(() => { + if (filters.tags) setSelectedKeys(filters.tags.split(',')); + }, [filters.tags]); return ( - { + return ( + + {value} + + ); + }} + onChange={setSelectedKeys} + placeholder="Select tags" + defaultValue={filters.tags ? filters.tags.split(',') : []} + value={selectedKeys} + options={options} + placement="topLeft" + /> + + + + + + ); }; export default FilterTags; diff --git a/ui/src/context/Context.tsx b/ui/src/context/Context.tsx new file mode 100644 index 00000000..0c04a526 --- /dev/null +++ b/ui/src/context/Context.tsx @@ -0,0 +1,10 @@ +import { HookAPI } from 'antd/es/modal/useModal'; +import { NotificationInstance } from 'antd/es/notification/interface'; +import { createContext } from 'react'; +import { getConfig } from '../api/interfaces'; + +export const Context = createContext<{ + notification: NotificationInstance | null; + modal: HookAPI | null; + config: getConfig | null; +}>({ notification: null, modal: null, config: null }); diff --git a/ui/src/helpers/dataContainer.tsx b/ui/src/helpers/dataContainer.tsx index dae389ce..6fda7ead 100644 --- a/ui/src/helpers/dataContainer.tsx +++ b/ui/src/helpers/dataContainer.tsx @@ -1,31 +1,79 @@ import { Popover, Space, Tag } from 'antd'; +import { useEffect, useRef, useState } from 'react'; interface Props { tags: string[]; classname?: string; } -export const TagsContainer: React.FC = ({ tags, classname }) => ( - - {tags - .filter((_el, id) => id < 3) - .map((e, id) => ( - - {e} - - ))} - {tags.length > 3 && ( - ( - - {e} - - ))} - title="Tags" - trigger="hover" - > - ... - - )} - -); +export const TagsContainer: React.FC = ({ tags, classname }) => { + const containerRef = useRef(null); + const gap = 8; + const tagsJSX: JSX.Element[] = tags.map((el, id) => ( + + {el} + + )); + const [visibleTags, setVisibleTags] = useState([] as JSX.Element[]); + const [isWidthLoading, setWidthLading] = useState(true); + useEffect(() => { + const handleTagsWidth = () => { + let containerW = 0; + const visibleChildren: JSX.Element[] = []; + if (containerRef.current) { + const container = containerRef.current as HTMLDivElement; + const hiddenTags = container.querySelectorAll('.not-visible-tags')[0]; + if (hiddenTags) { + containerW = container.getBoundingClientRect().width; + hiddenTags.childNodes.forEach((el) => { + const width = (el as HTMLDivElement).getBoundingClientRect().width; + containerW -= width; + if (containerW - (gap * visibleChildren.length + 40) > 0) + visibleChildren.push( + + {el.textContent} + + ); + else return; + }); + setVisibleTags(visibleChildren); + setWidthLading(false); + } + } + }; + handleTagsWidth(); + setWidthLading(false); + window.addEventListener('resize', () => { + setWidthLading(true); + handleTagsWidth(); + }); + return () => + window.removeEventListener('resize', () => { + setWidthLading(true); + handleTagsWidth(); + }); + }, []); + return ( + + {visibleTags.map((el) => el)} + {visibleTags.length < tags.length && ( + + {tagsJSX} + + } + title="Tags" + trigger="hover" + > + +{tags.length - visibleTags.length} + + )} + {isWidthLoading && ( +
+ {tagsJSX.map((el) => el)} +
+ )} + + ); +}; diff --git a/ui/src/index.sass b/ui/src/index.sass index f13abff0..52141877 100644 --- a/ui/src/index.sass +++ b/ui/src/index.sass @@ -1,6 +1,10 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,100..1000&display=swap') +@import url('https://fonts.googleapis.com/css2?family=Unica+One&display=swap') + body margin: 0 overflow: hidden + font-family: 'Roboto Flex' @import './func.sass' @@ -10,8 +14,65 @@ body @import './components/Sign/styles' +$headers:("fontSize1": 38, "fontSize2": 30, "fontSize3": 24, "fontSize4": 20, "fontSize5": 16, "lineHeight1": 2.875, "lineHeight2": 2.375, "lineHeight3": 2, "lineHeight4": 1.75, "lineHeight5": 1.5) + +@for $i from 1 through 5 + .h#{$i} + font-size: #{map-get($headers, "fontSize#{$i}")} + px !important + line-height: #{map-get($headers, "lineHeight#{$i}")} !important + .ant-table-body overflow-y: hidden !important -iframe - margin: auto +.ant-btn-dashed + @apply text-Primary/colorPrimary border-Primary/colorPrimary +.ant-btn-sm + padding-top: 0 + padding-bottom: 0 +.header-tab + & > .ant-tabs-nav::before + content: none + +// .test +// // background: orange +// height: 200px +// overflow: auto +// & > div +// height: 500px + +.ant-table-body + scrollbar-width: auto + scrollbar-color: auto + tbody + tr:last-child + td + border-bottom: none + +div + &::-webkit-scrollbar + width: 8px // Width of the scrollbar + height: 8px // Height of the scrollbar + + &::-webkit-scrollbar-track // Color of the track (the area behind the scrollbar) + @apply bg-Background/colorBgBase + + &::-webkit-scrollbar-thumb + // Color of the scrollbar thumb + @apply bg-Border/colorBorder + border-radius: 14px + &::-webkit-scrollbar-button + width: 4px +.transactions-table + .ant-table + @apply border-Border/colorBorderSecondary #{!important} + border-radius: 8px !important + border-width: 1px + border-style: solid + padding-bottom: 4px !important + .ant-table-thead th + @apply text-Text/colorText #{!important} + padding: 8px !important + .ant-table-tbody td + padding-left: 8px !important + padding-right: 8px !important + \ No newline at end of file diff --git a/ui/src/pages/Dashboard/Dashboard.tsx b/ui/src/pages/Dashboard/Dashboard.tsx index b92513af..975906d3 100644 --- a/ui/src/pages/Dashboard/Dashboard.tsx +++ b/ui/src/pages/Dashboard/Dashboard.tsx @@ -1,131 +1,237 @@ -import { Flex, Input, Space, Typography, Card, Segmented } from 'antd'; -import { CSSProperties, useState } from 'react'; -import ProjectTile from '../../components/ProjectTile/ProjectTile'; +import { Flex, Typography, Button, Row, Col, Pagination } from 'antd'; +import { useContext, useEffect, useState } from 'react'; import { getAllProjects } from '../../api/interfaces'; import { useGetAllProjects } from '../../api/queries'; -import { AppstoreOutlined, BarsOutlined, TableOutlined } from '@ant-design/icons'; -import TableDashboard from './TableDashboard'; -import { Link } from 'react-router-dom'; +import { PlusSquareOutlined } from '@ant-design/icons'; +import FilterDashboard from './FilterDashboard'; +import ProjectTile from '../../components/ProjectTile/ProjectTile'; +import noFoundImg from '../../assets/paper_boat.svg'; +import noResultsImg from '../../assets/loupe.svg'; +import { useNavigate } from 'react-router-dom'; +import HeaderContainer from '../../components/HeaderContainer/HeaderContainer'; +import { Context } from '../../context/Context'; const { Title, Text } = Typography; const Dashboard = () => { - const textStyles: CSSProperties = { lineHeight: '2em' }; - const titleStyles: CSSProperties = { margin: '0' }; - const [dashView, setDashView] = useState('list'); const projects = useGetAllProjects(); + const auth = useContext(Context).config?.authorization; const [filter, setFilter] = useState(''); - const filterProjects = (data: getAllProjects) => { - return ( - data.name.includes(filter) || - data.slug.includes(filter) || - data.description.includes(filter) || - data.tags.join(', ').includes(filter) - ); + const [filterOwner, setFilterOwner] = useState(null); + const [pageData, setPageData] = useState({ + page: 1, + size: 5 + }); + const [paginationInfo, setPaginationInfo] = useState(''); + const [filteredProjects, setFilteredProjects] = useState([]); + type range = { + start: number | null; + end: number | null; }; - if (projects.isLoading) - return ( - <> -
loading...
- - ); - if (projects.isError) - return ( - <> -
An error has occurred
- {console.error(projects.error)} - - ); - if (projects.isSuccess) { - const filteredProjects = projects.data.filter((el) => filterProjects(el)); - return ( - <> - -
- - - - Projects - - 1 members - {projects.data.length} projects - - - }, - { label: 'List', value: 'list', icon: }, - { label: 'Table', value: 'table', icon: } - ]} - disabled - /> - { - const val = e.currentTarget.value; - if (val.length > 2) setFilter(val); - else if (filter != '') setFilter(''); + const [isAsc, setAsc] = useState(false); + const [sortby, setSortby] = useState('title'); + const [costRange, setCostRange] = useState({ start: null, end: null }); + const [transactionsRange, setTransactionsRange] = useState({ start: null, end: null }); + const inSearch = (data: getAllProjects) => { + return data.name.includes(filter) || data.tags.join(', ').includes(filter); + }; + const inOwner = (data: getAllProjects) => { + return filterOwner ? data.owner === filterOwner : true; + }; + const inCostRange = (data: getAllProjects) => { + const { start, end } = costRange; + if (start == null || end == null) return true; + return data.total_cost >= start && data.total_cost <= end; + }; + const inTransactionsRange = (data: getAllProjects) => { + const { start, end } = transactionsRange; + if (start == null || end == null) return true; + return data.total_transactions >= start && data.total_transactions <= end; + }; + const sortInterpreter = (arg: string, a: getAllProjects, b: getAllProjects) => { + switch (arg) { + case 'title': + return a.name > b.name ? 1 : -1; + case 'transactions': + return a.total_transactions - b.total_transactions; + case 'cost': + return a.total_cost - b.total_cost; + default: + return 0; + } + }; + const navigate = useNavigate(); + useEffect(() => { + if (projects.isSuccess) { + const filteredData = projects.data + .filter( + (el) => + inSearch(el) && inCostRange(el) && inTransactionsRange(el) && inOwner(el) + ) + .sort((a, b) => { + const asc = isAsc ? 1 : -1; + return sortInterpreter(sortby, a, b) * asc; + }); + setFilteredProjects(filteredData); + setPaginationInfo( + `Showing ${(pageData.page - 1) * pageData.size}-${ + pageData.page * pageData.size + } of ${filteredData.length}` + ); + } + }, [ + projects.status, + pageData, + costRange, + transactionsRange, + isAsc, + sortby, + filter, + filterOwner + ]); + return ( + + +
+ + Projects ({filteredProjects.length}) + +
+ +
+ {projects.isLoading && ( + <> +
loading...
+ + )} + {projects.isError && ( + <> +
An error has occurred
+ {console.error(projects.error)} + + )} + {projects.isSuccess && ( +
+ {projects.data.length < 1 && ( + + + + Start your journey + + + Create your fist project + + + + )} + {projects.data.length > 0 && ( + <> + + current.total_cost > max ? current.total_cost : max, + 0 + ) }} - style={{ marginTop: 'auto', maxWidth: '300px' }} + transactionsRange={{ + ...transactionsRange, + max: projects.data.reduce( + (max, current) => + current.total_transactions > max + ? current.total_transactions + : max, + 0 + ) + }} + owner={filterOwner} + onSearch={setFilter} + onSortAsc={setAsc} + onSortByChange={setSortby} + onChangeCost={setCostRange} + onChangeTransactions={setTransactionsRange} + onSetOwner={setFilterOwner} /> - - {dashView != 'table' && ( - <> - 0 && ( + - - - - Add new project + - - - - - {filteredProjects.map((e) => ( - - ))} + + Title: + + {auth && Owner:} + Transactions: + Cost: + + )} + + {filteredProjects + .slice( + (pageData.page - 1) * pageData.size, + pageData.page * pageData.size + ) + .map((e) => ( + + ))} {filteredProjects.length == 0 && ( -

No projects found

+ + + + No results + + + Refine your search or adjust filters to see more + results. + + )} - - )} - {dashView == 'table' && } -
-
- - ); - } +
+ {filteredProjects.length > 0 && ( + + {paginationInfo} + setPageData({ page, size })} + pageSizeOptions={[5, 15, 30, 45, 60]} + showSizeChanger + total={filteredProjects.length} + hideOnSinglePage={filteredProjects.length < pageData.size} + /> + + )} + + )} +
+ )} +
+ ); }; export default Dashboard; diff --git a/ui/src/pages/Dashboard/FilterDashboard.tsx b/ui/src/pages/Dashboard/FilterDashboard.tsx new file mode 100644 index 00000000..32eb213a --- /dev/null +++ b/ui/src/pages/Dashboard/FilterDashboard.tsx @@ -0,0 +1,162 @@ +import { + CheckOutlined, + RedoOutlined, + SearchOutlined, + SortAscendingOutlined, + SortDescendingOutlined +} from '@ant-design/icons'; +import { Button, Flex, Form, Input, Select } from 'antd'; +import React, { useContext, useEffect } from 'react'; +import { useState } from 'react'; +import SelectForm from './SelectForm'; +import UserFilter from './UserFilter'; +import { Context } from '../../context/Context'; + +interface Props { + onSearch: (text: string) => void; + onSortAsc: (isAsc: boolean) => void; + onSortByChange: (sortby: string) => void; + onSetOwner: (value: string | null) => void; + owner: string | null; + costRange: { start: number | null; end: number | null; min?: number; max?: number }; + transactionsRange: { start: number | null; end: number | null; min?: number; max?: number }; + onChangeCost: (obj: { start: number | null; end: number | null }) => void; + onChangeTransactions: (obj: { start: number | null; end: number | null }) => void; +} + +const FilterDashboard: React.FC = ({ + onSearch, + onSortAsc, + onSortByChange, + owner, + costRange, + transactionsRange, + onChangeCost, + onChangeTransactions, + onSetOwner +}) => { + const [isAsc, setAsc] = useState(false); + const [isClearActive, setClear] = useState(false); + const auth = useContext(Context).config?.authorization; + useEffect(() => { + if ( + costRange.start != null || + costRange.end != null || + transactionsRange.start != null || + transactionsRange.end != null || + owner != null + ) + setClear(true); + else setClear(false); + }, [costRange, transactionsRange, owner]); + return ( +
+ + + + { + const val = e.currentTarget.value; + onSearch(val); + }} + suffix={} + allowClear + /> + + +
+ { + const val = v.target.valueAsNumber; + const min = Number(v.target.min); + if (val >= min || isNaN(val)) { + onChange({ start: val, end: endNull ? max : end }); + setDisplay( + `${prefix}${val || startNull ? min : start} - ${ + endNull ? max : end + }` + ); + } + }} + /> + { + const val = v.target.valueAsNumber; + const max = Number(v.target.max); + if (val <= max) { + onChange({ start: startNull ? min : start, end: val }); + setDisplay( + `${prefix}${startNull ? min : start} - ${ + val || endNull ? max : end + }` + ); + } + }} + /> + + + } + trigger="click" + onOpenChange={setOpen} + > + + + + ); +}; + +export default SelectForm; diff --git a/ui/src/pages/Dashboard/TableDashboard.tsx b/ui/src/pages/Dashboard/TableDashboard.tsx deleted file mode 100644 index 6b99af3d..00000000 --- a/ui/src/pages/Dashboard/TableDashboard.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { Card, Popover, Space, Table, Tag, Typography } from 'antd'; -import { Link, useNavigate } from 'react-router-dom'; -import { getAllProjects } from '../../api/interfaces'; -const { Text, Title } = Typography; - -interface Props { - data: getAllProjects[]; -} - -const TableContainer: React.FC = ({ data }) => { - const navigate = useNavigate(); - const tableData = data.map((el) => ({ - key: el.id, - name: [el.name, el.description], - members: 1, - transactions: el.total_transactions, - cost: el.total_cost, - tags: el.tags.map((tag) => ({ - label: tag, - color: 'blue' - })) - })); - - const columns = [ - { - title: 'Name', - dataIndex: 'name', - key: 'name', - render: (text: string[]) => ( - - - {text[0]} - - {text[1]} - - ) - }, - { - title: 'Members', - dataIndex: 'members', - key: 'members' - }, - { - title: 'Transactions', - dataIndex: 'transactions', - key: 'transactions' - }, - { - title: 'Total cost', - dataIndex: 'cost', - key: 'cost', - render: (text: number) => `$ ${text.toFixed(4)}` - }, - { - title: 'Tags', - dataIndex: 'tags', - key: 'tags', - render: (text: Array<{ color: string; label: string }>) => { - return ( - - {text - .filter((_el, id) => id < 3) - .map((e, id) => ( - - {e.label} - - ))} - {text.length > 3 && ( - ( - - {e.label} - - ))} - title="Tags" - trigger="hover" - > - ... - - )} - - ); - } - } - ]; - return ( - { - return { - onClick: () => { - navigate(`/projects/${row.key}`); - } - }; - }} - size="small" - summary={() => ( - - - - - - - - Add new project + - - - - - - - - )} - sticky={{ offsetHeader: 0 }} - /> - ); -}; -export default TableContainer; diff --git a/ui/src/pages/Dashboard/UserFilter.tsx b/ui/src/pages/Dashboard/UserFilter.tsx new file mode 100644 index 00000000..3958aef9 --- /dev/null +++ b/ui/src/pages/Dashboard/UserFilter.tsx @@ -0,0 +1,57 @@ +import { Flex, Select, SelectProps, Typography } from 'antd'; +import defAvatar from '../../assets/logo/symbol-white.svg'; +import { useGetUsers } from '../../api/queries'; +import { useEffect, useState } from 'react'; +const { Text } = Typography; +interface Props extends SelectProps {} +const UserFilter: React.FC = ({ ...rest }) => { + const [options, setOptions] = useState<{ label: JSX.Element | string; value: string }[]>([]); + const users = useGetUsers(); + useEffect(() => { + if (users.isSuccess) { + setOptions( + users.data?.data.map((user) => ({ + label: , + value: user.email + })) + ); + } + }, [users.status]); + return ( + { + setTags(value.length > 0 ? '?tags=' + value.join(',') : ''); + }} + /> + +
+ + AI Model Name and Version Tag: + + + Specifying the model name and version is only necessary for + Azure deployments - it will allow calculate cost properly. + + +
+ + + )} + + + + ); +}; +export default AiProvidersList; diff --git a/ui/src/pages/Project/AiProvidersTable.tsx b/ui/src/pages/Project/AiProvidersTable.tsx deleted file mode 100644 index 21673e3c..00000000 --- a/ui/src/pages/Project/AiProvidersTable.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Table, Typography } from 'antd'; -import { getProjectResponse } from '../../api/interfaces'; -import { useEffect, useState } from 'react'; -import { makeUrl } from '../../helpers/aiProvider'; -const { Text } = Typography; - -interface Props { - providers: getProjectResponse['ai_providers']; - slug: string; -} -interface DataType { - key: string; - provider: string; - deploymentName: string; - proxyUrl: string | React.ReactNode; - apiBaseUrl: string; -} - -const AiProvidersTable: React.FC = ({ providers, slug }) => { - const [isLoading, setLoading] = useState(true); - const [tableData, setTableData] = useState([]); - - useEffect(() => { - setTableData( - providers.map((el, id) => ({ - key: `${slug + el.deployment_name + id}`, - provider: el.provider_name, - deploymentName: el.deployment_name, - proxyUrl: {makeUrl(slug, el.deployment_name)}, - apiBaseUrl: el.api_base - })) - ); - setLoading(false); - }, []); - const columns = [ - { - title: 'Provider', - dataIndex: 'provider', - key: 'provider' - }, - { - title: 'Deployment name', - dataIndex: 'deploymentName', - key: 'deploymentName' - }, - { - title: 'Proxy url', - dataIndex: 'proxyUrl', - key: 'proxyUrl' - }, - { - title: 'Api base url', - dataIndex: 'apiBaseUrl', - key: 'apiBaseUrl' - } - ]; - return ( -
- ); -}; -export default AiProvidersTable; diff --git a/ui/src/pages/Project/Container.tsx b/ui/src/pages/Project/Container.tsx deleted file mode 100644 index 562b4da0..00000000 --- a/ui/src/pages/Project/Container.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Flex, Typography } from 'antd'; -import { ReactNode } from 'react'; -const { Title, Paragraph } = Typography; - -interface Props { - children: React.ReactNode; - header: string | React.ReactNode; - desc?: string | ReactNode; - classname?: { - parent?: string; - title?: string; - box?: string; - }; -} - -const Container: React.FC = ({ children, header, desc, classname }) => { - return ( - - {typeof header === typeof '' && ( - - {header} - - )} - {typeof header !== typeof '' && header} - {!!desc && {desc}} -
- {children} -
-
- ); -}; -export default Container; diff --git a/ui/src/pages/Project/LatestTransactions.tsx b/ui/src/pages/Project/LatestTransactions.tsx deleted file mode 100644 index f4b2a120..00000000 --- a/ui/src/pages/Project/LatestTransactions.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { Badge, Flex, Table, Tag, Tooltip } from 'antd'; -import { Link } from 'react-router-dom'; -import { TagsContainer } from '../../helpers/dataContainer'; -import { useEffect, useState } from 'react'; -import { DataType, columns } from '../../components/tables/columns'; -import { useGetAllTransactions } from '../../api/queries'; -import { ArrowRightOutlined } from '@ant-design/icons'; - -interface Props { - projectId: string; -} - -const LatestTransactions: React.FC = ({ projectId }) => { - const transactions = useGetAllTransactions({ - project_id: projectId, - page_size: '5' - }); - const [isLoading, setLoading] = useState(false); - const [tableData, setTableData] = useState<{ - items: DataType[]; - page_index: number; - page_size: number; - total_elements: number; - }>({ - items: [], - page_index: 1, - page_size: 10, - total_elements: 0 - }); - useEffect(() => { - if (transactions.isError) { - console.error(transactions.error); - alert(`${transactions.error.code}: ${transactions.error.message}`); - } - if (transactions.isLoading) { - setLoading(true); - } - if (transactions.isSuccess) { - setTableData(() => { - const data = transactions.data.data; - return { - items: data.items.map((tr) => { - const rightMessage = tr.error_message || tr.last_message; - return { - key: tr.id, - id: ( - - - - {tr.id.length > 10 - ? tr.id.substring(0, 10) + '...' - : tr.id} - - - - ), - time: new Date(tr.request_time + 'Z') - .toLocaleString('pl-PL') - .padStart(20, '0'), - speed: tr.status_code < 300 ? tr.generation_speed.toFixed(3) : 'null', - messages: ( - -
- Input: {tr.prompt} -
-
- Output: {' '} - {rightMessage.length > 25 - ? rightMessage.substring(0, 23) + '...' - : rightMessage} -
-
- ), - status: ( - = 300 - ? tr.status_code >= 400 - ? 'error' - : 'warning' - : 'success' - } - text={tr.status_code} - /> - ), - project: ( - {tr.project_name} - ), - aiProvider: tr.provider, - model: tr.model, - tags: , - cost: - tr.total_cost !== null && tr.status_code < 300 - ? `$ ${tr.total_cost.toFixed(4)}` - : 'null', - tokens: - tr.status_code < 300 ? ( - - {tr.input_tokens} {tr.output_tokens}{' '} - (Σ {tr.response.content.usage.total_tokens}) - - ) : ( - null - ) - }; - }), - page_index: data.page_index, - page_size: data.page_size, - total_elements: data.total_elements - }; - }); - setLoading(false); - } - }, [transactions.status]); - return ( -
({ - ...el, - sorter: false - }))} - pagination={false} - loading={isLoading} - size="small" - scroll={{ y: 400 }} - /> - ); -}; -export default LatestTransactions; diff --git a/ui/src/pages/Project/Project.tsx b/ui/src/pages/Project/Project.tsx index 376c53b4..44b554a7 100644 --- a/ui/src/pages/Project/Project.tsx +++ b/ui/src/pages/Project/Project.tsx @@ -1,25 +1,39 @@ -import { useNavigate, useParams } from 'react-router-dom'; -import { useGetProject } from '../../api/queries'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import { useGetProject, useUpdateProject } from '../../api/queries'; import { getProjectResponse } from '../../api/interfaces'; import { AxiosResponse } from 'axios'; import { UseQueryResult } from 'react-query'; import UpdateProject from './Update/UpdateProject'; import AddProject from './Add/AddProject'; -import { Button, Flex, Typography } from 'antd'; -import Container from './Container'; -import { TagsContainer } from '../../helpers/dataContainer'; -import AiProvidersTable from './AiProvidersTable'; -import LatestTransactions from './LatestTransactions'; -import DeleteProject from '../../components/ProjectForms/DeleteProject'; +import { Breadcrumb, Flex, Tabs, Typography } from 'antd'; import Statistics from './Statistics/Statistics'; -const { Title, Paragraph } = Typography; +import { NotificationInstance } from 'antd/es/notification/interface'; +import HeaderContainer from '../../components/HeaderContainer/HeaderContainer'; +import { useEffect, useState } from 'react'; +import ProjectDetails from './ProjectDetails'; +import AiProvidersList from './AiProvidersList'; +import ProjectTransactions from './ProjectTransactions'; +import DeleteProject from '../../components/ProjectForms/DeleteProject'; +import { Context } from '../../context/Context'; +import { useContext } from 'react'; +const { Title } = Typography; -const Project: React.FC & { Add: React.FC; Update: React.FC } = () => { +interface AddProps { + notification: NotificationInstance; +} + +const Project: React.FC & { Add: React.FC; Update: React.FC } = () => { const navigate = useNavigate(); + const { notification } = useContext(Context); + const [currentTab, setCurrentTab] = useState('1'); const params = useParams(); const project: UseQueryResult> = useGetProject( params.projectId || '' ); + const updateProject = useUpdateProject(); + useEffect(() => { + project.refetch(); + }, [currentTab]); if (project.isLoading) return ( <> @@ -37,82 +51,84 @@ const Project: React.FC & { Add: React.FC; Update: React.FC } = () => { if (project.isSuccess) { const data = project.data.data; return ( - <> - - {data.name} - - - - - - - {data.description} - - - - Members: - 1 - - - Total transactions: - {data.total_transactions} - - - Total cost: - {`$ ${data.total_cost.toFixed(4)}`} - - - Tags: - - - - - - - - - - - - Latest transactions + <Flex vertical> + <HeaderContainer height={123.5}> + <Flex vertical> + <Flex vertical justify="space-between"> + <Breadcrumb + className="ms-1" + items={[ + { + title: <Link to={'/projects'}>Projects</Link> + }, + { + title: data.name + } + ]} + /> + <Title level={1} className="h4 m-0"> + {data.name} - - } - classname={{ parent: 'my-5' }} - > - - - + setCurrentTab(activeKey)} + items={[ + { + key: '1', + label: 'Overview' + }, + { + key: '2', + label: 'AI Providers' + }, + { + key: '3', + label: 'Transactions' + } + ]} + /> + + + +
+ + {currentTab == '1' && ( + <> + + + + )} + {currentTab == '2' && ( + { + const updateData: Omit< + typeof data, + 'id' | 'total_cost' | 'total_transactions' + > = data; + updateProject.mutate({ + id: data.id, + data: { + ...updateData, + ai_providers: newProviders, + org_id: data.org_id || '' + } + }); + notification?.success({ + message: 'Changes saved!', + placement: 'topRight', + duration: 5 + }); + }} + /> + )} + {currentTab == '3' && } + +
+ ); } }; diff --git a/ui/src/pages/Project/ProjectDetails.tsx b/ui/src/pages/Project/ProjectDetails.tsx new file mode 100644 index 00000000..37f028ce --- /dev/null +++ b/ui/src/pages/Project/ProjectDetails.tsx @@ -0,0 +1,79 @@ +import { Button, Col, Flex, Row, Typography } from 'antd'; +import Container from '../../components/Container/Container'; +import { EditOutlined } from '@ant-design/icons'; +import { getProjectResponse } from '../../api/interfaces'; +import { TagsContainer } from '../../helpers/dataContainer'; +import { useNavigate } from 'react-router-dom'; +const { Title, Paragraph, Text } = Typography; + +interface Props { + details: getProjectResponse; +} + +const ProjectDetails: React.FC = ({ details }) => { + const navigate = useNavigate(); + return ( + <> + + + + Project details + + + + + +
+ Owner: + + + {details.owner} + + + + + Description: + + + {details.description} + + + + + Tags: + + + + + + + + + +
+ + Total transactions + + {details.total_transactions} +
+
+ + Total cost + + {details.total_cost.toFixed(4)} +
+
+
+ + ); +}; +export default ProjectDetails; diff --git a/ui/src/pages/Project/ProjectTransactions.tsx b/ui/src/pages/Project/ProjectTransactions.tsx new file mode 100644 index 00000000..27323dda --- /dev/null +++ b/ui/src/pages/Project/ProjectTransactions.tsx @@ -0,0 +1,37 @@ +import { useState } from 'react'; +import { getProjectResponse } from '../../api/interfaces'; +import { TransactionsFilters } from '../../api/types'; +import Container from '../../components/Container/Container'; +import FilterDates from '../../components/tables/filters/FilterDates'; +import TransactionsTable from '../../components/tables/AllTransactions/TransactionsTable'; +import { Flex, Typography } from 'antd'; + +const { Text } = Typography; +interface Props { + projectId: getProjectResponse['id']; +} + +const ProjectTransactions: React.FC = ({ projectId }) => { + const [filters, setFilters] = useState({ project_id: projectId }); + return ( + <> + + + Date: + console.log('setDates')} + /> + + + +
+ +
+
+ + ); +}; + +export default ProjectTransactions; diff --git a/ui/src/pages/Project/Statistics/Statistics.tsx b/ui/src/pages/Project/Statistics/Statistics.tsx index df8dac22..c32fb007 100644 --- a/ui/src/pages/Project/Statistics/Statistics.tsx +++ b/ui/src/pages/Project/Statistics/Statistics.tsx @@ -1,5 +1,5 @@ import { DatePicker, Flex, Select, Typography } from 'antd'; -import Container from '../Container'; +import Container from '../../../components/Container/Container'; import dayjs from 'dayjs'; import type { Dayjs } from 'dayjs'; import { StatisticsParams } from '../../../api/types'; @@ -26,7 +26,7 @@ export enum Period { const Statistics: React.FC = ({ projectId }) => { const [dates, setDates] = useState<{ start: Dayjs | null; end: Dayjs | null }>({ - start: dayjs().add(-30, 'd').startOf('day'), + start: dayjs().add(-1, 'w').startOf('day'), end: dayjs() }); const [granularity, setGranularity] = useState(Period.Daily); @@ -51,19 +51,19 @@ const Statistics: React.FC = ({ projectId }) => { options.push(Period.Monthly); break; case 2: // week - if (end.diff(start, 'w', true) >= 1 && end.diff(start, 'M', true) <= 6) + if (end.diff(start, 'w', true) >= 1 && end.diff(start, 'M', true) <= 12) options.push(Period.Weekly); break; case 3: // day - if (end.diff(start, 'd', true) >= 1 && end.diff(start, 'M', true) <= 1) + if (end.diff(start, 'd', true) >= 1 && end.diff(start, 'M', true) <= 2) options.push(Period.Daily); break; case 4: // hour - if (end.diff(start, 'h', true) >= 1 && end.diff(start, 'h', true) <= 24) + if (end.diff(start, 'h', true) >= 1 && end.diff(start, 'h', true) <= 60) options.push(Period.Hourly); break; case 5: // minutes - if (end.diff(start, 'h', true) <= 2) options.push(Period.Minutely); + if (end.diff(start, 'h', true) <= 5) options.push(Period.Minutely); break; default: break; @@ -99,92 +99,94 @@ const Statistics: React.FC = ({ projectId }) => { }; }); return ( - - - Statistics - -
- + + + Statistics + + + { + if (!rangeOKRef.current) { + let dateStart = ''; + let dateEnd = ''; + if (dates[0].length > 0 && dates[1].length > 0) { + dateStart = new Date(dates[0] + 'Z') + .toISOString() + .substring(0, 19); + dateEnd = new Date(dates[1] + 'Z') + .toISOString() + .substring(0, 19); } - ]} - onChange={(_, dates) => { - if (!rangeOKRef.current) { - let dateStart = ''; - let dateEnd = ''; - if (dates[0].length > 0 && dates[1].length > 0) { - dateStart = new Date(dates[0] + 'Z') - .toISOString() - .substring(0, 19); - dateEnd = new Date(dates[1] + 'Z') - .toISOString() - .substring(0, 19); - } - setDates({ - start: dateStart.length > 0 ? dayjs(dateStart) : null, - end: dateEnd.length > 0 ? dayjs(dateEnd) : null - }); - setGranularityAndUpdateApi(dayjs(dateStart), dayjs(dateEnd)); - } - rangeOKRef.current = false; - }} - onOk={(v) => { - setDates({ start: v[0], end: v[1] }); - setGranularityAndUpdateApi(v[0], v[1]); - rangeOKRef.current = true; - }} - value={[dates.start, dates.end]} - showTime - allowClear={false} - allowEmpty={false} - /> - { + setGranularity(val); + setStatisticsParams((old) => ({ ...old, period: val })); + }} + defaultValue={Period.Daily} + className="w-[100px]" + /> - } - classname={{ parent: 'mt-5', box: 'relative gap-5' }} - > - - - + +
+ +
+
+ +
+
+ +
); }; diff --git a/ui/src/pages/Project/Statistics/TransactionsCostAndTokensChart.tsx b/ui/src/pages/Project/Statistics/TransactionsCostAndTokensChart.tsx index 360be59a..d921b052 100644 --- a/ui/src/pages/Project/Statistics/TransactionsCostAndTokensChart.tsx +++ b/ui/src/pages/Project/Statistics/TransactionsCostAndTokensChart.tsx @@ -9,12 +9,13 @@ import { YAxis } from 'recharts'; import { useGetStatistics_TransactionsCost } from '../../../api/queries'; -import { Flex, Radio, Spin, Typography } from 'antd'; +import { Flex, Segmented, Spin, Typography } from 'antd'; import { StatisticsParams } from '../../../api/types'; import { useState } from 'react'; import { customSorter, dataRounding, dateFormatter } from './formatters'; import { schemeCategory10 as colors } from 'd3-scale-chromatic'; -const { Title, Paragraph } = Typography; +import * as styles from '../../../styles.json'; +const { Title } = Typography; interface Params { statisticsParams: StatisticsParams; } @@ -33,12 +34,12 @@ const TransactionsCostAndTokensChart: React.FC = ({ statisticsParams }) if (TransactionsCost.isSuccess) { const chartData = { legend: [] as string[], - records: [] as Record[] + records: [] as Record[] }; const data = TransactionsCost.data.data; data.map((el) => { const record: (typeof chartData.records)[0] = { - date: el.date + date: new Date(el.date).getTime() }; el.records.map((rec) => { @@ -55,54 +56,91 @@ const TransactionsCostAndTokensChart: React.FC = ({ statisticsParams }) }); return (
- - + + {TokensOrCost === 'tokens' ? 'Used tokens ' : 'Transactions cost '} by model - </Paragraph> - <Radio.Group - className="self-center" + + setTokensOrCost(e.target.value)} + onChange={(e: typeof TokensOrCost) => setTokensOrCost(e)} value={TokensOrCost} - optionType="button" + size="small" /> {data.length < 1 && ( - + <Title + level={3} + className="h1 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full text-center opacity-50 z-10 !m-0" + > No data found )} {data.length > 0 && ( <> - + - + { + const length = chartData.records.length; + const diff = + chartData.records[length - 1].date - + chartData.records[length - 2].date; + return [ + `dataMin - ${length % 2 == 0 ? 0 : diff}`, + `dataMax + ${diff}` + ]; + })()} + scale={'time'} tickFormatter={(v) => dateFormatter(v, statisticsParams.period)} - fontSize={12} - height={30} + tick={{ + fill: styles.Colors.light['Text/colorTextTertiary'], + fontWeight: 600 + }} + fontSize={14} + height={38} + stroke={styles.Colors.light['Border/colorBorder']} /> '$ ' + dataRounding(v, 4) : undefined } + domain={[ + 'auto', + `dataMax + ${(() => { + const length = chartData.records.length; + const record = chartData.records[length - 1]; + const keys = Object.keys(record).filter((el) => + el.includes(TokensOrCost) + ); + const values = keys.map((key) => record[key]); + const max = Math.max(...values); + return max / 8; + })()}` + ]} + tick={{ + fill: styles.Colors.light['Text/colorTextTertiary'] + }} + stroke={`${styles.Colors.light['Border/colorBorder']}`} /> = ({ statisticsParams }) } itemSorter={customSorter} /> - + ( + + {value} + + )} + /> {chartData.legend.map((el, id) => { return ( = ({ statisticsParams }) => { const data = TransactionsCount.data.data; return (
- + Transactions by response status - </Paragraph> + {data.length < 1 && ( - + <Title + level={3} + className="h1 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full text-center opacity-50 z-10 !m-0" + > No data found )} {data.length > 0 && ( - + - + dateFormatter(v, statisticsParams.period)} - fontSize={12} - height={30} + tick={{ + fill: styles.Colors.light['Text/colorTextTertiary'], + fontWeight: 600 + }} + fontSize={14} + height={38} + stroke={styles.Colors.light['Border/colorBorder']} + /> + - - + ( + + {value} + + )} + /> diff --git a/ui/src/pages/Project/Statistics/TransactionsSpeedChart.tsx b/ui/src/pages/Project/Statistics/TransactionsSpeedChart.tsx index 2dee5478..439f351f 100644 --- a/ui/src/pages/Project/Statistics/TransactionsSpeedChart.tsx +++ b/ui/src/pages/Project/Statistics/TransactionsSpeedChart.tsx @@ -17,7 +17,8 @@ import { schemeCategory10 as colors } from 'd3-scale-chromatic'; import { Segment } from 'recharts/types/cartesian/ReferenceLine'; import createTrend from 'trendline'; import React from 'react'; -const { Title, Paragraph } = Typography; +import * as styles from '../../../styles.json'; +const { Title } = Typography; interface Params { statisticsParams: StatisticsParams; } @@ -69,29 +70,35 @@ const TransactionsSpeedChart: React.FC = ({ statisticsParams }) => { }; return (
- + Response generation speed by model (tokens/s) - </Paragraph> + {data.length < 1 && ( - + <Title + level={3} + className="h1 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full text-center opacity-50 z-10 !m-0" + > No data found )} {data.length > 0 && ( <> - + - + { return new Date(val).toLocaleString('en-US', { @@ -102,17 +109,73 @@ const TransactionsSpeedChart: React.FC = ({ statisticsParams }) => { }} formatter={(v) => dataRounding(v as number, 2)} /> - - { + const length = chartData.records.length; + const diff = + chartData.records[length - 1].date - + chartData.records[length - 2].date; + return [ + `dataMin - ${length % 2 == 0 ? 0 : diff}`, + `dataMax + ${diff}` + ]; + })()} scale={'time'} - fontSize={12} tickFormatter={(v) => dateFormatter(v, statisticsParams.period)} + tick={{ + fill: styles.Colors.light['Text/colorTextTertiary'], + fontWeight: 600 + }} + fontSize={14} + height={38} + stroke={styles.Colors.light['Border/colorBorder']} + /> + { + // const recordsWithMoreThanOneParam = + // chartData.records.filter( + // (el) => Object.keys(el).length > 1 + // ); + // const max = Math.max( + // ...recordsWithMoreThanOneParam.map((obj) => { + // const keys = Object.keys(obj).filter( + // (key) => key !== 'date' + // ); + // return Math.max(...keys.map((el) => obj[el])); + // }) + // ); + // const a = Number(max + max / 3).toPrecision(2); + // console.log(a); + // return Number(a); + // })() + ]} + tick={{ + fill: styles.Colors.light['Text/colorTextTertiary'] + }} + stroke={`${styles.Colors.light['Border/colorBorder']}`} + /> + ( + + {value} + + )} /> - {chartData.legend.map((el, id) => { return ( diff --git a/ui/src/pages/Project/Update/UpdateProject.tsx b/ui/src/pages/Project/Update/UpdateProject.tsx index 1596c4e6..334af564 100644 --- a/ui/src/pages/Project/Update/UpdateProject.tsx +++ b/ui/src/pages/Project/Update/UpdateProject.tsx @@ -1,16 +1,24 @@ -import { useUpdateProject } from '../../../api/queries'; -import ProjectForm from '../../../components/ProjectForms/ProjectForm'; -import { FormikValues } from '../../../components/ProjectForms/types'; -import { useNavigate, useParams } from 'react-router-dom'; -import { Button, Typography } from 'antd'; +import { useGetProject, useUpdateProject } from '../../../api/queries'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import { Breadcrumb, Button, Flex, Spin, Typography } from 'antd'; +import { FormikValuesTemplate } from '../../../components/ProjectForms/types'; +import HeaderContainer from '../../../components/HeaderContainer/HeaderContainer'; +import { useFormik } from 'formik'; +import { projectSchema } from '../../../api/formSchemas'; +import ProjectDetails from '../../../components/ProjectForms/ProjectDetails'; +import { useContext, useEffect } from 'react'; +import { SaveOutlined } from '@ant-design/icons'; +import { Context } from '../../../context/Context'; const { Title } = Typography; const UpdateProject: React.FC = () => { + const { notification } = useContext(Context); const updateProject = useUpdateProject(); const projectId = useParams().projectId || ''; + const project = useGetProject(projectId); const navigate = useNavigate(); - const submit = async (values: typeof FormikValues) => { + const submit = async (values: typeof FormikValuesTemplate) => { updateProject .mutateAsync( { id: projectId, data: values }, @@ -21,19 +29,105 @@ const UpdateProject: React.FC = () => { } ) .then(() => { + notification?.success({ + message: 'Changes saved!', + placement: 'topRight', + duration: 5 + }); navigate(`/projects/${projectId}`); }); }; - return ( -
- - Update project - - - -
- ); + useEffect(() => { + if (project.isSuccess) { + const data = project.data.data; + const updateData: Omit = data; + formikDetails.setValues({ ...updateData, org_id: data.org_id || '' }); + } + }, [project.status]); + const formikDetails = useFormik({ + initialValues: { ...FormikValuesTemplate, owner: '__UPDATE__' }, + onSubmit: async (values) => { + submit(values); + }, + validationSchema: projectSchema, + validateOnChange: false + }); + if (project.isError) + return ( + <> +
An error has occurred {project.error.code}
+ {console.error(project.error)} + + ); + if (project.isLoading) + return ( + + ); + if (project.isSuccess) { + const data = project.data.data; + return ( + + + + Projects + }, + { + title: data.name + }, + { + title: 'Edit project details' + } + ]} + /> + + Edit project details + + + +
+ + + + + +
+
+ ); + } }; export default UpdateProject; diff --git a/ui/src/pages/Signin/AzureBtn.tsx b/ui/src/pages/Signin/AzureBtn.tsx index 3497e60a..b04367a3 100644 --- a/ui/src/pages/Signin/AzureBtn.tsx +++ b/ui/src/pages/Signin/AzureBtn.tsx @@ -2,18 +2,19 @@ import { WindowsFilled } from '@ant-design/icons'; import { PublicClientApplication } from '@azure/msal-browser'; import { Button } from 'antd'; const { CLIENT_ID, SCOPES, AUTHORITY } = SSO_AZURE; -const msalInstance = new PublicClientApplication({ - auth: { - clientId: CLIENT_ID, - redirectUri: window.location.href, - authority: AUTHORITY - }, - cache: { - cacheLocation: 'sessionStorage', - storeAuthStateInCookie: true - } -}); + const AzureBtn: React.FC<{ onOk: (arg: string) => void }> = ({ onOk }) => { + const msalInstance = new PublicClientApplication({ + auth: { + clientId: CLIENT_ID, + redirectUri: window.location.href, + authority: AUTHORITY + }, + cache: { + cacheLocation: 'sessionStorage', + storeAuthStateInCookie: true + } + }); const handleClick = async () => { try { await msalInstance.initialize(); @@ -31,7 +32,7 @@ const AzureBtn: React.FC<{ onOk: (arg: string) => void }> = ({ onOk }) => { + /> ); }; export default GoogleBtn; diff --git a/ui/src/pages/Signin/Signin.tsx b/ui/src/pages/Signin/Signin.tsx index 70347713..673b90f6 100644 --- a/ui/src/pages/Signin/Signin.tsx +++ b/ui/src/pages/Signin/Signin.tsx @@ -1,19 +1,33 @@ -import { SetStateAction, useEffect, useState } from 'react'; +import { SetStateAction, useCallback, useEffect, useState } from 'react'; import { checkLogin } from '../../storage/login'; import Logo from '../../assets/logo/symbol-teal.svg'; -import { Button, Flex, Space, Spin, Typography } from 'antd'; +import { Button, Divider, Flex, Space, Spin, Typography } from 'antd'; import GoogleBtn from './GoogleBtn'; import AzureBtn from './AzureBtn'; import api from '../../api/api'; import { GoogleOAuthProvider } from '@react-oauth/google'; import { useGetConfig } from '../../api/queries'; +import Container from '../../components/Container/Container'; +import { ArrowRightOutlined } from '@ant-design/icons'; -const { Title, Paragraph } = Typography; +// symbol from ../../assets/logo/symbol-teal-outline.svg +const symbol = ( + + + +); + +const { Title, Paragraph, Text } = Typography; const Signin: React.FC<{ setLoginState: (arg: SetStateAction) => void }> = ({ setLoginState }) => { const [token, setToken] = useState(null); const config = useGetConfig(); + const [googleBtnWidth, setGoogleBtnWidth] = useState(300); useEffect(() => { if (token !== null) { localStorage.setItem('PS_TOKEN', token); @@ -26,6 +40,9 @@ const Signin: React.FC<{ setLoginState: (arg: SetStateAction) => void } }); } }, [token]); + const setWidth = useCallback((node: HTMLDivElement) => { + if (node !== null) setGoogleBtnWidth(node.clientWidth); + }, []); if (config.isError) return ( <> @@ -43,34 +60,83 @@ const Signin: React.FC<{ setLoginState: (arg: SetStateAction) => void } if (config.isSuccess) { const { authorization, organization, azure_auth, google_auth } = config.data.data; return ( - - - - Welcome to {organization} - - {!authorization && ( - - )} - {authorization && ( - <> - - Log in to continue - - {google_auth && ( - - - - )} - {azure_auth && } - - )} - - - Prompt Sail - - + + + +
+
+ +
+ {symbol} +
+
+ {symbol} +
+ + Smooth sailing for your LLM operations + +
+
+
+ + + + + Prompt Sail + + + + + {authorization ? 'Log in' : 'Welcome'} to {organization} + + + Unlock cost control, security, and optimization for your large + language model interactions. + + + + {!authorization && ( + + )} + {authorization && ( + <> + {google_auth && ( + + + + )} + {google_auth && azure_auth && ( + or + )} + {azure_auth && } + + )} + + +
+
); } diff --git a/ui/src/pages/Transaction/BasicInfo.tsx b/ui/src/pages/Transaction/BasicInfo.tsx deleted file mode 100644 index 403158f0..00000000 --- a/ui/src/pages/Transaction/BasicInfo.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Collapse, Flex, theme } from 'antd'; -import { getTransactionResponse } from '../../api/interfaces'; -import Container from '../Project/Container'; -import { CaretRightOutlined } from '@ant-design/icons'; - -interface Props { - data: getTransactionResponse; -} - -const BasicInfo: React.FC = ({ data }) => { - const { token } = theme.useToken(); - - const collapseItems = data.messages?.map((el, id) => ({ - key: id, - label: el.role, - children:

{el.content}

, - style: { - background: token.colorFillAlter, - borderRadius: token.borderRadiusLG, - border: 'none' - } - })) || [ - { - key: 0, - label: data.error_message, - children: '', - style: { - background: token.colorFillAlter, - borderRadius: token.borderRadiusLG, - border: 'none' - } - } - ]; - return ( - - - } - items={collapseItems} - /> - - - ); -}; -export default BasicInfo; diff --git a/ui/src/pages/Transaction/Details.tsx b/ui/src/pages/Transaction/Details.tsx deleted file mode 100644 index 512db066..00000000 --- a/ui/src/pages/Transaction/Details.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import SyntaxHighlighter from 'react-syntax-highlighter'; -import { getTransactionResponse } from '../../api/interfaces'; -import * as styles from 'react-syntax-highlighter/dist/esm/styles/hljs'; -import { Typography } from 'antd'; - -const { Title } = Typography; -interface Props { - data: getTransactionResponse; -} - -const Details: React.FC = ({ data }) => { - return ( - <> - Request - - {JSON.stringify(data.request, null, 4)} - - Response - - {JSON.stringify(data.response, null, 4)} - - - {`request_time: ${JSON.stringify(data.request_time, null, 4)}, -response_time: ${JSON.stringify(data.response_time, null, 4)}, -status_code: ${JSON.stringify(data.response.status_code, null, 4)}, -processing_time: ${JSON.stringify(data.response.elapsed, null, 4)} - `} - - - ); -}; - -export default Details; diff --git a/ui/src/pages/Transaction/JSONformat.tsx b/ui/src/pages/Transaction/JSONformat.tsx new file mode 100644 index 00000000..cae400b4 --- /dev/null +++ b/ui/src/pages/Transaction/JSONformat.tsx @@ -0,0 +1,78 @@ +import { LightAsync } from 'react-syntax-highlighter'; +import { getTransactionResponse } from '../../api/interfaces'; +import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs'; +import { Collapse, CollapseProps, Typography, theme } from 'antd'; +import Container from '../../components/Container/Container'; + +const { Title } = Typography; +interface Props { + data: getTransactionResponse; +} +const JSONformat: React.FC = ({ data }) => { + const { token } = theme.useToken(); + const itemStyle = { + background: token.colorBgContainer, + // @ts-expect-error token.Collapse.colorBorder is correctly defined in /ui/src/theme-light.tsx + border: `1px solid ${token.Collapse.colorBorder}`, + borderRadius: '8px' + }; + const items: CollapseProps['items'] = [ + { + key: 0, + label: ( + + Request + + ), + style: itemStyle, + children: ( + + {JSON.stringify(data.request, null, 4)} + + ) + }, + { + key: 1, + label: ( + + Response + + ), + style: itemStyle, + children: ( + + {JSON.stringify(data.response, null, 4)} + + ) + } + ]; + return ( + <> + +
+ + {`request_time: ${JSON.stringify(data.request_time, null, 4)}, +response_time: ${JSON.stringify(data.response_time, null, 4)}, +status_code: ${JSON.stringify(data.response.status_code, null, 4)}, +processing_time: ${JSON.stringify(data.response.elapsed, null, 4)}`} + +
+
+ + + ); +}; + +export default JSONformat; diff --git a/ui/src/pages/Transaction/Messages.tsx b/ui/src/pages/Transaction/Messages.tsx new file mode 100644 index 00000000..631b4fc9 --- /dev/null +++ b/ui/src/pages/Transaction/Messages.tsx @@ -0,0 +1,39 @@ +import { Collapse, Typography } from 'antd'; +import { getTransactionResponse } from '../../api/interfaces'; +import Container from '../../components/Container/Container'; +import { DownOutlined } from '@ant-design/icons'; + +const { Title, Text } = Typography; + +interface Props { + data: getTransactionResponse; +} + +const Messages: React.FC = ({ data }) => { + const collapseItems = data.messages?.map((el, id) => ({ + key: id, + label: el.role, + children: {el.content} + })) || [ + { + key: 0, + label: data.error_message, + children: '' + } + ]; + return ( + + + Messages details + +
+ } + items={collapseItems} + /> +
+
+ ); +}; +export default Messages; diff --git a/ui/src/pages/Transaction/Overview.tsx b/ui/src/pages/Transaction/Overview.tsx new file mode 100644 index 00000000..73b97ccf --- /dev/null +++ b/ui/src/pages/Transaction/Overview.tsx @@ -0,0 +1,148 @@ +import { Badge, Col, Flex, Row, Typography } from 'antd'; +import Container from '../../components/Container/Container'; +import { getTransactionResponse } from '../../api/interfaces'; +import React from 'react'; +import { TagsContainer } from '../../helpers/dataContainer'; +import { ArrowRightOutlined } from '@ant-design/icons'; +import { useNavigate } from 'react-router-dom'; +const { Title, Text, Link } = Typography; + +interface Props { + data: getTransactionResponse; +} + +const Overview: React.FC = ({ data }) => { + const toLocalDate = (date: string) => { + const local = new Date(date + 'Z'); + return `${local.toLocaleDateString()} ${local.toLocaleTimeString()}`; + }; + const navigate = useNavigate(); + const labelWidth = '119px'; + return ( + + + Transaction details + + + +
+ Project: + + + { + navigate('/projects/' + data.project_id); + }} + > + {data.project_name} + + + + + + API base: + + + {data.request.url} + + + + + Request time: + + + {toLocalDate(data.request_time)} + + + + + Response status: + + + = 300 + ? data.status_code >= 400 + ? 'error' + : 'warning' + : 'success' + } + text={data.status_code} + /> + + + + + Model: + + + {data.model} + + + + + Response time: + + + {toLocalDate(data.response_time)} + + + + + Tags: + + + + + + + + Cost: + + + + {data.status_code < 300 && data.total_cost !== null + ? `$ ${data.total_cost.toFixed(4)}` + : 'null'} + + + + + + Speed: + + + + {data.status_code < 300 && data.generation_speed !== null + ? data.generation_speed.toFixed(4) + : 'null'} + + + + + + Tokens: + + + + {data.status_code < 300 ? ( + + {data.input_tokens} {data.output_tokens}{' '} + (Σ{' '} + {data.input_tokens !== null && data.output_tokens !== null + ? data.input_tokens + data.output_tokens + : 'null'} + ) + + ) : ( + null + )} + + + + + + ); +}; + +export default Overview; diff --git a/ui/src/pages/Transaction/Transaction.tsx b/ui/src/pages/Transaction/Transaction.tsx index 6d829819..fd0e9ce7 100644 --- a/ui/src/pages/Transaction/Transaction.tsx +++ b/ui/src/pages/Transaction/Transaction.tsx @@ -1,41 +1,25 @@ import { Link, useNavigate, useParams } from 'react-router-dom'; import { useGetTransaction } from '../../api/queries'; -import { - Badge, - Descriptions, - DescriptionsProps, - Flex, - Space, - Tabs, - TabsProps, - Tooltip, - Typography -} from 'antd'; -import BasicInfo from './BasicInfo'; -import Details from './Details'; -import { transactionTabOnLoad } from '../../storage/transactionsDetails'; -import { TagsContainer } from '../../helpers/dataContainer'; -import { ArrowRightOutlined } from '@ant-design/icons'; -import Container from '../Project/Container'; +import { Breadcrumb, Flex, Spin, Tabs, Typography } from 'antd'; +import HeaderContainer from '../../components/HeaderContainer/HeaderContainer'; +import { useState } from 'react'; +import Overview from './Overview'; +import Messages from './Messages'; +import JSONformat from './JSONformat'; const { Title } = Typography; const Transaction: React.FC = () => { const params = useParams(); const navigate = useNavigate(); const transaction = useGetTransaction(params.transactionId || ''); - const onChange = (key: string) => { - localStorage.setItem('transactionDetailsTab', key); - }; - const toLocalDate = (date: string) => { - const local = new Date(date + 'Z'); - return `${local.toLocaleDateString()} ${local.toLocaleTimeString()}`; - }; + const [currentTab, setCurrentTab] = useState('1'); if (transaction) if (transaction.isLoading) return ( - <> -
loading...
- + ); if (transaction.isError) return ( @@ -47,91 +31,56 @@ const Transaction: React.FC = () => { ); if (transaction.isSuccess) { const data = transaction.data.data; - const descItems: DescriptionsProps['items'] = [ - { - label: 'Project', - children: {data.project_name} - }, - { - label: 'Model', - children: data.model - }, - { - label: 'Cost', - children: data.total_cost !== null ? `$ ${data.total_cost.toFixed(4)}` : 'null' - }, - { - label: 'Api base', - children: data.request.url, - span: 3 - }, - { - label: 'Request time', - children: toLocalDate(data.request_time) - }, - { - label: 'Response time', - children: toLocalDate(data.response_time) - }, - { - label: ( - - Speed - - ), - children: data.generation_speed.toFixed(3) - }, - { - label: 'Response status', - children: ( - = 300 - ? data.status_code >= 400 - ? 'error' - : 'warning' - : 'success' - } - text={data.status_code} - /> - ) - }, - { - label: 'Tags', - children: - }, - { - label: 'Tokens', - children: ( - - {data.input_tokens} {data.output_tokens} (Σ{' '} - {data.response.content.usage.total_tokens}) - - ) - } - ]; - const items: TabsProps['items'] = [ - { - key: 'basic', - label: 'Basic', - children: - }, - { - key: 'details', - label: 'Details', - children:
- } - ]; return ( - - - Transaction {data.id} - - - - - - + + + + + Transactions + }, + { + title: data.id + } + ]} + /> + + {data.id} + + + setCurrentTab(activeKey)} + items={[ + { + key: '1', + label: 'Overview' + }, + { + key: '2', + label: 'Messages' + }, + { + key: '3', + label: 'JSON' + } + ]} + /> + + +
+ + <> + {currentTab == '1' && } + {currentTab == '2' && } + {currentTab == '3' && } + +
+
); } }; diff --git a/ui/src/pages/Transactions.tsx b/ui/src/pages/Transactions.tsx index 5f25b5f0..0868a60f 100644 --- a/ui/src/pages/Transactions.tsx +++ b/ui/src/pages/Transactions.tsx @@ -1,9 +1,15 @@ -import { Typography, Flex } from 'antd'; -import { useSearchParams } from 'react-router-dom'; +import { Typography, Flex, Spin } from 'antd'; +// import { useSearchParams } from 'react-router-dom'; import { TransactionsFilters } from '../api/types'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import TransactionsTable from '../components/tables/AllTransactions/TransactionsTable'; -import TableFilters from '../components/tables/AllTransactions/TableFilters'; +import HeaderContainer from '../components/HeaderContainer/HeaderContainer'; +import { useGetAllTransactions } from '../api/queries'; +import Container from '../components/Container/Container'; +import FilterDates from '../components/tables/filters/FilterDates'; +import { useSearchParams } from 'react-router-dom'; + +const { Text } = Typography; const { Title } = Typography; const Transactions = () => { @@ -13,6 +19,9 @@ const Transactions = () => { tags: params.get('tags') || '', date_from: params.get('date_from') || '', date_to: params.get('date_to') || '', + models: params.get('models') || '', + status_codes: params.get('status_codes') || '', + providers: params.get('providers') || '', page_size: params.get('page_size') || '10', page: params.get('page') || '1' }); @@ -25,20 +34,55 @@ const Transactions = () => { } setParams(newParam); }; + useEffect(() => { + setURLParam(filters); + }, [filters]); + const transactions = useGetAllTransactions(filters); + return ( - <> - - Transactions - - - - + + + {transactions.isLoading && ( + + )} + {!transactions.isLoading && ( +
+ + Transactions{' '} + {transactions.isSuccess ? ( + `(${transactions.data.data.total_elements})` + ) : ( + <> + {console.error(transactions.error)} + {console.error(transactions.error?.message)} + <span>(An error has occurred {transactions.error?.code})</span> + </> + )} + +
+ )} +
+ + + + Date: + console.log('setDates')} + /> + + + +
+ +
+
- +
); }; export default Transactions; diff --git a/ui/src/styles.json b/ui/src/styles.json new file mode 100644 index 00000000..c6ab190a --- /dev/null +++ b/ui/src/styles.json @@ -0,0 +1,363 @@ +{ + "global": { + "Size/sizeXXS":4, + "Size/sizeXS":8, + "Size/sizeSM":12, + "Size/size":16, + "Size/sizeMS":16, + "Size/sizeMD":20, + "Size/sizeLG":24, + "Size/sizeXL":32, + "Size/sizeXXL":48, + "Space/Padding/padding":16, + "Space/Padding/paddingXXS":4, + "Space/Padding/paddingXS":8, + "Space/Padding/paddingSM":12, + "Space/Number":0, + "Space/Padding/paddingMD":20, + "Space/Padding/paddingLG":24, + "Space/Padding/paddingXL":32, + "Space/Padding/paddingXXL":48, + "Height/controlHeight":32, + "Height/controlHeightXS":16, + "Height/controlHeightSM":24, + "Height/controlHeightLG":40, + "Space/Margin/margin":16, + "Space/Margin/marginXXS":4, + "Space/Margin/marginXS":8, + "Space/Margin/marginSM":12, + "Space/Margin/marginLG":24, + "Space/Margin/marginXL":32, + "Space/Margin/marginXXL":48, + "BorderRadius/borderRadius":6, + "BorderRadius/borderRadiusLG":8, + "BorderRadius/borderRadiusSM":4, + "BorderRadius/borderRadiusXS":2, + "BorderRadius/borderRadius0":0, + "BorderRadius/borderRadius16":16, + "BorderRadius/borderRadius24":24, + "BorderRadius/borderRadius32":3, + "Typography": { + "Code":{ + "fontSize": 12, + "fontFamily": "Consolas", + "fontWeight": "Regular", + "lineHeight": 1.33333, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/Normal":{ + "fontSize": 14, + "fontFamily": "Roboto Flex", + "fontWeight": "Regular", + "lineHeight": 1.57142, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/LG":{ + "fontSize": 16, + "fontFamily": "Roboto Flex", + "fontWeight": "Regular", + "lineHeight": 1.5, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/XL":{ + "fontSize": 20, + "fontFamily": "Roboto Flex", + "fontWeight": "SemiBold", + "lineHeight": 1.4, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/H1":{ + "fontSize": 38, + "fontFamily": "Roboto Flex", + "fontWeight": "SemiBold", + "lineHeight": 1.21053, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/H2":{ + "fontSize": 30, + "fontFamily": "Roboto Flex", + "fontWeight": "SemiBold", + "lineHeight": 1.26667, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/H3":{ + "fontSize": 24, + "fontFamily": "Roboto Flex", + "fontWeight": "SemiBold", + "lineHeight": 1.33333, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/H4":{ + "fontSize": 20, + "fontFamily": "Roboto Flex", + "fontWeight": "SemiBold", + "lineHeight": 1.40, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/H5":{ + "fontSize": 16, + "fontFamily": "Roboto Flex", + "fontWeight": "SemiBold", + "lineHeight": 1.5, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + }, + "Desktop/Caption":{ + "fontSize": 12, + "fontFamily": "Roboto Flex", + "fontWeight": "Medium", + "lineHeight": 1.66666, + "letterSpacing": 0, + "letterSpacingUnit": "PERCENT", + "textCase": "ORIGINAL", + "textDecoration": "NONE" + } + }, + "Effects":{ + "boxShadow":{ + "box-shadow": "0 2px 4px 0 rgba(0,0,0,0.02)", + "box-shadow2": "0 1px 6px -1px rgba(0,0,0,0.02)", + "box-shadow3": "0 1px 2px 0 rgba(0,0,0,0.03)" + }, + "boxShadowSecondary":{ + "box-shadow": "0 9px 28px 8px rgba(0,0,0,0.05)", + "box-shadow2": "0 3px 6px -4px rgba(0,0,0,0.12)", + "box-shadow3": "0 6px 16px 0 rgba(0,0,0,0.08)" + }, + "FocusPrimary":{ + "box-shadow": "0 0 0 2px rgba(48,124,120,0.2)" + }, + "FocusYellow":{ + "box-shadow": "0 0 0 2px rgba(237,178,26,0.2)" + }, + "FocusRed":{ + "box-shadow": "0 0 0 2px rgba(199,70,53,0.2)" + } + }, + "Grids": { + "Grid-24": { + "pattern": "COLUMNS", + "color": { + "r": 255, + "g": 0, + "b": 0, + "a": 0.1 + }, + "alignment": "STRETCH", + "gutterSize": 24, + "offset": 24, + "count": 24 + }, + "Grid-16": { + "pattern": "COLUMNS", + "color": { + "r": 255, + "g": 0, + "b": 0, + "a": 0.1 + }, + "alignment": "STRETCH", + "gutterSize": 24, + "offset": 24, + "count": 16 + }, + "Grid-12":{ + "pattern": "COLUMNS", + "color": { + "r": 255, + "g": 0, + "b": 0, + "a": 0.1 + }, + "alignment": "STRETCH", + "gutterSize": 24, + "offset": 24, + "count": 12 + } + } + }, + "Colors": { + "light": { + "Text/colorTextLight": "#FFFFFF", + "Text/colorTextDisabled": "#0B1D1C8C", + "Text/colorTextPlaceholder": "#0B1D1C8C", + "Text/colorTextDescription": "#0B1D1CA6", + "Text/colorTextHeading": "#0B1D1CE0", + "Text/colorTextQuaternary": "#0B1D1C8C", + "Text/colorTextTertiary": "#0B1D1CA6", + "Text/colorTextSecondary": "#0B1D1CBF", + "Text/colorText": "#0B1D1CE0", + "Text/colorTextBase": "#0B1D1C", + "Background/colorBgBase": "#FFFFFF", + "Background/colorBgContainer": "#FFFFFF", + "Background/colorBgElevated": "#FFFFFF", + "Background/colorBgLayout": "#F5F5F5", + "Background/colorBgSpotlight": "#0B1D1CD9", + "Background/colorBgMask": "#0B1D1C73", + "Background/colorBgTextHover": "#0B1D1C0F", + "Background/colorBgTextActive": "#0B1D1C26", + "Background/colorBgContainerDisabled": "#0B1D1C0A", + "Fill/colorFill": "#0B1D1C26", + "Fill/colorFillSecondary": "#0B1D1C0F", + "Fill/colorFillTertiary": "#0B1D1C0A", + "Fill/colorFillQuaternary": "#0B1D1C05", + "Fill/colorFillContent": "#0B1D1C0F", + "Fill/colorFillAlter": "#0B1D1C05", + "Border/colorBorder": "#D9D9D9", + "Border/colorBorderSecondary": "#F0F0F0", + "Border/colorSplit": "#F0F0F0", + "ControlItem/controlItemBgHover": "#0B1D1C0A", + "ControlItem/controlItemBgActive": "#EFF9F8", + "ControlItem/controlItemBgActiveHover": "#DCECEB", + "Primary/colorPrimaryTextActive": "#1D4A48", + "Primary/colorPrimaryText": "#307C78", + "Primary/colorPrimaryTextHover": "#266360", + "Primary/colorPrimaryActive": "#1D4A48", + "Primary/colorPrimary": "#307C78", + "Primary/colorPrimaryHover": "#266360", + "Primary/colorPrimaryBorderHover": "#A3C7C5", + "Primary/colorPrimaryBorder": "#B6D3D2", + "Primary/colorPrimaryBgHover": "#DCECEB", + "Primary/colorPrimaryBg": "#EFF9F8", + "Success/colorSuccessBg": "#FCFFFC", + "Success/colorSuccessBgHover": "#E6F3E5", + "Success/colorSuccessBorder": "#BADBB8", + "Success/colorSuccessBorderHover": "#A4CFA1", + "Success/colorSuccessHover": "#1A6D13", + "Success/colorSuccess": "#218818", + "Success/colorSuccessActive": "#14520E", + "Success/colorSuccessTextHover": "#1A6D13", + "Success/colorSuccessText": "#218818", + "Success/colorSuccessTextActive": "#14520E", + "Warning/colorWarningBg": "#FEFDF8", + "Warning/colorWarningBgHover": "#FCF5E2", + "Warning/colorWarningBorder": "#F9E6B5", + "Warning/colorWarningBorderHover": "#F7DF9F", + "Warning/colorWarningHover": "#C29215", + "Warning/colorWarning": "#EDB21A", + "Warning/colorWarningActive": "#977110", + "Warning/colorWarningTextHover": "#C29215", + "Warning/colorWarningText": "#EDB21A", + "Warning/colorWarningTextActive": "#977110", + "Error/colorErrorBg": "#FDF9F9", + "Error/colorErrorBgHover": "#F8E7E5", + "Error/colorErrorBorder": "#EDC3BE", + "Error/colorErrorBorderHover": "#E7B1AB", + "Error/colorErrorHover": "#A0382A", + "Error/colorError": "#C74635", + "Error/colorErrorActive": "#782A20", + "Error/colorErrorTextHover": "#A0382A", + "Error/colorErrorText": "#C74635", + "Error/colorErrorTextActive": "#782A20", + "colorLink": "#307C78", + "colorLinkHover": "#266360", + "colorLinkActive": "#1D4A48", + "colorIcon": "#0B1D1C8C", + "colorIconHover": "#0B1D1CE0" + }, + "dark": { + "Text/colorTextLight": "#FFFFFF", + "Text/colorTextDisabled": ["#FFFFFF40", "#FFFFFF8C"], + "Text/colorTextPlaceholder": ["#FFFFFF40","#FFFFFF8C"], + "Text/colorTextDescription": "#FFFFFFA6", + "Text/colorTextHeading": "#FFFFFFD9", + "Text/colorTextQuaternary": "#FFFFFF8C", + "Text/colorTextTertiary": "#FFFFFF73", + "Text/colorTextSecondary": "#FFFFFFA6", + "Text/colorText": "#FFFFFFD9", + "Text/colorTextBase": "#FFFFFF", + "Background/colorBgBase": "#131316", + "Background/colorBgContainer": ["#26262C","#0B1D1C"], + "Background/colorBgElevated": "#2F2F37", + "Background/colorBgLayout": "#131316", + "Background/colorBgSpotlight": "#50505D", + "Background/colorBgMask": "#00000073", + "Background/colorBgTextHover": "#FFFFFF0F", + "Background/colorBgTextActive": "#FFFFFF26", + "Background/colorBgContainerDisabled": "#FFFFFF0A", + "Fill/colorFill": "#FFFFFF26", + "Fill/colorFillSecondary": "#FFFFFF0F", + "Fill/colorFillTertiary": "#FFFFFF0A", + "Fill/colorFillQuaternary": "#FFFFFF05", + "Fill/colorFillContent": "#FFFFFF0F", + "Fill/colorFillAlter": "#FFFFFF05", + "Border/colorBorder": ["#50505D", "#1D4A48"], + "Border/colorBorderSecondary": ["#40404A","#133230"], + "Border/colorSplit": "#40404A", + "ControlItem/controlItemBgHover": "#0000000A", + "ControlItem/controlItemBgActive": "#16122C", + "ControlItem/controlItemBgActiveHover": "#1D1545", + "Primary/colorPrimaryTextActive": "#1554AD", + "Primary/colorPrimaryText": "#1668DC", + "Primary/colorPrimaryTextHover": "#3C89E8", + "Primary/colorPrimaryActive": "#1554AD", + "Primary/colorPrimary": ["#1668DC", "#569592"], + "Primary/colorPrimaryHover": "#3C89E8", + "Primary/colorPrimaryBorderHover": "#15417E", + "Primary/colorPrimaryBorder": "#15325B", + "Primary/colorPrimaryBgHover": "#112545", + "Primary/colorPrimaryBg": "#111A2C", + "Success/colorSuccessBg": "Colors/green/1", + "Success/colorSuccessBgHover": "Colors/green/2", + "Success/colorSuccessBorder": "Colors/green/3", + "Success/colorSuccessBorderHover": "Colors/green/4", + "Success/colorSuccessHover": "Colors/green/5", + "Success/colorSuccess": "Colors/green/6", + "Success/colorSuccessActive": "Colors/green/7", + "Success/colorSuccessTextHover": "Colors/green/8", + "Success/colorSuccessText": "Colors/green/9", + "Success/colorSuccessTextActive": "Colors/green/10", + "Warning/colorWarningBg": "Colors/gold/1", + "Warning/colorWarningBgHover": "Colors/gold/2", + "Warning/colorWarningBorder": "Colors/gold/3", + "Warning/colorWarningBorderHover": "Colors/gold/4", + "Warning/colorWarningHover": "Colors/gold/5", + "Warning/colorWarning": "Colors/gold/6", + "Warning/colorWarningActive": "Colors/gold/7", + "Warning/colorWarningTextHover": "Colors/gold/8", + "Warning/colorWarningText": "Colors/gold/9", + "Warning/colorWarningTextActive": "Colors/gold/10", + "Error/colorErrorBg": "Colors/red/1", + "Error/colorErrorBgHover": "Colors/red/2", + "Error/colorErrorBorder": "Colors/red/3", + "Error/colorErrorBorderHover": "Colors/red/4", + "Error/colorErrorHover": "Colors/red/5", + "Error/colorError": "#D26A5C", + "Error/colorErrorActive": "Colors/red/7", + "Error/colorErrorTextHover": "Colors/red/8", + "Error/colorErrorText": "Colors/red/9", + "Error/colorErrorTextActive": "Colors/red/10", + "colorLink": "#1668DC", + "colorLinkHover": "#15417E", + "colorLinkActive": "#1554AD", + "colorIcon": ["#FFFFFF73","#FFFFFF8C"], + "colorIconHover": "#FFFFFFD9" + } + } +} \ No newline at end of file diff --git a/ui/src/theme-dark.tsx b/ui/src/theme-dark.tsx new file mode 100644 index 00000000..61db16e6 --- /dev/null +++ b/ui/src/theme-dark.tsx @@ -0,0 +1,64 @@ +import { ThemeConfig } from 'antd'; + +const theme: ThemeConfig = { + token: { + wireframe: false, + colorPrimaryBg: '#0e2524', + colorPrimaryBgHover: '#133230', + colorPrimaryBorder: '#1d4a48', + colorPrimaryBorderHover: '#266360', + colorPrimaryHover: '#a3c7c5', + colorPrimaryActive: '#b6d3d2', + colorPrimaryTextHover: '#a3c7c5', + colorPrimaryText: '#569592', + colorPrimary: '#569592', + colorInfo: '#569592', + colorPrimaryTextActive: '#b6d3d2', + colorSuccessBg: '#0a2907', + colorSuccessBgHover: '#0d360a', + colorSuccessBorder: '#14520e', + colorSuccessBorderHover: '#1a6d13', + colorSuccessHover: '#a4cfa1', + colorSuccess: '#4da046', + colorSuccessActive: '#badbb8', + colorSuccessTextHover: '#a4cfa1', + colorSuccessText: '#4da046', + colorSuccessTextActive: '#badbb8', + colorWarningBg: '#201803', + colorWarningBgHover: '#302405', + colorWarningBorder: '#574109', + colorWarningBorderHover: '#6c510c', + colorWarningHover: '#f7df9f', + colorWarning: '#f0c146', + colorWarningActive: '#f9e6b5', + colorWarningTextHover: '#f7df9f', + colorWarningText: '#f0c146', + colorWarningTextActive: '#f9e6b5', + colorErrorBg: '#230c09', + colorErrorBgHover: '#3d1610', + colorErrorBorder: '#782a20', + colorErrorBorderHover: '#a0382a', + colorErrorHover: '#e7b1ab', + colorError: '#d26a5c', + colorErrorActive: '#edc3be', + colorErrorTextHover: '#e7b1ab', + colorErrorText: '#d26a5c', + colorErrorTextActive: '#edc3be', + colorLink: '#569592', + colorLinkHover: '#a3c7c5', + colorLinkActive: '#b6d3d2', + colorBgBase: '#040c0b', + colorTextSecondary: '#ffffffbf', + colorTextTertiary: '#ffffffa6', + colorTextQuaternary: '#ffffff8c', + colorBorder: '#1d4a48', + colorBorderSecondary: '#133230', + colorBgContainer: '#0b1d1c', + colorBgElevated: '#133230', + colorBgLayout: '#1d4a48', + colorBgSpotlight: '#266360', + colorBgMask: '#040c0b73' + } +}; + +export default theme; diff --git a/ui/src/theme-light.tsx b/ui/src/theme-light.tsx new file mode 100644 index 00000000..e241b4d2 --- /dev/null +++ b/ui/src/theme-light.tsx @@ -0,0 +1,116 @@ +import { ThemeConfig } from 'antd'; +import styles from './styles.json'; +const theme: ThemeConfig = { + token: { + wireframe: false, + colorTextTertiary: styles.Colors.light['Text/colorTextTertiary'], + colorTextQuaternary: styles.Colors.light['Text/colorTextQuaternary'], + colorPrimaryBg: styles.Colors.light['Primary/colorPrimaryBg'], + colorPrimaryBgHover: styles.Colors.light['Primary/colorPrimaryBgHover'], + colorPrimaryBorder: styles.Colors.light['Primary/colorPrimaryBorder'], + colorPrimaryBorderHover: styles.Colors.light['Primary/colorPrimaryBorderHover'], + colorPrimaryHover: styles.Colors.light['Primary/colorPrimaryHover'], + colorPrimary: styles.Colors.light['Primary/colorPrimary'], + colorInfo: styles.Colors.light['Primary/colorPrimary'], + colorPrimaryActive: styles.Colors.light['Primary/colorPrimaryActive'], + colorPrimaryTextHover: styles.Colors.light['Primary/colorPrimaryTextHover'], + colorPrimaryText: styles.Colors.light['Primary/colorPrimaryText'], + colorPrimaryTextActive: styles.Colors.light['Primary/colorPrimaryTextActive'], + colorSuccessBg: styles.Colors.light['Success/colorSuccessBg'], + colorSuccessBgHover: styles.Colors.light['Success/colorSuccessBgHover'], + colorSuccessBorder: styles.Colors.light['Success/colorSuccessBorder'], + colorSuccessBorderHover: styles.Colors.light['Success/colorSuccessBorderHover'], + colorSuccessHover: styles.Colors.light['Success/colorSuccessHover'], + colorSuccessActive: styles.Colors.light['Success/colorSuccessActive'], + colorSuccessTextHover: styles.Colors.light['Success/colorSuccessTextHover'], + colorSuccessTextActive: styles.Colors.light['Success/colorSuccessTextActive'], + colorSuccessText: styles.Colors.light['Success/colorSuccessText'], + colorSuccess: styles.Colors.light['Success/colorSuccess'], + colorWarningBg: styles.Colors.light['Warning/colorWarningBg'], + colorWarningBgHover: styles.Colors.light['Warning/colorWarningBgHover'], + colorWarningBorder: styles.Colors.light['Warning/colorWarningBorder'], + colorWarningBorderHover: styles.Colors.light['Warning/colorWarningBorderHover'], + colorWarningHover: styles.Colors.light['Warning/colorWarningHover'], + colorWarning: styles.Colors.light['Warning/colorWarning'], + colorWarningActive: styles.Colors.light['Warning/colorWarningActive'], + colorWarningTextHover: styles.Colors.light['Warning/colorWarningTextHover'], + colorWarningText: styles.Colors.light['Warning/colorWarningText'], + colorWarningTextActive: styles.Colors.light['Warning/colorWarningTextActive'], + colorErrorBg: styles.Colors.light['Error/colorErrorBg'], + colorErrorBgHover: styles.Colors.light['Error/colorErrorBgHover'], + colorErrorBorder: styles.Colors.light['Error/colorErrorBorder'], + colorErrorBorderHover: styles.Colors.light['Error/colorErrorBorderHover'], + colorErrorHover: styles.Colors.light['Error/colorErrorHover'], + colorError: styles.Colors.light['Error/colorError'], + colorErrorActive: styles.Colors.light['Error/colorErrorActive'], + colorErrorTextHover: styles.Colors.light['Error/colorErrorTextHover'], + colorErrorText: styles.Colors.light['Error/colorErrorText'], + colorErrorTextActive: styles.Colors.light['Error/colorErrorTextActive'], + colorLinkHover: styles.Colors.light['colorLinkHover'], + colorLinkActive: styles.Colors.light['colorLinkActive'], + colorTextBase: styles.Colors.light['Text/colorTextBase'], + colorTextSecondary: styles.Colors.light['Text/colorTextSecondary'], + colorBgMask: styles.Colors.light['Background/colorBgMask'] + }, + components: { + Button: { + paddingInline: styles.global['Space/Padding/padding'], + paddingInlineLG: styles.global['Space/Padding/padding'], + paddingBlockSM: styles.global['Space/Padding/paddingXS'] + }, + Collapse: { + colorBorder: styles.Colors.light['Border/colorBorderSecondary'], + contentPadding: `${styles.global['Space/Padding/padding']}px ${styles.global['Space/Padding/paddingLG']}px`, + headerPadding: `${styles.global['Space/Padding/padding']}px ${styles.global['Space/Padding/paddingLG']}px` + }, + Layout: { + bodyBg: '#fafbfa' || styles.Colors.light['Fill/colorFillQuaternary'], + headerBg: styles.Colors.light['Background/colorBgContainer'], + headerPadding: `${styles.global['Size/sizeMD']}px 24px`, + siderBg: styles.Colors.light['Text/colorTextBase'] + }, + Menu: { + colorText: styles.Colors.light['Text/colorTextLight'], + iconSize: 16, + itemBg: styles.Colors.light['Text/colorTextBase'], + itemActiveBg: styles.Colors.dark['Background/colorBgTextActive'], + itemHoverBg: styles.Colors.dark['Background/colorBgTextHover'], + itemMarginBlock: styles.global['BorderRadius/borderRadius'], + itemMarginInline: styles.global['Space/Margin/margin'], + itemSelectedColor: styles.Colors.light['Text/colorTextLight'], + itemSelectedBg: styles.Colors.light['Primary/colorPrimaryActive'] + }, + Typography: { + colorError: styles.Colors.light['Error/colorError'], + colorErrorHover: styles.Colors.light['Error/colorError'], + colorErrorActive: styles.Colors.light['Error/colorError'], + colorLink: styles.Colors.light['colorLink'], + colorLinkActive: styles.Colors.light['colorLinkActive'], + colorLinkHover: styles.Colors.light['colorLinkHover'], + colorSuccess: styles.Colors.light['Success/colorSuccess'], + colorText: styles.Colors.light['Text/colorText'], + colorTextDescription: styles.Colors.light['Text/colorTextDescription'], + colorTextDisabled: styles.Colors.light['Text/colorTextDisabled'], + colorTextHeading: styles.Colors.light['Text/colorTextHeading'], + colorWarning: styles.Colors.light['Warning/colorWarning'], + fontFamily: styles.global.Typography['Desktop/Normal'].fontFamily, + fontSize: styles.global.Typography['Desktop/Normal'].fontSize, + fontSizeHeading1: styles.global.Typography['Desktop/H1'].fontSize, + fontSizeHeading2: styles.global.Typography['Desktop/H2'].fontSize, + fontSizeHeading3: styles.global.Typography['Desktop/H3'].fontSize, + fontSizeHeading4: styles.global.Typography['Desktop/H4'].fontSize, + fontSizeHeading5: styles.global.Typography['Desktop/H5'].fontSize, + lineHeight: styles.global.Typography['Desktop/Normal'].lineHeight, + lineHeightHeading1: styles.global.Typography['Desktop/H1'].lineHeight, + lineHeightHeading2: styles.global.Typography['Desktop/H2'].lineHeight, + lineHeightHeading3: styles.global.Typography['Desktop/H3'].lineHeight, + lineHeightHeading4: styles.global.Typography['Desktop/H4'].lineHeight, + lineHeightHeading5: styles.global.Typography['Desktop/H5'].lineHeight, + linkDecoration: 'underline', + titleMarginBottom: 0, + titleMarginTop: 0 + } + } +}; + +export default theme; diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js index bec39c60..9318e166 100644 --- a/ui/tailwind.config.js +++ b/ui/tailwind.config.js @@ -1,10 +1,100 @@ -/** @type {import('tailwindcss').Config} */ +// @type {import('tailwindcss').Config} +import styles from './src/styles.json' +const theme = 'light' export default { - // content: ['src/**/*.{sass,js}', '../templates/**/*.html'], content: ['./index.html', './src/**/*.{js,jsx,ts,tsx,vue}'], - // purge: ['./index.html', './src/**/*.{js,jsx,ts,tsx,vue}'], theme: { - extend: {} + extend: { + colors: { + "Text/colorTextLight": styles.Colors[theme]["Text/colorTextLight"], + "Text/colorTextDisabled": styles.Colors[theme]["Text/colorTextDisabled"], + "Text/colorTextPlaceholder": styles.Colors[theme]["Text/colorTextPlaceholder"], + "Text/colorTextDescription": styles.Colors[theme]["Text/colorTextDescription"], + "Text/colorTextHeading": styles.Colors[theme]["Text/colorTextHeading"], + "Text/colorTextQuaternary": styles.Colors.dark["Text/colorTextQuaternary"], + "Text/colorTextTertiary": styles.Colors[theme]["Text/colorTextTertiary"], + "Text/colorTextSecondary": styles.Colors[theme]["Text/colorTextSecondary"], + "Text/colorText": styles.Colors[theme]["Text/colorText"], + "Text/colorTextBase": styles.Colors[theme]["Text/colorTextBase"], + "Background/colorBgBase": styles.Colors[theme]["Background/colorBgBase"], + "Background/colorBgContainer": styles.Colors[theme]["Background/colorBgContainer"], + "Background/colorBgElevated": styles.Colors[theme]["Background/colorBgElevated"], + "Background/colorBgLayout": styles.Colors[theme]["Background/colorBgLayout"], + "Background/colorBgSpotlight": styles.Colors[theme]["Background/colorBgSpotlight"], + "Background/colorBgMask": styles.Colors[theme]["Background/colorBgMask"], + "Background/colorBgTextHover": styles.Colors[theme]["Background/colorBgTextHover"], + "Background/colorBgTextActive": styles.Colors[theme]["Background/colorBgTextActive"], + "Background/colorBgContainerDisabled": styles.Colors[theme]["Background/colorBgContainerDisabled"], + "Fill/colorFill": styles.Colors[theme]["Fill/colorFill"], + "Fill/colorFillSecondary": styles.Colors[theme]["Fill/colorFillSecondary"], + "Fill/colorFillTertiary": styles.Colors[theme]["Fill/colorFillTertiary"], + "Fill/colorFillQuaternary": styles.Colors[theme]["Fill/colorFillQuaternary"], + "Fill/colorFillContent": styles.Colors[theme]["Fill/colorFillContent"], + "Fill/colorFillAlter": styles.Colors[theme]["Fill/colorFillAlter"], + "Border/colorBorder": styles.Colors[theme]["Border/colorBorder"], + "Border/colorBorderSecondary": styles.Colors[theme]["Border/colorBorderSecondary"], + "Border/colorSplit": styles.Colors[theme]["Border/colorSplit"], + "ControlItem/controlItemBgHover": styles.Colors[theme]["ControlItem/controlItemBgHover"], + "ControlItem/controlItemBgActive": styles.Colors[theme]["ControlItem/controlItemBgActive"], + "ControlItem/controlItemBgActiveHover": styles.Colors[theme]["ControlItem/controlItemBgActiveHover"], + "Primary/colorPrimaryTextActive": styles.Colors[theme]["Primary/colorPrimaryTextActive"], + "Primary/colorPrimaryText": styles.Colors[theme]["Primary/colorPrimaryText"], + "Primary/colorPrimaryTextHover": styles.Colors[theme]["Primary/colorPrimaryTextHover"], + "Primary/colorPrimaryActive": styles.Colors[theme]["Primary/colorPrimaryActive"], + "Primary/colorPrimary": styles.Colors[theme]["Primary/colorPrimary"], + "Primary/colorPrimaryHover": styles.Colors[theme]["Primary/colorPrimaryHover"], + "Primary/colorPrimaryBorderHover": styles.Colors[theme]["Primary/colorPrimaryBorderHover"], + "Primary/colorPrimaryBorder": styles.Colors[theme]["Primary/colorPrimaryBorder"], + "Primary/colorPrimaryBgHover": styles.Colors[theme]["Primary/colorPrimaryBgHover"], + "Primary/colorPrimaryBg": styles.Colors[theme]["Primary/colorPrimaryBg"], + "Success/colorSuccessBg": styles.Colors[theme]["Success/colorSuccessBg"], + "Success/colorSuccessBgHover": styles.Colors[theme]["Success/colorSuccessBgHover"], + "Success/colorSuccessBorder": styles.Colors[theme]["Success/colorSuccessBorder"], + "Success/colorSuccessBorderHover": styles.Colors[theme]["Success/colorSuccessBorderHover"], + "Success/colorSuccessHover": styles.Colors[theme]["Success/colorSuccessHover"], + "Success/colorSuccess": styles.Colors[theme]["Success/colorSuccess"], + "Success/colorSuccessActive": styles.Colors[theme]["Success/colorSuccessActive"], + "Success/colorSuccessTextHover": styles.Colors[theme]["Success/colorSuccessTextHover"], + "Success/colorSuccessText": styles.Colors[theme]["Success/colorSuccessText"], + "Success/colorSuccessTextActive": styles.Colors[theme]["Success/colorSuccessTextActive"], + "Warning/colorWarningBg": styles.Colors[theme]["Warning/colorWarningBg"], + "Warning/colorWarningBgHover": styles.Colors[theme]["Warning/colorWarningBgHover"], + "Warning/colorWarningBorder": styles.Colors[theme]["Warning/colorWarningBorder"], + "Warning/colorWarningBorderHover": styles.Colors[theme]["Warning/colorWarningBorderHover"], + "Warning/colorWarningHover": styles.Colors[theme]["Warning/colorWarningHover"], + "Warning/colorWarning": styles.Colors[theme]["Warning/colorWarning"], + "Warning/colorWarningActive": styles.Colors[theme]["Warning/colorWarningActive"], + "Warning/colorWarningTextHover": styles.Colors[theme]["Warning/colorWarningTextHover"], + "Warning/colorWarningText": styles.Colors[theme]["Warning/colorWarningText"], + "Warning/colorWarningTextActive": styles.Colors[theme]["Warning/colorWarningTextActive"], + "Error/colorErrorBg": styles.Colors[theme]["Error/colorErrorBg"], + "Error/colorErrorBgHover": styles.Colors[theme]["Error/colorErrorBgHover"], + "Error/colorErrorBorder": styles.Colors[theme]["Error/colorErrorBorder"], + "Error/colorErrorBorderHover": styles.Colors[theme]["Error/colorErrorBorderHover"], + "Error/colorErrorHover": styles.Colors[theme]["Error/colorErrorHover"], + "Error/colorError": styles.Colors[theme]["Error/colorError"], + "Error/colorErrorActive": styles.Colors[theme]["Error/colorErrorActive"], + "Error/colorErrorTextHover": styles.Colors[theme]["Error/colorErrorTextHover"], + "Error/colorErrorText": styles.Colors[theme]["Error/colorErrorText"], + "Error/colorErrorTextActive": styles.Colors[theme]["Error/colorErrorTextActive"], + "colorLink": styles.Colors[theme]["colorLink"], + "colorLinkHover": styles.Colors[theme]["colorLinkHover"], + "colorLinkActive": styles.Colors[theme]["colorLinkActive"], + "colorIcon": styles.Colors[theme]["colorIcon"], + "colorIconHover": styles.Colors[theme]["colorIconHover"], + }, + boxShadow: { + "shadowPrimpary": styles.global.Effects.boxShadow["box-shadow"], + "shadowPrimpary1": styles.global.Effects.boxShadow["box-shadow2"], + "shadowPrimpary2": styles.global.Effects.boxShadow["box-shadow3"], + "shadowSecondary": styles.global.Effects.boxShadowSecondary['box-shadow'], + "shadowSecondary2": styles.global.Effects.boxShadowSecondary['box-shadow2'], + "shadowSecondary3": styles.global.Effects.boxShadowSecondary['box-shadow3'], + "FocusPrimary": styles.global.Effects.FocusPrimary['box-shadow'], + "FocusYellow": styles.global.Effects.FocusYellow['box-shadow'], + "FocusRed": styles.global.Effects.FocusRed['box-shadow'], + } + } }, corePlugins:{ preflight: false