diff --git a/.gitignore b/.gitignore index 426558c90..09f4f040b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ __pycache__/ *.py[cod] *$py.class - +.vennv # C extensions *.so /backend/graph @@ -168,4 +168,8 @@ google-cloud-cli-469.0.0-linux-x86_64.tar.gz /backend/src/chunks /backend/merged_files /backend/chunks -google-cloud-cli-479.0.0-linux-x86_64.tar.gz +google-cloud-cli-linux-x86_64.tar.gz +.vennv +newenv +files + diff --git a/README.md b/README.md index 13d89f814..f1ef12198 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,13 @@ DIFFBOT_API_KEY="your-diffbot-key" if you only want OpenAI: ```env -LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" +VITE_LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" OPENAI_API_KEY="your-openai-key" ``` if you only want Diffbot: ```env -LLM_MODELS="diffbot" +VITE_LLM_MODELS="diffbot" DIFFBOT_API_KEY="your-diffbot-key" ``` @@ -59,13 +59,13 @@ docker-compose up --build By default, the input sources will be: Local files, Youtube, Wikipedia ,AWS S3 and Webpages. As this default config is applied: ```env -REACT_APP_SOURCES="local,youtube,wiki,s3,web" +VITE_REACT_APP_SOURCES="local,youtube,wiki,s3,web" ``` If however you want the Google GCS integration, add `gcs` and your Google client ID: ```env -REACT_APP_SOURCES="local,youtube,wiki,s3,gcs,web" -GOOGLE_CLIENT_ID="xxxx" +VITE_REACT_APP_SOURCES="local,youtube,wiki,s3,gcs,web" +VITE_GOOGLE_CLIENT_ID="xxxx" ``` You can of course combine all (local, youtube, wikipedia, s3 and gcs) or remove any you don't want/need. @@ -75,12 +75,12 @@ You can of course combine all (local, youtube, wikipedia, s3 and gcs) or remove By default,all of the chat modes will be available: vector, graph+vector and graph. If none of the mode is mentioned in the chat modes variable all modes will be available: ```env -CHAT_MODES="" +VITE_CHAT_MODES="" ``` If however you want to specify the only vector mode or only graph mode you can do that by specifying the mode in the env: ```env -CHAT_MODES="vector,graph+vector" +VITE_CHAT_MODES="vector,graph+vector" ``` #### Running Backend and Frontend separately (dev environment) @@ -134,8 +134,8 @@ Allow unauthenticated request : Yes | KNN_MIN_SCORE | Optional | 0.94 | Minimum score for KNN algorithm | | GEMINI_ENABLED | Optional | False | Flag to enable Gemini | | GCP_LOG_METRICS_ENABLED | Optional | False | Flag to enable Google Cloud logs | -| NUMBER_OF_CHUNKS_TO_COMBINE | Optional | 5 | Number of chunks to combine when processing embeddings | -| UPDATE_GRAPH_CHUNKS_PROCESSED | Optional | 20 | Number of chunks processed before updating progress | +| NUMBER_OF_CHUNKS_TO_COMBINE | Optional | 5 | Number of chunks to combine when processing embeddings | +| UPDATE_GRAPH_CHUNKS_PROCESSED | Optional | 20 | Number of chunks processed before updating progress | | NEO4J_URI | Optional | neo4j://database:7687 | URI for Neo4j database | | NEO4J_USERNAME | Optional | neo4j | Username for Neo4j database | | NEO4J_PASSWORD | Optional | password | Password for Neo4j database | @@ -143,18 +143,19 @@ Allow unauthenticated request : Yes | LANGCHAIN_PROJECT | Optional | | Project for Langchain | | LANGCHAIN_TRACING_V2 | Optional | true | Flag to enable Langchain tracing | | LANGCHAIN_ENDPOINT | Optional | https://api.smith.langchain.com | Endpoint for Langchain API | -| BACKEND_API_URL | Optional | http://localhost:8000 | URL for backend API | -| BLOOM_URL | Optional | https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true | URL for Bloom visualization | -| REACT_APP_SOURCES | Optional | local,youtube,wiki,s3 | List of input sources that will be available | -| LLM_MODELS | Optional | diffbot,openai-gpt-3.5,openai-gpt-4o | Models available for selection on the frontend, used for entities extraction and Q&A -| CHAT_MODES | Optional | vector,graph+vector,graph | Chat modes available for Q&A -| ENV | Optional | DEV | Environment variable for the app | -| TIME_PER_CHUNK | Optional | 4 | Time per chunk for processing | -| CHUNK_SIZE | Optional | 5242880 | Size of each chunk of file for upload | -| GOOGLE_CLIENT_ID | Optional | | Client ID for Google authentication | -| GCS_FILE_CACHE | Optional | False | If set to True, will save the files to process into GCS. If set to False, will save the files locally | -| ENTITY_EMBEDDING | Optional | False | If set to True, It will add embeddings for each entity in database | -| LLM_MODEL_CONFIG_ollama_ | Optional | | Set ollama config as - model_name,model_local_url for local deployments | +| VITE_BACKEND_API_URL | Optional | http://localhost:8000 | URL for backend API | +| VITE_BLOOM_URL | Optional | https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true | URL for Bloom visualization | +| VITE_REACT_APP_SOURCES | Mandatory | local,youtube,wiki,s3 | List of input sources that will be available | +| VITE_LLM_MODELS | Mandatory | diffbot,openai-gpt-3.5,openai-gpt-4o | Models available for selection on the frontend, used for entities extraction and Q&A +| VITE_CHAT_MODES | Mandatory | vector,graph+vector,graph,hybrid | Chat modes available for Q&A +| VITE_ENV | Mandatory | DEV or PROD | Environment variable for the app | +| VITE_TIME_PER_PAGE | Optional | 50 | Time per page for processing | +| VITE_CHUNK_SIZE | Optional | 5242880 | Size of each chunk of file for upload | +| VITE_GOOGLE_CLIENT_ID | Optional | | Client ID for Google authentication | +| GCS_FILE_CACHE | Optional | False | If set to True, will save the files to process into GCS. If set to False, will save the files locally | +| ENTITY_EMBEDDING | Optional | False | If set to True, It will add embeddings for each entity in database | +| LLM_MODEL_CONFIG_ollama_ | Optional | | Set ollama config as - model_name,model_local_url for local deployments | + diff --git a/backend/Dockerfile b/backend/Dockerfile index 5249ac53c..ff46b33e5 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -21,4 +21,4 @@ RUN pip install -r requirements.txt # Copy application code COPY . /code # Set command -CMD ["gunicorn", "score:app", "--workers", "8","--threads", "8", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--timeout", "300"] +CMD ["gunicorn", "score:app", "--workers", "8","--preload","--threads", "8", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--timeout", "300"] diff --git a/backend/Performance_test.py b/backend/Performance_test.py new file mode 100644 index 000000000..fc0aee66f --- /dev/null +++ b/backend/Performance_test.py @@ -0,0 +1,116 @@ +import concurrent.futures +import requests +import time +API_BASE_URL = 'https://dev-backend-dcavk67s4a-uc.a.run.app' +ENDPOINTS = { + 'get_sourcelist': '/sources_list', + 'get_health' : '/health', + 'post_connect' : '/connect', + 'chatbot': '/chat_bot', + # 'post_chunk': '/chunk_entities' #'chatbot_details' +} + + +CONCURRENT_REQUESTS = 25 # Number of concurrent requests +CHATBOT_MESSAGES = ["list out details of Patents of Jacob Einstein","hi","hello","mango","apple","amazon","Patrick pichette","Sunder pichai","tesla","Joe biden","Modi","AI","Neo4j","list out details of Patents of Jacob Einstein","hi","hello","mango","apple","amazon","Patrick pichette",] + +#Source_list + +# def get_request_sourcelist(): +# data = {'uri': '', +# 'database' : '', +# 'userName' : '', +# 'password' : ''} +# #response = requests.get(ENDPOINTS['get_sourcelist'], headers=data) +# response = requests.get(ENDPOINTS['get_sourcelist']) +# return response.status_code, response.text + +#Connect + +# def post_request_connect(): +# data = {'uri': '', +# 'database' : '', +# 'userName' : '', +# 'password' : ''} +# response = requests.post(API_BASE_URL + ENDPOINTS['post_connect'], headers=data) +# return response.status_code, response.text + +#Chunk + +def post_request_chunk(): + data = {'uri': '', + 'database' : '', + 'userName' : '', + 'password' : '', + 'chunk_ids' : "14b337cdcab8f4c8006f7cd5a699ffe69f377e6c" + } + response = requests.post(API_BASE_URL + ENDPOINTS['post_chunk'], headers=data) + return response.status_code, response.text + +#chat_bot + +# def chatbot_request(message): +# #data = {'message': message} # Replacing with actual message +# data = {"uri":"", +# "database":"", +# "userName":"", +# "password": "", +# "question": message, +# "session_id": "697c0fa9-f340-4a8f-960b-50158d8ea804", +# "model": "openai-gpt-3.5", +# "mode": "graph+vector", +# "document_names": [] +# } +# response = requests.post(API_BASE_URL + ENDPOINTS['chatbot'], data=data) +# return response.status_code, response.text + +#Health API + +# def get_request_health(): +# response = requests.get(API_BASE_URL + ENDPOINTS['get_health']) +# return response.status_code, response.text + + +def performance_main(): + start_time = time.time() + + with concurrent.futures.ThreadPoolExecutor(max_workers=CONCURRENT_REQUESTS) as executor: + # List of futures for all tasks + futures = [] + + # GET request futures + # for _ in range(CONCURRENT_REQUESTS): + # futures.append(executor.submit(get_request_sourcelist)) + + # GET request futures + # for _ in range(CONCURRENT_REQUESTS): + # futures.append(executor.submit(get_request_health)) + + # POST request futures + # for _ in range(CONCURRENT_REQUESTS): + # futures.append(executor.submit(post_request_connect)) + + # POST request futures + for _ in range(CONCURRENT_REQUESTS): + futures.append(executor.submit(post_request_chunk)) + + # Chatbot request futures + # for message in CHATBOT_MESSAGES: + # futures.append(executor.submit(chatbot_request, message)) + + + + # Process completed futures + print(len(futures)) + for future in concurrent.futures.as_completed(futures): + try: + status_code, response_text = future.result() + print(f'Status Code: {status_code}, Response: {response_text}') + except Exception as e: + print(f'Error: {e}') + + end_time = time.time() + print(f'Total Time Taken: {end_time - start_time} seconds') + +if __name__ == '__main__': + performance_main() \ No newline at end of file diff --git a/backend/dbtest.py b/backend/dbtest.py new file mode 100644 index 000000000..c9ef8d47f --- /dev/null +++ b/backend/dbtest.py @@ -0,0 +1,72 @@ +import time +from neo4j import GraphDatabase + +# Database configurations +neo4j_configurations = [ + { + 'name': 'Neo4j Config 1', + 'NEO4J_URI': 'neo4j+s://73b760b4.databases.neo4j.io', + 'NEO4J_USERNAME': 'neo4j', + 'NEO4J_PASSWORD': 'HqwAzfG83XwcEQ-mvEG4yNpcRTHMpsgZaYW3qIGJh2I' + }, + # { + # 'name': 'Neo4j Config 2', + # 'uri': 'bolt://another-host:7687', + # 'user': 'neo4j', + # 'password': 'password2' + # } +] + +# Function to create a Neo4j driver +def create_driver(uri, user, password): + return GraphDatabase.driver(uri, auth=(user, password)) + +# Function to clear the database +def clear_database(driver): + with driver.session() as session: + session.run("MATCH (n) DETACH DELETE n") + +# Performance test function +def performance_test(driver, query, num_operations): + with driver.session() as session: + start_time = time.time() + for i in range(num_operations): + session.run(query, parameters={"id": i, "name": f"name_{i}"}) + end_time = time.time() + return end_time - start_time + +# Query to execute +query = "CREATE (n:Person {id: $id, name: $name})" + +# Number of operations to perform +num_operations = 1000 + +def dbtest_main(): + results = [] + + for config in neo4j_configurations: + print(f"Testing {config['name']}...") + + # Create driver + driver = create_driver(config['uri'], config['user'], config['password']) + + # Clear database before test + clear_database(driver) + + # Run performance test + elapsed_time = performance_test(driver, query, num_operations) + + # Store result + results.append((config['name'], elapsed_time)) + + # Close driver + driver.close() + + print(f"{config['name']} completed in {elapsed_time:.4f} seconds") + + print("\nPerformance Test Results:") + for name, time_taken in results: + print(f"{name}: {time_taken:.4f} seconds") + +if __name__ == "__main__": + dbtest_main() \ No newline at end of file diff --git a/backend/example.env b/backend/example.env index 0bbbf2403..1d14cfae4 100644 --- a/backend/example.env +++ b/backend/example.env @@ -25,6 +25,8 @@ NEO4J_USER_AGENT="" ENABLE_USER_AGENT = "" LLM_MODEL_CONFIG_model_version="" ENTITY_EMBEDDING="" True or False +DUPLICATE_SCORE_VALUE = "" +DUPLICATE_TEXT_DISTANCE = "" #examples LLM_MODEL_CONFIG_azure_ai_gpt_35="azure_deployment_name,azure_endpoint or base_url,azure_api_key,api_version" LLM_MODEL_CONFIG_azure_ai_gpt_4o="gpt-4o,https://YOUR-ENDPOINT.openai.azure.com/,azure_api_key,api_version" diff --git a/backend/locustperf.py b/backend/locustperf.py new file mode 100644 index 000000000..38ff9c0ed --- /dev/null +++ b/backend/locustperf.py @@ -0,0 +1,85 @@ +import os +import time +from locust import HttpUser, TaskSet, task, between, events +from requests.auth import HTTPBasicAuth +import json + +# Global variables to store results +results = { + "total_requests": 0, + "total_failures": 0, + "response_times": [] +} + +class UserBehavior(TaskSet): + + @task + def post_request(self): + payload = { + "uri": "", + "database": "", + "userName": "" + } + with self.client.post("/connect", json=payload, catch_response=True) as response: + # https://prod-backend-dcavk67s4a-uc.a.run.app/connect + results["total_requests"] += 1 + if response.status_code == 200: + results["response_times"].append(response.elapsed.total_seconds()) + else: + results["total_failures"] += 1 + response.failure("Failed POST request") + + @task + def get_request(self): + with self.client.get("/health", catch_response=True) as response: + results["total_requests"] += 1 + if response.status_code == 200: + results["response_times"].append(response.elapsed.total_seconds()) + else: + results["total_failures"] += 1 + response.failure("Failed GET request") + + @task + def get_request(self): + with self.client.get("/sources_list", catch_response=True) as response: + results["total_requests"] += 1 + if response.status_code == 200: + results["response_times"].append(response.elapsed.total_seconds()) + else: + results["total_failures"] += 1 + response.failure("Failed GET request") + + # @task(4) + # def upload_file(self): + # files = { + # "file": ("filename.txt", open("filename.txt", "rb"), "text/plain") + # } + # with self.client.post("/your-upload-endpoint", files=files, catch_response=True) as response: + # results["total_requests"] += 1 + # if response.status_code == 200: + # results["response_times"].append(response.elapsed.total_seconds()) + # else: + # results["total_failures"] += 1 + # response.failure("Failed UPLOAD request") + +class WebsiteUser(HttpUser): + tasks = [UserBehavior] + wait_time = between(5, 15) + +@events.quitting.add_listener +def generate_summary(environment, **kwargs): + total_requests = results["total_requests"] + total_failures = results["total_failures"] + avg_response_time = sum(results["response_times"]) / len(results["response_times"]) if results["response_times"] else 0 + + summary = { + "total_requests": total_requests, + "total_failures": total_failures, + "average_response_time": avg_response_time + } + + print("\nPerformance Test Summary:") + print(json.dumps(summary, indent=4)) + +if __name__ == "__main__": + os.system("locust -f locustperf.py") \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 2c0f33af4..46c57aea5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -59,7 +59,7 @@ huggingface-hub humanfriendly==10.0 idna==3.6 importlib-resources==6.1.1 -install==1.3.5 +pip-install==1.3.5 iopath==0.1.10 Jinja2==3.1.3 jmespath==1.0.1 @@ -69,18 +69,18 @@ jsonpath-python==1.0.6 jsonpointer==2.4 json-repair==0.25.2 kiwisolver==1.4.5 -langchain==0.2.6 -langchain-aws==0.1.9 -langchain-anthropic==0.1.19 -langchain-fireworks==0.1.4 -langchain-google-genai==1.0.7 -langchain-community==0.2.6 -langchain-core==0.2.10 -langchain-experimental==0.0.62 -langchain-google-vertexai==1.0.6 -langchain-groq==0.1.6 -langchain-openai==0.1.14 -langchain-text-splitters==0.2.2 +langchain +langchain-aws +langchain-anthropic +langchain-fireworks +langchain-google-genai +langchain-community +langchain-core +langchain-experimental +langchain-google-vertexai +langchain-groq +langchain-openai +langchain-text-splitters langdetect==1.0.9 langsmith==0.1.83 layoutparser==0.3.4 @@ -175,6 +175,7 @@ wrapt==1.16.0 yarl==1.9.4 youtube-transcript-api==0.6.2 zipp==3.17.0 -sentence-transformers==2.7.0 +sentence-transformers==3.0.1 google-cloud-logging==3.10.0 PyMuPDF==1.24.5 +pypandoc==1.13 diff --git a/backend/score.py b/backend/score.py index b8de56b6b..00650b56e 100644 --- a/backend/score.py +++ b/backend/score.py @@ -1,11 +1,7 @@ -from fastapi import FastAPI, File, UploadFile, Form, Query -from fastapi import FastAPI -from fastapi import FastAPI, File, UploadFile, Form, Body -from fastapi import FastAPI, Request +from fastapi import FastAPI, File, UploadFile, Form, Request from fastapi_health import health from fastapi.middleware.cors import CORSMiddleware from src.main import * -# from src.QA_integration import * from src.QA_integration_new import * from src.entities.user_credential import user_credential from src.shared.common_fn import * @@ -22,16 +18,12 @@ from sse_starlette.sse import EventSourceResponse import json from typing import List, Mapping -from fastapi.responses import RedirectResponse, HTMLResponse from starlette.middleware.sessions import SessionMiddleware import google_auth_oauthlib.flow from google.oauth2.credentials import Credentials import os -from typing import List -from google.cloud import logging as gclogger from src.logger import CustomLogger from datetime import datetime, timezone -from fastapi.middleware.gzip import GZipMiddleware import time import gc @@ -58,7 +50,6 @@ def sick(): allow_methods=["*"], allow_headers=["*"], ) -# app.add_middleware(GZipMiddleware, minimum_size=1000) is_gemini_enabled = os.environ.get("GEMINI_ENABLED", "False").lower() in ("true", "1", "yes") if is_gemini_enabled: @@ -72,15 +63,15 @@ def sick(): @app.post("/url/scan") async def create_source_knowledge_graph_url( request: Request, - uri=Form(None), - userName=Form(None), - password=Form(None), + uri=Form(), + userName=Form(), + password=Form(), source_url=Form(None), - database=Form(None), + database=Form(), aws_access_key_id=Form(None), aws_secret_access_key=Form(None), wiki_query=Form(None), - model=Form(None), + model=Form(), gcs_bucket_name=Form(None), gcs_bucket_folder=Form(None), source_type=Form(None), @@ -114,8 +105,8 @@ async def create_source_knowledge_graph_url( return create_api_response('Failed',message='source_type is other than accepted source') message = f"Source Node created successfully for source type: {source_type} and source: {source}" - josn_obj = {'api_name':'url_scan','db_url':uri,'url_scanned_file':lst_file_name, 'source_url':source_url, 'wiki_query':wiki_query, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name':'url_scan','db_url':uri,'url_scanned_file':lst_file_name, 'source_url':source_url, 'wiki_query':wiki_query, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) return create_api_response("Success",message=message,success_count=success_count,failed_count=failed_count,file_name=lst_file_name) except Exception as e: error_message = str(e) @@ -124,16 +115,14 @@ async def create_source_knowledge_graph_url( return create_api_response('Failed',message=message + error_message[:80],error=error_message,file_source=source_type) finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'url/scan') @app.post("/extract") async def extract_knowledge_graph_from_file( - uri=Form(None), - userName=Form(None), - password=Form(None), - model=Form(None), - database=Form(None), + uri=Form(), + userName=Form(), + password=Form(), + model=Form(), + database=Form(), source_url=Form(None), aws_access_key_id=Form(None), aws_secret_access_key=Form(None), @@ -167,33 +156,35 @@ async def extract_knowledge_graph_from_file( try: graph = create_graph_database_connection(uri, userName, password, database) graphDb_data_Access = graphDBdataAccess(graph) + if source_type == 'local file': merged_file_path = os.path.join(MERGED_DIR,file_name) logging.info(f'File path:{merged_file_path}') result = await asyncio.to_thread( - extract_graph_from_file_local_file, graph, model, merged_file_path, file_name, allowedNodes, allowedRelationship, uri) + extract_graph_from_file_local_file, uri, userName, password, database, model, merged_file_path, file_name, allowedNodes, allowedRelationship) elif source_type == 's3 bucket' and source_url: result = await asyncio.to_thread( - extract_graph_from_file_s3, graph, model, source_url, aws_access_key_id, aws_secret_access_key, allowedNodes, allowedRelationship) + extract_graph_from_file_s3, uri, userName, password, database, model, source_url, aws_access_key_id, aws_secret_access_key, allowedNodes, allowedRelationship) elif source_type == 'web-url': result = await asyncio.to_thread( - extract_graph_from_web_page, graph, model, source_url, allowedNodes, allowedRelationship) + extract_graph_from_web_page, uri, userName, password, database, model, source_url, allowedNodes, allowedRelationship) elif source_type == 'youtube' and source_url: result = await asyncio.to_thread( - extract_graph_from_file_youtube, graph, model, source_url, allowedNodes, allowedRelationship) + extract_graph_from_file_youtube, uri, userName, password, database, model, source_url, allowedNodes, allowedRelationship) elif source_type == 'Wikipedia' and wiki_query: result = await asyncio.to_thread( - extract_graph_from_file_Wikipedia, graph, model, wiki_query, max_sources, language, allowedNodes, allowedRelationship) + extract_graph_from_file_Wikipedia, uri, userName, password, database, model, wiki_query, max_sources, language, allowedNodes, allowedRelationship) elif source_type == 'gcs bucket' and gcs_bucket_name: result = await asyncio.to_thread( - extract_graph_from_file_gcs, graph, model, gcs_project_id, gcs_bucket_name, gcs_bucket_folder, gcs_blob_filename, access_token, allowedNodes, allowedRelationship) + extract_graph_from_file_gcs, uri, userName, password, database, model, gcs_project_id, gcs_bucket_name, gcs_bucket_folder, gcs_blob_filename, access_token, allowedNodes, allowedRelationship) else: return create_api_response('Failed',message='source_type is other than accepted source') + if result is not None: result['db_url'] = uri result['api_name'] = 'extract' @@ -217,14 +208,12 @@ async def extract_knowledge_graph_from_file( else: logging.info(f'Deleted File Path: {merged_file_path} and Deleted File Name : {file_name}') delete_uploaded_local_file(merged_file_path,file_name) - josn_obj = {'message':message,'error_message':error_message, 'file_name': file_name,'status':'Failed','db_url':uri,'failed_count':1, 'source_type': source_type, 'source_url':source_url, 'wiki_query':wiki_query, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) - logging.exception(f'File Failed in extraction: {josn_obj}') + json_obj = {'message':message,'error_message':error_message, 'file_name': file_name,'status':'Failed','db_url':uri,'failed_count':1, 'source_type': source_type, 'source_url':source_url, 'wiki_query':wiki_query, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) + logging.exception(f'File Failed in extraction: {json_obj}') return create_api_response('Failed', message=message + error_message[:100], error=error_message, file_name = file_name) finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'extract') @app.get("/sources_list") async def get_source_list(uri:str, userName:str, password:str, database:str=None): @@ -236,8 +225,8 @@ async def get_source_list(uri:str, userName:str, password:str, database:str=None if " " in uri: uri = uri.replace(" ","+") result = await asyncio.to_thread(get_source_list_from_graph,uri,userName,decoded_password,database) - josn_obj = {'api_name':'sources_list','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name':'sources_list','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) return create_api_response("Success",data=result) except Exception as e: job_status = "Failed" @@ -247,25 +236,27 @@ async def get_source_list(uri:str, userName:str, password:str, database:str=None return create_api_response(job_status, message=message, error=error_message) @app.post("/post_processing") -async def post_processing(uri=Form(None), userName=Form(None), password=Form(None), database=Form(None), tasks=Form(None)): +async def post_processing(uri=Form(), userName=Form(), password=Form(), database=Form(), tasks=Form(None)): try: graph = create_graph_database_connection(uri, userName, password, database) tasks = set(map(str.strip, json.loads(tasks))) - if "update_similarity_graph" in tasks: + if "materialize_text_chunk_similarities" in tasks: await asyncio.to_thread(update_graph, graph) - josn_obj = {'api_name': 'post_processing/update_similarity_graph', 'db_url': uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name': 'post_processing/update_similarity_graph', 'db_url': uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) logging.info(f'Updated KNN Graph') - if "create_fulltext_index" in tasks: - await asyncio.to_thread(create_fulltext, uri=uri, username=userName, password=password, database=database) - josn_obj = {'api_name': 'post_processing/create_fulltext_index', 'db_url': uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + + if "enable_hybrid_search_and_fulltext_search_in_bloom" in tasks: + await asyncio.to_thread(create_fulltext, uri=uri, username=userName, password=password, database=database,type="entities") + # await asyncio.to_thread(create_fulltext, uri=uri, username=userName, password=password, database=database,type="keyword") + josn_obj = {'api_name': 'post_processing/enable_hybrid_search_and_fulltext_search_in_bloom', 'db_url': uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} logger.log_struct(josn_obj) logging.info(f'Full Text index created') - if os.environ.get('ENTITY_EMBEDDING','False').upper()=="TRUE" and "create_entity_embedding" in tasks: + if os.environ.get('ENTITY_EMBEDDING','False').upper()=="TRUE" and "materialize_entity_similarities" in tasks: await asyncio.to_thread(create_entity_embedding, graph) - josn_obj = {'api_name': 'post_processing/create_entity_embedding', 'db_url': uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name': 'post_processing/create_entity_embedding', 'db_url': uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) logging.info(f'Entity Embeddings created') return create_api_response('Success', message='All tasks completed successfully') @@ -278,11 +269,9 @@ async def post_processing(uri=Form(None), userName=Form(None), password=Form(Non finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'post_processing') @app.post("/chat_bot") -async def chat_bot(uri=Form(None),model=Form(None),userName=Form(None), password=Form(None), database=Form(None),question=Form(None), document_names=Form(None),session_id=Form(None),mode=Form(None)): +async def chat_bot(uri=Form(),model=Form(None),userName=Form(), password=Form(), database=Form(),question=Form(None), document_names=Form(None),session_id=Form(None),mode=Form(None)): logging.info(f"QA_RAG called at {datetime.now()}") qa_rag_start_time = time.time() try: @@ -296,8 +285,8 @@ async def chat_bot(uri=Form(None),model=Form(None),userName=Form(None), password logging.info(f"Total Response time is {total_call_time:.2f} seconds") result["info"]["response_time"] = round(total_call_time, 2) - josn_obj = {'api_name':'chat_bot','db_url':uri,'session_id':session_id, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name':'chat_bot','db_url':uri,'session_id':session_id, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) return create_api_response('Success',data=result) except Exception as e: job_status = "Failed" @@ -309,12 +298,12 @@ async def chat_bot(uri=Form(None),model=Form(None),userName=Form(None), password gc.collect() @app.post("/chunk_entities") -async def chunk_entities(uri=Form(None),userName=Form(None), password=Form(None), chunk_ids=Form(None)): +async def chunk_entities(uri=Form(),userName=Form(), password=Form(), chunk_ids=Form(None)): try: logging.info(f"URI: {uri}, Username: {userName}, chunk_ids: {chunk_ids}") result = await asyncio.to_thread(get_entities_from_chunkids,uri=uri, username=userName, password=password, chunk_ids=chunk_ids) - josn_obj = {'api_name':'chunk_entities','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name':'chunk_entities','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) return create_api_response('Success',data=result) except Exception as e: job_status = "Failed" @@ -327,9 +316,9 @@ async def chunk_entities(uri=Form(None),userName=Form(None), password=Form(None) @app.post("/graph_query") async def graph_query( - uri: str = Form(None), - userName: str = Form(None), - password: str = Form(None), + uri: str = Form(), + userName: str = Form(), + password: str = Form(), document_names: str = Form(None), ): try: @@ -341,8 +330,8 @@ async def graph_query( password=password, document_names=document_names ) - josn_obj = {'api_name':'graph_query','db_url':uri,'document_names':document_names, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name':'graph_query','db_url':uri,'document_names':document_names, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) return create_api_response('Success', data=result) except Exception as e: job_status = "Failed" @@ -355,7 +344,7 @@ async def graph_query( @app.post("/clear_chat_bot") -async def clear_chat_bot(uri=Form(None),userName=Form(None), password=Form(None), database=Form(None), session_id=Form(None)): +async def clear_chat_bot(uri=Form(),userName=Form(), password=Form(), database=Form(), session_id=Form(None)): try: graph = create_graph_database_connection(uri, userName, password, database) result = await asyncio.to_thread(clear_chat_history,graph=graph,session_id=session_id) @@ -368,17 +357,15 @@ async def clear_chat_bot(uri=Form(None),userName=Form(None), password=Form(None) return create_api_response(job_status, message=message, error=error_message) finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'clear_chat_bot') @app.post("/connect") -async def connect(uri=Form(None), userName=Form(None), password=Form(None), database=Form(None)): +async def connect(uri=Form(), userName=Form(), password=Form(), database=Form()): try: graph = create_graph_database_connection(uri, userName, password, database) - result = await asyncio.to_thread(connection_check, graph) - josn_obj = {'api_name':'connect','db_url':uri,'status':result, 'count':1, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) - return create_api_response('Success',message=result) + result = await asyncio.to_thread(connection_check_and_get_vector_dimensions, graph) + json_obj = {'api_name':'connect','db_url':uri,'status':result, 'count':1, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) + return create_api_response('Success',data=result) except Exception as e: job_status = "Failed" message="Connection failed to connect Neo4j database" @@ -388,13 +375,13 @@ async def connect(uri=Form(None), userName=Form(None), password=Form(None), data @app.post("/upload") async def upload_large_file_into_chunks(file:UploadFile = File(...), chunkNumber=Form(None), totalChunks=Form(None), - originalname=Form(None), model=Form(None), uri=Form(None), userName=Form(None), - password=Form(None), database=Form(None)): + originalname=Form(None), model=Form(None), uri=Form(), userName=Form(), + password=Form(), database=Form()): try: graph = create_graph_database_connection(uri, userName, password, database) result = await asyncio.to_thread(upload_file, graph, model, file, chunkNumber, totalChunks, originalname, uri, CHUNK_DIR, MERGED_DIR) - josn_obj = {'api_name':'upload','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name':'upload','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) if int(chunkNumber) == int(totalChunks): return create_api_response('Success',data=result, message='Source Node Created Successfully') else: @@ -408,17 +395,15 @@ async def upload_large_file_into_chunks(file:UploadFile = File(...), chunkNumber return create_api_response('Failed', message=message + error_message[:100], error=error_message, file_name = originalname) finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'upload') @app.post("/schema") -async def get_structured_schema(uri=Form(None), userName=Form(None), password=Form(None), database=Form(None)): +async def get_structured_schema(uri=Form(), userName=Form(), password=Form(), database=Form()): try: graph = create_graph_database_connection(uri, userName, password, database) result = await asyncio.to_thread(get_labels_and_relationtypes, graph) logging.info(f'Schema result from DB: {result}') - josn_obj = {'api_name':'schema','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + json_obj = {'api_name':'schema','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) return create_api_response('Success', data=result) except Exception as e: message="Unable to get the labels and relationtypes from neo4j database" @@ -428,8 +413,6 @@ async def get_structured_schema(uri=Form(None), userName=Form(None), password=Fo return create_api_response("Failed", message=message, error=error_message) finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'schema') def decode_password(pwd): sample_string_bytes = base64.b64decode(pwd) @@ -445,29 +428,32 @@ async def generate(): if " " in url: uri= url.replace(" ","+") while True: - if await request.is_disconnected(): - logging.info("Request disconnected") - break - #get the current status of document node - graph = create_graph_database_connection(uri, userName, decoded_password, database) - graphDb_data_Access = graphDBdataAccess(graph) - result = graphDb_data_Access.get_current_status_document_node(file_name) - if result is not None: - status = json.dumps({'fileName':file_name, - 'status':result[0]['Status'], - 'processingTime':result[0]['processingTime'], - 'nodeCount':result[0]['nodeCount'], - 'relationshipCount':result[0]['relationshipCount'], - 'model':result[0]['model'], - 'total_chunks':result[0]['total_chunks'], - 'total_pages':result[0]['total_pages'], - 'fileSize':result[0]['fileSize'], - 'processed_chunk':result[0]['processed_chunk'], - 'fileSource':result[0]['fileSource'] - }) - else: - status = json.dumps({'fileName':file_name, 'status':'Failed'}) - yield status + try: + if await request.is_disconnected(): + logging.info(" SSE Client disconnected") + break + # get the current status of document node + graph = create_graph_database_connection(uri, userName, decoded_password, database) + graphDb_data_Access = graphDBdataAccess(graph) + result = graphDb_data_Access.get_current_status_document_node(file_name) + if result is not None: + status = json.dumps({'fileName':file_name, + 'status':result[0]['Status'], + 'processingTime':result[0]['processingTime'], + 'nodeCount':result[0]['nodeCount'], + 'relationshipCount':result[0]['relationshipCount'], + 'model':result[0]['model'], + 'total_chunks':result[0]['total_chunks'], + 'total_pages':result[0]['total_pages'], + 'fileSize':result[0]['fileSize'], + 'processed_chunk':result[0]['processed_chunk'], + 'fileSource':result[0]['fileSource'] + }) + else: + status = json.dumps({'fileName':file_name, 'status':'Failed'}) + yield status + except asyncio.CancelledError: + logging.info("SSE Connection cancelled") return EventSourceResponse(generate(),ping=60) @@ -483,10 +469,10 @@ async def delete_document_and_entities(uri=Form(), graph = create_graph_database_connection(uri, userName, password, database) graphDb_data_Access = graphDBdataAccess(graph) result, files_list_size = await asyncio.to_thread(graphDb_data_Access.delete_file_from_graph, filenames, source_types, deleteEntities, MERGED_DIR, uri) - entities_count = result[0]['deletedEntities'] if 'deletedEntities' in result[0] else 0 - message = f"Deleted {files_list_size} documents with {entities_count} entities from database" - josn_obj = {'api_name':'delete_document_and_entities','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} - logger.log_struct(josn_obj) + # entities_count = result[0]['deletedEntities'] if 'deletedEntities' in result[0] else 0 + message = f"Deleted {files_list_size} documents with entities from database" + json_obj = {'api_name':'delete_document_and_entities','db_url':uri, 'logging_time': formatted_time(datetime.now(timezone.utc))} + logger.log_struct(json_obj) return create_api_response('Success',message=message) except Exception as e: job_status = "Failed" @@ -496,8 +482,6 @@ async def delete_document_and_entities(uri=Form(), return create_api_response(job_status, message=message, error=error_message) finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'delete_document_and_entities') @app.get('/document_status/{file_name}') async def get_document_status(file_name, url, userName, password, database): @@ -534,7 +518,7 @@ async def get_document_status(file_name, url, userName, password, database): return create_api_response('Failed',message=message) @app.post("/cancelled_job") -async def cancelled_job(uri=Form(None), userName=Form(None), password=Form(None), database=Form(None), filenames=Form(None), source_types=Form(None)): +async def cancelled_job(uri=Form(), userName=Form(), password=Form(), database=Form(), filenames=Form(None), source_types=Form(None)): try: graph = create_graph_database_connection(uri, userName, password, database) result = manually_cancelled_job(graph,filenames, source_types, MERGED_DIR, uri) @@ -548,8 +532,6 @@ async def cancelled_job(uri=Form(None), userName=Form(None), password=Form(None) return create_api_response(job_status, message=message, error=error_message) finally: gc.collect() - if graph is not None: - close_db_connection(graph, 'cancelled_job') @app.post("/populate_graph_schema") async def populate_graph_schema(input_text=Form(None), model=Form(None), is_schema_description_checked=Form(None)): @@ -579,12 +561,10 @@ async def get_unconnected_nodes_list(uri=Form(), userName=Form(), password=Form( logging.exception(f'Exception in getting list of unconnected nodes:{error_message}') return create_api_response(job_status, message=message, error=error_message) finally: - if graph is not None: - close_db_connection(graph,"get_unconnected_nodes_list") gc.collect() @app.post("/delete_unconnected_nodes") -async def get_unconnected_nodes_list(uri=Form(), userName=Form(), password=Form(), database=Form(),unconnected_entities_list=Form()): +async def delete_orphan_nodes(uri=Form(), userName=Form(), password=Form(), database=Form(),unconnected_entities_list=Form()): try: graph = create_graph_database_connection(uri, userName, password, database) graphDb_data_Access = graphDBdataAccess(graph) @@ -597,9 +577,55 @@ async def get_unconnected_nodes_list(uri=Form(), userName=Form(), password=Form( logging.exception(f'Exception in delete the unconnected nodes:{error_message}') return create_api_response(job_status, message=message, error=error_message) finally: - if graph is not None: - close_db_connection(graph,"delete_unconnected_nodes") + gc.collect() + +@app.post("/get_duplicate_nodes") +async def get_duplicate_nodes(uri=Form(), userName=Form(), password=Form(), database=Form()): + try: + graph = create_graph_database_connection(uri, userName, password, database) + graphDb_data_Access = graphDBdataAccess(graph) + nodes_list, total_nodes = graphDb_data_Access.get_duplicate_nodes_list() + return create_api_response('Success',data=nodes_list, message=total_nodes) + except Exception as e: + job_status = "Failed" + message="Unable to get the list of duplicate nodes" + error_message = str(e) + logging.exception(f'Exception in getting list of duplicate nodes:{error_message}') + return create_api_response(job_status, message=message, error=error_message) + finally: + gc.collect() + +@app.post("/merge_duplicate_nodes") +async def merge_duplicate_nodes(uri=Form(), userName=Form(), password=Form(), database=Form(),duplicate_nodes_list=Form()): + try: + graph = create_graph_database_connection(uri, userName, password, database) + graphDb_data_Access = graphDBdataAccess(graph) + result = graphDb_data_Access.merge_duplicate_nodes(duplicate_nodes_list) + return create_api_response('Success',data=result,message="Duplicate entities merged successfully") + except Exception as e: + job_status = "Failed" + message="Unable to merge the duplicate nodes" + error_message = str(e) + logging.exception(f'Exception in merge the duplicate nodes:{error_message}') + return create_api_response(job_status, message=message, error=error_message) + finally: + gc.collect() + +@app.post("/drop_create_vector_index") +async def merge_duplicate_nodes(uri=Form(), userName=Form(), password=Form(), database=Form(), isVectorIndexExist=Form()): + try: + graph = create_graph_database_connection(uri, userName, password, database) + graphDb_data_Access = graphDBdataAccess(graph) + result = graphDb_data_Access.drop_create_vector_index(isVectorIndexExist) + return create_api_response('Success',message=result) + except Exception as e: + job_status = "Failed" + message="Unable to drop and re-create vector index with correct dimesion as per application configuration" + error_message = str(e) + logging.exception(f'Exception into drop and re-create vector index with correct dimesion as per application configuration:{error_message}') + return create_api_response(job_status, message=message, error=error_message) + finally: gc.collect() if __name__ == "__main__": - uvicorn.run(app) \ No newline at end of file + uvicorn.run(app) diff --git a/backend/src/QA_integration.py b/backend/src/QA_integration.py index 951a1ce8f..6a70c3ec0 100644 --- a/backend/src/QA_integration.py +++ b/backend/src/QA_integration.py @@ -30,18 +30,18 @@ # RETRIEVAL_QUERY = """ -# WITH node, score, apoc.text.join([ (node)-[:HAS_ENTITY]->(e) | head(labels(e)) + ": "+ e.id],", ") as entities -# MATCH (node)-[:PART_OF]->(d:Document) +# WITH node, score, apoc.text.join([ (node)-[:__HAS_ENTITY__]->(e) | head(labels(e)) + ": "+ e.id],", ") as entities +# MATCH (node)-[:__PART_OF__]->(d:Document) # WITH d, apoc.text.join(collect(node.text + "\n" + entities),"\n----\n") as text, avg(score) as score # RETURN text, score, {source: COALESCE(CASE WHEN d.url CONTAINS "None" THEN d.fileName ELSE d.url END, d.fileName)} as metadata # """ RETRIEVAL_QUERY = """ WITH node as chunk, score -MATCH (chunk)-[:PART_OF]->(d:Document) +MATCH (chunk)-[:__PART_OF__]->(d:__Document__) CALL { WITH chunk -MATCH (chunk)-[:HAS_ENTITY]->(e) -MATCH path=(e)(()-[rels:!HAS_ENTITY&!PART_OF]-()){0,3}(:!Chunk&!Document) +MATCH (chunk)-[:__HAS_ENTITY__]->(e) +MATCH path=(e)(()-[rels:!__HAS_ENTITY__&!__PART_OF__]-()){0,3}(:!__Chunk__&!__Document__) UNWIND rels as r RETURN collect(distinct r) as rels } @@ -83,7 +83,8 @@ def get_llm(model: str,max_tokens=1000) -> Any: "gemini-1.5-pro": "gemini-1.5-pro-preview-0409", "openai-gpt-4": "gpt-4-0125-preview", "diffbot" : "gpt-4-0125-preview", - "openai-gpt-4o":"gpt-4o" + "openai-gpt-4o":"gpt-4o", + "openai-gpt-4o-mini": "gpt-4o-mini", } if model in model_versions: model_version = model_versions[model] diff --git a/backend/src/QA_integration_new.py b/backend/src/QA_integration_new.py index dbd08f7a1..eeac78c1e 100644 --- a/backend/src/QA_integration_new.py +++ b/backend/src/QA_integration_new.py @@ -39,55 +39,90 @@ EMBEDDING_FUNCTION , _ = load_embedding_model(EMBEDDING_MODEL) -def get_neo4j_retriever(graph, retrieval_query,document_names,index_name="vector", search_k=CHAT_SEARCH_KWARG_K, score_threshold=CHAT_SEARCH_KWARG_SCORE_THRESHOLD): +def get_neo4j_retriever(graph, retrieval_query,document_names,mode,index_name="vector",keyword_index="keyword", search_k=CHAT_SEARCH_KWARG_K, score_threshold=CHAT_SEARCH_KWARG_SCORE_THRESHOLD): try: - neo_db = Neo4jVector.from_existing_index( - embedding=EMBEDDING_FUNCTION, - index_name=index_name, - retrieval_query=retrieval_query, - graph=graph - ) - logging.info(f"Successfully retrieved Neo4jVector index '{index_name}'") + if mode == "fulltext" or mode == "graph + vector + fulltext": + neo_db = Neo4jVector.from_existing_graph( + embedding=EMBEDDING_FUNCTION, + index_name=index_name, + retrieval_query=retrieval_query, + graph=graph, + search_type="hybrid", + node_label="Chunk", + embedding_node_property="embedding", + text_node_properties=["text"], + keyword_index_name=keyword_index + ) + # neo_db = Neo4jVector.from_existing_index( + # embedding=EMBEDDING_FUNCTION, + # index_name=index_name, + # retrieval_query=retrieval_query, + # graph=graph, + # search_type="hybrid", + # keyword_index_name=keyword_index + # ) + logging.info(f"Successfully retrieved Neo4jVector index '{index_name}' and keyword index '{keyword_index}'") + else: + neo_db = Neo4jVector.from_existing_index( + embedding=EMBEDDING_FUNCTION, + index_name=index_name, + retrieval_query=retrieval_query, + graph=graph + ) + logging.info(f"Successfully retrieved Neo4jVector index '{index_name}'") document_names= list(map(str.strip, json.loads(document_names))) if document_names: - retriever = neo_db.as_retriever(search_kwargs={'k': search_k, "score_threshold": score_threshold,'filter':{'fileName': {'$in': document_names}}}) + retriever = neo_db.as_retriever(search_type="similarity_score_threshold",search_kwargs={'k': search_k, "score_threshold": score_threshold,'filter':{'fileName': {'$in': document_names}}}) logging.info(f"Successfully created retriever for index '{index_name}' with search_k={search_k}, score_threshold={score_threshold} for documents {document_names}") else: - retriever = neo_db.as_retriever(search_kwargs={'k': search_k, "score_threshold": score_threshold}) + retriever = neo_db.as_retriever(search_type="similarity_score_threshold",search_kwargs={'k': search_k, "score_threshold": score_threshold}) logging.info(f"Successfully created retriever for index '{index_name}' with search_k={search_k}, score_threshold={score_threshold}") return retriever except Exception as e: logging.error(f"Error retrieving Neo4jVector index '{index_name}' or creating retriever: {e}") - return None + raise Exception("An error occurred while retrieving the Neo4jVector index '{index_name}' or creating the retriever. Please drop and create a new vector index: {e}") from e -def create_document_retriever_chain(llm,retriever): - query_transform_prompt = ChatPromptTemplate.from_messages( - [ - ("system", QUESTION_TRANSFORM_TEMPLATE), - MessagesPlaceholder(variable_name="messages") - ] - ) - output_parser = StrOutputParser() +def create_document_retriever_chain(llm, retriever): + try: + logging.info("Starting to create document retriever chain") - splitter = TokenTextSplitter(chunk_size=CHAT_DOC_SPLIT_SIZE, chunk_overlap=0) - embeddings_filter = EmbeddingsFilter(embeddings=EMBEDDING_FUNCTION, similarity_threshold=CHAT_EMBEDDING_FILTER_SCORE_THRESHOLD) + query_transform_prompt = ChatPromptTemplate.from_messages( + [ + ("system", QUESTION_TRANSFORM_TEMPLATE), + MessagesPlaceholder(variable_name="messages") + ] + ) - pipeline_compressor = DocumentCompressorPipeline( - transformers=[splitter, embeddings_filter] - ) - compression_retriever = ContextualCompressionRetriever( - base_compressor=pipeline_compressor, base_retriever=retriever - ) + output_parser = StrOutputParser() - query_transforming_retriever_chain = RunnableBranch( - ( - lambda x: len(x.get("messages", [])) == 1, - (lambda x: x["messages"][-1].content) | compression_retriever, - ), - query_transform_prompt | llm | output_parser | compression_retriever, - ).with_config(run_name="chat_retriever_chain") + splitter = TokenTextSplitter(chunk_size=CHAT_DOC_SPLIT_SIZE, chunk_overlap=0) + embeddings_filter = EmbeddingsFilter( + embeddings=EMBEDDING_FUNCTION, + similarity_threshold=CHAT_EMBEDDING_FILTER_SCORE_THRESHOLD + ) - return query_transforming_retriever_chain + pipeline_compressor = DocumentCompressorPipeline( + transformers=[splitter, embeddings_filter] + ) + + compression_retriever = ContextualCompressionRetriever( + base_compressor=pipeline_compressor, base_retriever=retriever + ) + + query_transforming_retriever_chain = RunnableBranch( + ( + lambda x: len(x.get("messages", [])) == 1, + (lambda x: x["messages"][-1].content) | compression_retriever, + ), + query_transform_prompt | llm | output_parser | compression_retriever, + ).with_config(run_name="chat_retriever_chain") + + logging.info("Successfully created document retriever chain") + return query_transforming_retriever_chain + + except Exception as e: + logging.error(f"Error creating document retriever chain: {e}", exc_info=True) + raise def create_neo4j_chat_message_history(graph, session_id): @@ -105,7 +140,7 @@ def create_neo4j_chat_message_history(graph, session_id): except Exception as e: logging.error(f"Error creating Neo4jChatMessageHistory: {e}") - return None + raise def format_documents(documents,model): prompt_token_cutoff = 4 @@ -219,13 +254,13 @@ def clear_chat_history(graph,session_id): "user": "chatbot" } -def setup_chat(model, graph, session_id, document_names,retrieval_query): +def setup_chat(model, graph, document_names,retrieval_query,mode): start_time = time.time() if model in ["diffbot"]: model = "openai-gpt-4o" llm,model_name = get_llm(model) logging.info(f"Model called in chat {model} and model version is {model_name}") - retriever = get_neo4j_retriever(graph=graph,retrieval_query=retrieval_query,document_names=document_names) + retriever = get_neo4j_retriever(graph=graph,retrieval_query=retrieval_query,document_names=document_names,mode=mode) doc_retriever = create_document_retriever_chain(llm, retriever) chat_setup_time = time.time() - start_time logging.info(f"Chat setup completed in {chat_setup_time:.2f} seconds") @@ -339,15 +374,15 @@ def QA_RAG(graph, model, question, document_names,session_id, mode): "user": "chatbot" } return result - elif mode == "vector": + elif mode == "vector" or mode == "fulltext": retrieval_query = VECTOR_SEARCH_QUERY else: retrieval_query = VECTOR_GRAPH_SEARCH_QUERY.format(no_of_entites=VECTOR_GRAPH_SEARCH_ENTITY_LIMIT) - llm, doc_retriever, model_version = setup_chat(model, graph, session_id, document_names,retrieval_query) + llm, doc_retriever, model_version = setup_chat(model, graph, document_names,retrieval_query,mode) docs = retrieve_documents(doc_retriever, messages) - + if docs: content, result, total_tokens = process_documents(docs, question, messages, llm,model) else: diff --git a/backend/src/QA_optimization.py b/backend/src/QA_optimization.py index fcb179a0d..70c5ffdc8 100644 --- a/backend/src/QA_optimization.py +++ b/backend/src/QA_optimization.py @@ -46,13 +46,13 @@ async def _vector_embed_results(self): t=datetime.now() print("Vector embeddings start time",t) # retrieval_query=""" - # MATCH (node)-[:PART_OF]->(d:Document) + # MATCH (node)-[:__PART_OF__]->(d:Document) # WITH d, apoc.text.join(collect(node.text),"\n----\n") as text, avg(score) as score # RETURN text, score, {source: COALESCE(CASE WHEN d.url CONTAINS "None" THEN d.fileName ELSE d.url END, d.fileName)} as metadata # """ retrieval_query=""" - WITH node, score, apoc.text.join([ (node)-[:HAS_ENTITY]->(e) | head(labels(e)) + ": "+ e.id],", ") as entities - MATCH (node)-[:PART_OF]->(d:Document) + WITH node, score, apoc.text.join([ (node)-[:__HAS_ENTITY__]->(e) | head(labels(e)) + ": "+ e.id],", ") as entities + MATCH (node)-[:__PART_OF__]->(d:__Document__) WITH d, apoc.text.join(collect(node.text + "\n" + entities),"\n----\n") as text, avg(score) as score RETURN text, score, {source: COALESCE(CASE WHEN d.url CONTAINS "None" THEN d.fileName ELSE d.url END, d.fileName)} as metadata """ diff --git a/backend/src/chunkid_entities.py b/backend/src/chunkid_entities.py index 9785403a3..cdbd358d5 100644 --- a/backend/src/chunkid_entities.py +++ b/backend/src/chunkid_entities.py @@ -8,7 +8,7 @@ MATCH (chunk)-[:PART_OF]->(d:Document) CALL {WITH chunk MATCH (chunk)-[:HAS_ENTITY]->(e) -MATCH path=(e)(()-[rels:!HAS_ENTITY&!PART_OF]-()){0,2}(:!Chunk&!Document) +MATCH path=(e)(()-[rels:!HAS_ENTITY&!PART_OF]-()){0,2}(:!Chunk &! Document) UNWIND rels as r RETURN collect(distinct r) as rels } @@ -79,7 +79,7 @@ def process_chunk_data(chunk_data): for chunk in record["chunks"]: chunk.update(doc_properties) if chunk["fileSource"] == "youtube": - chunk["start_time"] = time_to_seconds(chunk["start_time"]) + chunk["start_time"] = min(time_to_seconds(chunk["start_time"]),time_to_seconds(chunk["end_time"])) chunk["end_time"] = time_to_seconds(chunk["end_time"]) chunk_properties.append(chunk) diff --git a/backend/src/create_chunks.py b/backend/src/create_chunks.py index 4686cc9c3..621785a31 100644 --- a/backend/src/create_chunks.py +++ b/backend/src/create_chunks.py @@ -3,7 +3,8 @@ from langchain_community.graphs import Neo4jGraph import logging import os -from src.document_sources.youtube import get_chunks_with_timestamps +from src.document_sources.youtube import get_chunks_with_timestamps, get_calculated_timestamps +import re logging.basicConfig(format="%(asctime)s - %(message)s", level="INFO") @@ -34,8 +35,15 @@ def split_file_into_chunks(self): chunks.append(Document(page_content=chunk.page_content, metadata={'page_number':page_number})) elif 'length' in self.pages[0].metadata: - chunks_without_timestamps = text_splitter.split_documents(self.pages) - chunks = get_chunks_with_timestamps(chunks_without_timestamps, self.pages[0].metadata['source']) + if len(self.pages) == 1 or (len(self.pages) > 1 and self.pages[1].page_content.strip() == ''): + match = re.search(r'(?:v=)([0-9A-Za-z_-]{11})\s*',self.pages[0].metadata['source']) + youtube_id=match.group(1) + chunks_without_time_range = text_splitter.split_documents([self.pages[0]]) + chunks = get_calculated_timestamps(chunks_without_time_range, youtube_id) + + else: + chunks_without_time_range = text_splitter.split_documents(self.pages) + chunks = get_chunks_with_timestamps(chunks_without_time_range) else: chunks = text_splitter.split_documents(self.pages) return chunks \ No newline at end of file diff --git a/backend/src/document_sources/gcs_bucket.py b/backend/src/document_sources/gcs_bucket.py index d9719c2b3..4a5909fc4 100644 --- a/backend/src/document_sources/gcs_bucket.py +++ b/backend/src/document_sources/gcs_bucket.py @@ -8,7 +8,8 @@ import io from google.oauth2.credentials import Credentials import time -from .local_file import load_document_content, get_pages_with_page_numbers +import nltk +from .local_file import load_document_content def get_gcs_bucket_files_info(gcs_project_id, gcs_bucket_name, gcs_bucket_folder, creds): storage_client = storage.Client(project=gcs_project_id, credentials=creds) @@ -44,7 +45,8 @@ def load_pdf(file_path): return PyMuPDFLoader(file_path) def get_documents_from_gcs(gcs_project_id, gcs_bucket_name, gcs_bucket_folder, gcs_blob_filename, access_token=None): - + nltk.download('punkt') + nltk.download('averaged_perceptron_tagger') if gcs_bucket_folder is not None: if gcs_bucket_folder.endswith('/'): blob_name = gcs_bucket_folder+gcs_blob_filename @@ -59,8 +61,6 @@ def get_documents_from_gcs(gcs_project_id, gcs_bucket_name, gcs_bucket_folder, g storage_client = storage.Client(project=gcs_project_id) loader = GCSFileLoader(project_name=gcs_project_id, bucket=gcs_bucket_name, blob=blob_name, loader_func=load_document_content) pages = loader.load() - if (gcs_blob_filename.split('.')[-1]).lower() != 'pdf': - pages = get_pages_with_page_numbers(pages) else: creds= Credentials(access_token) storage_client = storage.Client(project=gcs_project_id, credentials=creds) diff --git a/backend/src/document_sources/local_file.py b/backend/src/document_sources/local_file.py index f73e1d391..9fb31649f 100644 --- a/backend/src/document_sources/local_file.py +++ b/backend/src/document_sources/local_file.py @@ -24,7 +24,7 @@ def load_document_content(file_path): return PyMuPDFLoader(file_path) else: print("in else") - return UnstructuredFileLoader(file_path, encoding="utf-8", mode="elements") + return UnstructuredFileLoader(file_path, mode="elements",autodetect_encoding=True) def get_documents_from_file_by_path(file_path,file_name): file_path = Path(file_path) diff --git a/backend/src/document_sources/web_pages.py b/backend/src/document_sources/web_pages.py index 39f2fb855..659a81267 100644 --- a/backend/src/document_sources/web_pages.py +++ b/backend/src/document_sources/web_pages.py @@ -4,13 +4,8 @@ def get_documents_from_web_page(source_url:str): try: - pages = WebBaseLoader(source_url).load() + pages = WebBaseLoader(source_url, verify_ssl=False).load() file_name = pages[0].metadata['title'] return file_name, pages except Exception as e: - job_status = "Failed" - message="Failed To Process Web URL" - error_message = str(e) - logging.error(f"Failed To Process Web URL: {file_name}") - logging.exception(f'Exception Stack trace: {error_message}') - return create_api_response(job_status,message=message,error=error_message,file_name=file_name) \ No newline at end of file + raise Exception(str(e)) \ No newline at end of file diff --git a/backend/src/document_sources/youtube.py b/backend/src/document_sources/youtube.py index 63f178023..df8ad9a92 100644 --- a/backend/src/document_sources/youtube.py +++ b/backend/src/document_sources/youtube.py @@ -5,9 +5,24 @@ from urllib.parse import urlparse,parse_qs from difflib import SequenceMatcher from datetime import timedelta +from langchain_community.document_loaders.youtube import TranscriptFormat +from src.shared.constants import YOUTUBE_CHUNK_SIZE_SECONDS +from typing import List, Dict, Any def get_youtube_transcript(youtube_id): try: + #transcript = YouTubeTranscriptApi.get_transcript(youtube_id) + transcript_list = YouTubeTranscriptApi.list_transcripts(youtube_id) + transcript = transcript_list.find_transcript(["en"]) + transcript_pieces: List[Dict[str, Any]] = transcript.fetch() + return transcript_pieces + except Exception as e: + message = f"Youtube transcript is not available for youtube Id: {youtube_id}" + raise Exception(message) + +def get_youtube_combined_transcript(youtube_id): + try: + transcript_dict = get_youtube_transcript(youtube_id) transcript = YouTubeTranscriptApi.get_transcript(youtube_id) return transcript except Exception as e: @@ -43,7 +58,9 @@ def get_documents_from_youtube(url): youtube_loader = YoutubeLoader.from_youtube_url(url, language=["en-US", "en-gb", "en-ca", "en-au","zh-CN", "zh-Hans", "zh-TW", "fr-FR","de-DE","it-IT","ja-JP","pt-BR","ru-RU","es-ES"], translation = "en", - add_video_info=True) + add_video_info=True, + transcript_format=TranscriptFormat.CHUNKS, + chunk_size_seconds=YOUTUBE_CHUNK_SIZE_SECONDS) pages = youtube_loader.load() file_name = YouTube(url).title return file_name, pages @@ -52,15 +69,16 @@ def get_documents_from_youtube(url): logging.exception(f'Exception in reading transcript from youtube:{error_message}') raise Exception(error_message) -def get_chunks_with_timestamps(chunks, youtube_id): +def get_calculated_timestamps(chunks, youtube_id): + logging.info('Calculating timestamps for chunks') max_start_similarity=0 max_end_similarity=0 transcript = get_youtube_transcript(youtube_id) for chunk in chunks: - start_content = chunk.page_content[:40] - end_content = chunk.page_content[-40:] - + start_content = chunk.page_content[:40].strip().replace('\n', ' ') + end_content = chunk.page_content[-40:].strip().replace('\n', ' ') for segment in transcript: + segment['text'] = segment['text'].replace('\n', ' ') start_similarity = SequenceMatcher(None, start_content, segment['text']) end_similarity = SequenceMatcher(None, end_content, segment['text']) @@ -72,8 +90,14 @@ def get_chunks_with_timestamps(chunks, youtube_id): max_end_similarity = end_similarity.ratio() end_time = segment['start']+segment['duration'] - chunk.metadata['start_time'] = str(timedelta(seconds = start_time)).split('.')[0] - chunk.metadata['end_time'] = str(timedelta(seconds = end_time)).split('.')[0] + chunk.metadata['start_timestamp'] = str(timedelta(seconds = start_time)).split('.')[0] + chunk.metadata['end_timestamp'] = str(timedelta(seconds = end_time)).split('.')[0] max_start_similarity=0 max_end_similarity=0 + return chunks + +def get_chunks_with_timestamps(chunks): + logging.info('adding end_timestamp to chunks') + for chunk in chunks : + chunk.metadata['end_timestamp'] = str(timedelta(seconds = chunk.metadata['start_seconds']+60)).split('.')[0] return chunks \ No newline at end of file diff --git a/backend/src/gemini_llm.py b/backend/src/gemini_llm.py deleted file mode 100644 index 1054ad867..000000000 --- a/backend/src/gemini_llm.py +++ /dev/null @@ -1,54 +0,0 @@ -from langchain_community.graphs import Neo4jGraph -from dotenv import load_dotenv -from langchain.schema import Document -import logging -import concurrent.futures -from concurrent.futures import ThreadPoolExecutor -from typing import List -from langchain_experimental.graph_transformers import LLMGraphTransformer -from langchain_core.documents import Document -from typing import List -import google.auth -from typing import List -from langchain_core.documents import Document -import vertexai -from src.llm import get_graph_document_list, get_combined_chunks, get_llm - -load_dotenv() -logging.basicConfig(format='%(asctime)s - %(message)s',level='DEBUG') - - -def get_graph_from_Gemini(model_version, - graph: Neo4jGraph, - chunkId_chunkDoc_list: List, - allowedNodes, - allowedRelationship): - """ - Extract graph from OpenAI and store it in database. - This is a wrapper for extract_and_store_graph - - Args: - model_version : identify the model of LLM - graph: Neo4jGraph to be extracted. - chunks: List of chunk documents created from input file - Returns: - List of langchain GraphDocument - used to generate graph - """ - logging.info(f"Get graphDocuments from {model_version}") - futures = [] - graph_document_list = [] - location = "us-central1" - #project_id = "llm-experiments-387609" - credentials, project_id = google.auth.default() - if hasattr(credentials, "service_account_email"): - logging.info(credentials.service_account_email) - else: - logging.info("WARNING: no service account credential. User account credential?") - vertexai.init(project=project_id, location=location) - - combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) - - llm,model_name = get_llm(model_version) - return get_graph_document_list(llm, combined_chunk_document_list, allowedNodes, allowedRelationship) - - diff --git a/backend/src/generate_graphDocuments_from_llm.py b/backend/src/generate_graphDocuments_from_llm.py deleted file mode 100644 index 4be4d89dc..000000000 --- a/backend/src/generate_graphDocuments_from_llm.py +++ /dev/null @@ -1,44 +0,0 @@ -from langchain_community.graphs import Neo4jGraph -from src.diffbot_transformer import get_graph_from_diffbot -from src.openAI_llm import get_graph_from_OpenAI -from src.gemini_llm import get_graph_from_Gemini -from typing import List -import logging -from src.shared.constants import * -import os -from src.llm import get_graph_from_llm - -logging.basicConfig(format="%(asctime)s - %(message)s", level="INFO") - - -def generate_graphDocuments(model: str, graph: Neo4jGraph, chunkId_chunkDoc_list: List, allowedNodes=None, allowedRelationship=None): - - if allowedNodes is None or allowedNodes=="": - allowedNodes =[] - else: - allowedNodes = allowedNodes.split(',') - if allowedRelationship is None or allowedRelationship=="": - allowedRelationship=[] - else: - allowedRelationship = allowedRelationship.split(',') - - logging.info(f"allowedNodes: {allowedNodes}, allowedRelationship: {allowedRelationship}") - - graph_documents = [] - if model == "diffbot": - graph_documents = get_graph_from_diffbot(graph, chunkId_chunkDoc_list) - - elif model in OPENAI_MODELS: - graph_documents = get_graph_from_OpenAI(model, graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) - - elif model in GEMINI_MODELS: - graph_documents = get_graph_from_Gemini(model, graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) - - # elif model in GROQ_MODELS : - # graph_documents = get_graph_from_Groq_Llama3(MODEL_VERSIONS[model], graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) - - else : - graph_documents = get_graph_from_llm(model,chunkId_chunkDoc_list, allowedNodes, allowedRelationship) - - logging.info(f"graph_documents = {len(graph_documents)}") - return graph_documents diff --git a/backend/src/graphDB_dataAccess.py b/backend/src/graphDB_dataAccess.py index 1289f32fa..c77a1e773 100644 --- a/backend/src/graphDB_dataAccess.py +++ b/backend/src/graphDB_dataAccess.py @@ -2,7 +2,7 @@ import os from datetime import datetime from langchain_community.graphs import Neo4jGraph -from src.shared.common_fn import create_gcs_bucket_folder_name_hashed, delete_uploaded_local_file +from src.shared.common_fn import create_gcs_bucket_folder_name_hashed, delete_uploaded_local_file, load_embedding_model from src.document_sources.gcs_bucket import delete_file_from_gcs from src.shared.constants import BUCKET_UPLOAD from src.entities.source_node import sourceNode @@ -141,8 +141,10 @@ def update_KNN_graph(self): else: logging.info("Vector index does not exist, So KNN graph not update") - def connection_check(self): + def connection_check_and_get_vector_dimensions(self): """ + Get the vector index dimension from database and application configuration and DB connection status + Args: uri: URI of the graph to extract userName: Username to use for graph creation ( if None will use username from config file ) @@ -151,8 +153,32 @@ def connection_check(self): Returns: Returns a status of connection from NEO4j is success or failure """ + + db_vector_dimension = self.graph.query("""SHOW INDEXES YIELD * + WHERE type = 'VECTOR' AND name = 'vector' + RETURN options.indexConfig['vector.dimensions'] AS vector_dimensions + """) + + result_chunks = self.graph.query("""match (c:Chunk) return size(c.embedding) as embeddingSize, count(*) as chunks, + count(c.embedding) as hasEmbedding + """) + + embedding_model = os.getenv('EMBEDDING_MODEL') + embeddings, application_dimension = load_embedding_model(embedding_model) + logging.info(f'embedding model:{embeddings} and dimesion:{application_dimension}') + # print(chunks_exists) + if self.graph: - return "Connection Successful" + if len(db_vector_dimension) > 0: + return {'db_vector_dimension': db_vector_dimension[0]['vector_dimensions'], 'application_dimension':application_dimension, 'message':"Connection Successful"} + else: + if len(db_vector_dimension) == 0 and len(result_chunks) == 0: + logging.info("Chunks and vector index does not exists in database") + return {'db_vector_dimension': 0, 'application_dimension':application_dimension, 'message':"Connection Successful","chunks_exists":False} + elif len(db_vector_dimension) == 0 and result_chunks[0]['hasEmbedding']==0 and result_chunks[0]['chunks'] > 0: + return {'db_vector_dimension': 0, 'application_dimension':application_dimension, 'message':"Connection Successful","chunks_exists":True} + else: + return {'message':"Connection Successful"} def execute_query(self, query, param=None): return self.graph.query(query, param) @@ -189,21 +215,17 @@ def delete_file_from_graph(self, filenames, source_types, deleteEntities:str, me detach delete c, d return count(*) as deletedChunks """ - query_to_delete_document_and_entities=""" - MATCH (d:Document) where d.fileName in $filename_list and d.fileSource in $source_types_list - with collect(d) as documents + query_to_delete_document_and_entities=""" + match (d:Document) where d.fileName IN $filename_list and d.fileSource in $source_types_list + detach delete d + with collect(d) as documents unwind documents as d - optional match (d)<-[:PART_OF]-(c:Chunk) - // if delete-entities checkbox is set - call { with c, documents - match (c)-[:HAS_ENTITY]->(e) - // belongs to another document - where not exists { (d2)<-[:PART_OF]-()-[:HAS_ENTITY]->(e) WHERE NOT d2 IN documents } - detach delete e - return count(*) as entities - } - detach delete c, d - return sum(entities) as deletedEntities, count(*) as deletedChunks + match (d)<-[:PART_OF]-(c:Chunk) + detach delete c + with * + match (c)-[:HAS_ENTITY]->(e) + where not exists { (e)<-[:HAS_ENTITY]-()-[:PART_OF]->(d2) where not d2 in documents } + detach delete e """ param = {"filename_list" : filename_list, "source_types_list": source_types_list} if deleteEntities == "true": @@ -240,4 +262,95 @@ def delete_unconnected_nodes(self,unconnected_entities_list): DETACH DELETE e """ param = {"elementIds":entities_list} - return self.execute_query(query,param) \ No newline at end of file + return self.execute_query(query,param) + + def get_duplicate_nodes_list(self): + score_value = float(os.environ.get('DUPLICATE_SCORE_VALUE')) + text_distance = int(os.environ.get('DUPLICATE_TEXT_DISTANCE')) + query_duplicate_nodes = """ + MATCH (n:!Chunk&!Document) with n + WHERE n.embedding is not null and n.id is not null // and size(n.id) > 3 + WITH n ORDER BY count {{ (n)--() }} DESC, size(n.id) DESC // updated + WITH collect(n) as nodes + UNWIND nodes as n + WITH n, [other in nodes + // only one pair, same labels e.g. Person with Person + WHERE elementId(n) < elementId(other) and labels(n) = labels(other) + // at least embedding similarity of X + AND + ( + // either contains each other as substrings or has a text edit distinct of less than 3 + (size(other.id) > 2 AND toLower(n.id) CONTAINS toLower(other.id)) OR + (size(n.id) > 2 AND toLower(other.id) CONTAINS toLower(n.id)) + OR (size(n.id)>5 AND apoc.text.distance(toLower(n.id), toLower(other.id)) < $duplicate_text_distance) + OR + vector.similarity.cosine(other.embedding, n.embedding) > $duplicate_score_value + )] as similar + WHERE size(similar) > 0 + // remove duplicate subsets + with collect([n]+similar) as all + CALL {{ with all + unwind all as nodes + with nodes, all + // skip current entry if it's smaller and a subset of any other entry + where none(other in all where other <> nodes and size(other) > size(nodes) and size(apoc.coll.subtract(nodes, other))=0) + return head(nodes) as n, tail(nodes) as similar + }} + OPTIONAL MATCH (doc:Document)<-[:PART_OF]-(c:Chunk)-[:HAS_ENTITY]->(n) + {return_statement} + """ + return_query_duplicate_nodes = """ + RETURN n {.*, embedding:null, elementId:elementId(n), labels:labels(n)} as e, + [s in similar | s {.id, .description, labels:labels(s), elementId: elementId(s)}] as similar, + collect(distinct doc.fileName) as documents, count(distinct c) as chunkConnections + ORDER BY e.id ASC + """ + return_query_duplicate_nodes_total = "RETURN COUNT(DISTINCT(n)) as total" + + param = {"duplicate_score_value": score_value, "duplicate_text_distance" : text_distance} + + nodes_list = self.execute_query(query_duplicate_nodes.format(return_statement=return_query_duplicate_nodes),param=param) + total_nodes = self.execute_query(query_duplicate_nodes.format(return_statement=return_query_duplicate_nodes_total),param=param) + return nodes_list, total_nodes[0] + + def merge_duplicate_nodes(self,duplicate_nodes_list): + nodes_list = json.loads(duplicate_nodes_list) + print(f'Nodes list to merge {nodes_list}') + query = """ + UNWIND $rows AS row + CALL { with row + MATCH (first) WHERE elementId(first) = row.firstElementId + MATCH (rest) WHERE elementId(rest) IN row.similarElementIds + WITH first, collect (rest) as rest + WITH [first] + rest as nodes + CALL apoc.refactor.mergeNodes(nodes, + {properties:"discard",mergeRels:true, produceSelfRel:false, preserveExistingSelfRels:false, singleElementAsArray:true}) + YIELD node + RETURN size(nodes) as mergedCount + } + RETURN sum(mergedCount) as totalMerged + """ + param = {"rows":nodes_list} + return self.execute_query(query,param) + + def drop_create_vector_index(self, isVectorIndexExist): + """ + drop and create the vector index when vector index dimesion are different. + """ + embedding_model = os.getenv('EMBEDDING_MODEL') + embeddings, dimension = load_embedding_model(embedding_model) + + if isVectorIndexExist == 'true': + self.graph.query("""drop index vector""") + # self.graph.query("""drop index vector""") + self.graph.query("""CREATE VECTOR INDEX `vector` if not exists for (c:Chunk) on (c.embedding) + OPTIONS {indexConfig: { + `vector.dimensions`: $dimensions, + `vector.similarity_function`: 'cosine' + }} + """, + { + "dimensions" : dimension + } + ) + return "Drop and Re-Create vector index succesfully" diff --git a/backend/src/graph_query.py b/backend/src/graph_query.py index 0e7df3a05..5468fe2c3 100644 --- a/backend/src/graph_query.py +++ b/backend/src/graph_query.py @@ -3,48 +3,11 @@ from neo4j import GraphDatabase import os import json +from src.shared.constants import GRAPH_CHUNK_LIMIT,GRAPH_QUERY # from neo4j.debug import watch # watch("neo4j") -QUERY_MAP = { - "document" : " + [docs] ", - "chunks" : " + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } ", - "entities" : " + collect { OPTIONAL MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)-[*0..1]-(:!Chunk) RETURN p }", - "docEntities" : " + [docs] + collect { MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)--(:!Chunk) RETURN p }", - "docChunks" : " + [chunks] + collect { MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } ", - "chunksEntities" : " + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } + collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }", - "docChunkEntities" : " + [chunks] + collect { MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } + collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }" -} - -QUERY_WITH_DOCUMENT = """ - MATCH docs = (d:Document) - WHERE d.fileName IN $document_names - WITH docs, d ORDER BY d.createdAt DESC - CALL {{ WITH d - OPTIONAL MATCH chunks=(d)<-[:PART_OF]-(c:Chunk) - RETURN chunks, c LIMIT 50 - }} - WITH [] {query_to_change} AS paths - CALL {{ WITH paths UNWIND paths AS path UNWIND nodes(path) as node RETURN collect(distinct node) as nodes }} - CALL {{ WITH paths UNWIND paths AS path UNWIND relationships(path) as rel RETURN collect(distinct rel) as rels }} - RETURN nodes, rels -""" - -QUERY_WITHOUT_DOCUMENT = """ - MATCH docs = (d:Document) - WITH docs, d ORDER BY d.createdAt DESC - LIMIT $doc_limit - CALL {{ WITH d - OPTIONAL MATCH chunks=(d)<-[:PART_OF]-(c:Chunk) - RETURN chunks, c LIMIT 50 - }} - WITH [] {query_to_change} AS paths - CALL {{ WITH paths UNWIND paths AS path UNWIND nodes(path) as node RETURN collect(distinct node) as nodes }} - CALL {{ WITH paths UNWIND paths AS path UNWIND relationships(path) as rel RETURN collect(distinct rel) as rels }} - RETURN nodes, rels -""" - def get_graphDB_driver(uri, username, password): """ Creates and returns a Neo4j database driver instance configured with the provided credentials. @@ -68,29 +31,6 @@ def get_graphDB_driver(uri, username, password): # raise Exception(error_message) from e -def get_cypher_query(query_map, query_type, document_names): - """ - Generates a Cypher query based on the provided parameters using global templates. - - Returns: - str: A Cypher query string ready to be executed. - """ - try: - query_to_change = query_map[query_type].strip() - logging.info(f"Query template retrieved for type {query_type}") - - if document_names: - logging.info(f"Generating query for documents: {document_names}") - query = QUERY_WITH_DOCUMENT.format(query_to_change=query_to_change) - else: - logging.info("Generating query without specific document.") - query = QUERY_WITHOUT_DOCUMENT.format(query_to_change=query_to_change) - return query.strip() - - except Exception as e: - logging.error("graph_query module: An unexpected error occurred while generating the Cypher query.") - - def execute_query(driver, query,document_names,doc_limit=None): """ Executes a specified query using the Neo4j driver, with parameters based on the presence of a document name. @@ -257,9 +197,8 @@ def get_graph_results(uri, username, password,document_names): logging.info(f"Starting graph query process") driver = get_graphDB_driver(uri, username, password) document_names= list(map(str.strip, json.loads(document_names))) - query_type = "docChunkEntities" - query = get_cypher_query(QUERY_MAP, query_type, document_names) - records, summary , keys = execute_query(driver, query, document_names) + query = GRAPH_QUERY.format(graph_chunk_limit=GRAPH_CHUNK_LIMIT) + records, summary , keys = execute_query(driver, query.strip(), document_names) document_nodes = extract_node_elements(records) document_relationships = extract_relationships(records) diff --git a/backend/src/groq_llama3_llm.py b/backend/src/groq_llama3_llm.py deleted file mode 100644 index 7f0fa9f5e..000000000 --- a/backend/src/groq_llama3_llm.py +++ /dev/null @@ -1,48 +0,0 @@ -from langchain_community.graphs import Neo4jGraph -from dotenv import load_dotenv -import os -import logging -import concurrent.futures -from concurrent.futures import ThreadPoolExecutor -from typing import List -from langchain_experimental.graph_transformers import LLMGraphTransformer -from langchain_core.documents import Document -from src.llm import get_combined_chunks, get_llm - -load_dotenv() -logging.basicConfig(format='%(asctime)s - %(message)s',level='INFO') - -def get_graph_from_Groq_Llama3(model_version, - graph: Neo4jGraph, - chunkId_chunkDoc_list: List, - allowedNodes, - allowedRelationship): - """ - Extract graph from Groq Llama3 and store it in database. - This is a wrapper for extract_and_store_graph - - Args: - model_version : identify the model of LLM - graph: Neo4jGraph to be extracted. - chunks: List of chunk documents created from input file - Returns: - List of langchain GraphDocument - used to generate graph - """ - logging.info(f"Get graphDocuments from {model_version}") - futures = [] - graph_document_list = [] - combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) - #api_key = os.environ.get('GROQ_API_KEY') - llm,model_name = get_llm(model_version) - llm_transformer = LLMGraphTransformer(llm=llm, node_properties=["description"], allowed_nodes=allowedNodes, allowed_relationships=allowedRelationship) - - with ThreadPoolExecutor(max_workers=10) as executor: - for chunk in combined_chunk_document_list: - chunk_doc = Document(page_content= chunk.page_content.encode("utf-8"), metadata=chunk.metadata) - futures.append(executor.submit(llm_transformer.convert_to_graph_documents,[chunk_doc])) - - for i, future in enumerate(concurrent.futures.as_completed(futures)): - graph_document = future.result() - graph_document_list.append(graph_document[0]) - - return graph_document_list \ No newline at end of file diff --git a/backend/src/llm.py b/backend/src/llm.py index fe0eeeca2..0ee61b650 100644 --- a/backend/src/llm.py +++ b/backend/src/llm.py @@ -18,14 +18,14 @@ from src.shared.constants import MODEL_VERSIONS -def get_llm(model_version: str): +def get_llm(model: str): """Retrieve the specified language model based on the model name.""" - env_key = "LLM_MODEL_CONFIG_" + model_version + env_key = "LLM_MODEL_CONFIG_" + model env_value = os.environ.get(env_key) logging.info("Model: {}".format(env_key)) - if "gemini" in model_version: + if "gemini" in model: credentials, project_id = google.auth.default() - model_name = MODEL_VERSIONS[model_version] + model_name = MODEL_VERSIONS[model] llm = ChatVertexAI( model_name=model_name, convert_system_message_to_human=True, @@ -40,15 +40,15 @@ def get_llm(model_version: str): HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE, }, ) - elif "openai" in model_version: - model_name = MODEL_VERSIONS[model_version] + elif "openai" in model: + model_name = MODEL_VERSIONS[model] llm = ChatOpenAI( api_key=os.environ.get("OPENAI_API_KEY"), model=model_name, temperature=0, ) - elif "azure" in model_version: + elif "azure" in model: model_name, api_endpoint, api_key, api_version = env_value.split(",") llm = AzureChatOpenAI( api_key=api_key, @@ -60,21 +60,21 @@ def get_llm(model_version: str): timeout=None, ) - elif "anthropic" in model_version: + elif "anthropic" in model: model_name, api_key = env_value.split(",") llm = ChatAnthropic( api_key=api_key, model=model_name, temperature=0, timeout=None ) - elif "fireworks" in model_version: + elif "fireworks" in model: model_name, api_key = env_value.split(",") llm = ChatFireworks(api_key=api_key, model=model_name) - elif "groq" in model_version: + elif "groq" in model: model_name, base_url, api_key = env_value.split(",") llm = ChatGroq(api_key=api_key, model_name=model_name, temperature=0) - elif "bedrock" in model_version: + elif "bedrock" in model: model_name, aws_access_key, aws_secret_key, region_name = env_value.split(",") bedrock_client = boto3.client( service_name="bedrock-runtime", @@ -87,17 +87,27 @@ def get_llm(model_version: str): client=bedrock_client, model_id=model_name, model_kwargs=dict(temperature=0) ) - elif "ollama" in model_version: + elif "ollama" in model: model_name, base_url = env_value.split(",") llm = ChatOllama(base_url=base_url, model=model_name) - else: + elif "diffbot" in model: model_name = "diffbot" llm = DiffbotGraphTransformer( diffbot_api_key=os.environ.get("DIFFBOT_API_KEY"), extract_types=["entities", "facts"], ) - logging.info(f"Model created - Model Version: {model_version}") + + else: + model_name, api_endpoint, api_key = env_value.split(",") + llm = ChatOpenAI( + api_key=api_key, + base_url=api_endpoint, + model=model_name, + temperature=0, + ) + + logging.info(f"Model created - Model Version: {model}") return llm, model_name @@ -135,16 +145,20 @@ def get_graph_document_list( ): futures = [] graph_document_list = [] - if llm.get_name() == "ChatOllama": - node_properties = False + + if "diffbot_api_key" in dir(llm): + llm_transformer = llm else: - node_properties = ["description"] - llm_transformer = LLMGraphTransformer( - llm=llm, - node_properties=node_properties, - allowed_nodes=allowedNodes, - allowed_relationships=allowedRelationship, - ) + if "get_name" in dir(llm) and llm.get_name() == "ChatOllama": + node_properties = False + else: + node_properties = ["description"] + llm_transformer = LLMGraphTransformer( + llm=llm, + node_properties=node_properties, + allowed_nodes=allowedNodes, + allowed_relationships=allowedRelationship, + ) with ThreadPoolExecutor(max_workers=10) as executor: for chunk in combined_chunk_document_list: chunk_doc = Document( @@ -162,8 +176,19 @@ def get_graph_document_list( def get_graph_from_llm(model, chunkId_chunkDoc_list, allowedNodes, allowedRelationship): + llm, model_name = get_llm(model) combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) + + if allowedNodes is None or allowedNodes=="": + allowedNodes =[] + else: + allowedNodes = allowedNodes.split(',') + if allowedRelationship is None or allowedRelationship=="": + allowedRelationship=[] + else: + allowedRelationship = allowedRelationship.split(',') + graph_document_list = get_graph_document_list( llm, combined_chunk_document_list, allowedNodes, allowedRelationship ) diff --git a/backend/src/main.py b/backend/src/main.py index 10ae66f2f..16eb0e622 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -8,7 +8,7 @@ from src.graphDB_dataAccess import graphDBdataAccess from src.document_sources.local_file import get_documents_from_file_by_path from src.entities.source_node import sourceNode -from src.generate_graphDocuments_from_llm import generate_graphDocuments +from src.llm import get_graph_from_llm from src.document_sources.gcs_bucket import * from src.document_sources.s3_bucket import * from src.document_sources.wikipedia import * @@ -182,7 +182,7 @@ def create_source_node_graph_url_wikipedia(graph, model, wiki_query, source_type lst_file_name.append({'fileName':obj_source_node.file_name,'fileSize':obj_source_node.file_size,'url':obj_source_node.url, 'language':obj_source_node.language, 'status':'Success'}) return lst_file_name,success_count,failed_count -def extract_graph_from_file_local_file(graph, model, merged_file_path, fileName, allowedNodes, allowedRelationship,uri): +def extract_graph_from_file_local_file(uri, userName, password, database, model, merged_file_path, fileName, allowedNodes, allowedRelationship): logging.info(f'Process file name :{fileName}') gcs_file_cache = os.environ.get('GCS_FILE_CACHE') @@ -194,9 +194,9 @@ def extract_graph_from_file_local_file(graph, model, merged_file_path, fileName, if pages==None or len(pages)==0: raise Exception(f'File content is not available for file : {file_name}') - return processing_source(graph, model, file_name, pages, allowedNodes, allowedRelationship, True, merged_file_path, uri) + return processing_source(uri, userName, password, database, model, file_name, pages, allowedNodes, allowedRelationship, True, merged_file_path) -def extract_graph_from_file_s3(graph, model, source_url, aws_access_key_id, aws_secret_access_key, allowedNodes, allowedRelationship): +def extract_graph_from_file_s3(uri, userName, password, database, model, source_url, aws_access_key_id, aws_secret_access_key, allowedNodes, allowedRelationship): if(aws_access_key_id==None or aws_secret_access_key==None): raise Exception('Please provide AWS access and secret keys') @@ -207,43 +207,43 @@ def extract_graph_from_file_s3(graph, model, source_url, aws_access_key_id, aws_ if pages==None or len(pages)==0: raise Exception(f'File content is not available for file : {file_name}') - return processing_source(graph, model, file_name, pages, allowedNodes, allowedRelationship) + return processing_source(uri, userName, password, database, model, file_name, pages, allowedNodes, allowedRelationship) -def extract_graph_from_web_page(graph, model, source_url, allowedNodes, allowedRelationship): +def extract_graph_from_web_page(uri, userName, password, database, model, source_url, allowedNodes, allowedRelationship): file_name, pages = get_documents_from_web_page(source_url) if pages==None or len(pages)==0: raise Exception(f'Content is not available for given URL : {file_name}') - return processing_source(graph, model, file_name, pages, allowedNodes, allowedRelationship) + return processing_source(uri, userName, password, database, model, file_name, pages, allowedNodes, allowedRelationship) -def extract_graph_from_file_youtube(graph, model, source_url, allowedNodes, allowedRelationship): +def extract_graph_from_file_youtube(uri, userName, password, database, model, source_url, allowedNodes, allowedRelationship): file_name, pages = get_documents_from_youtube(source_url) if pages==None or len(pages)==0: raise Exception(f'Youtube transcript is not available for file : {file_name}') - return processing_source(graph, model, file_name, pages, allowedNodes, allowedRelationship) + return processing_source(uri, userName, password, database, model, file_name, pages, allowedNodes, allowedRelationship) -def extract_graph_from_file_Wikipedia(graph, model, wiki_query, max_sources, language, allowedNodes, allowedRelationship): +def extract_graph_from_file_Wikipedia(uri, userName, password, database, model, wiki_query, max_sources, language, allowedNodes, allowedRelationship): file_name, pages = get_documents_from_Wikipedia(wiki_query, language) if pages==None or len(pages)==0: raise Exception(f'Wikipedia page is not available for file : {file_name}') - return processing_source(graph, model, file_name, pages, allowedNodes, allowedRelationship) + return processing_source(uri, userName, password, database, model, file_name, pages, allowedNodes, allowedRelationship) -def extract_graph_from_file_gcs(graph, model, gcs_project_id, gcs_bucket_name, gcs_bucket_folder, gcs_blob_filename, access_token, allowedNodes, allowedRelationship): +def extract_graph_from_file_gcs(uri, userName, password, database, model, gcs_project_id, gcs_bucket_name, gcs_bucket_folder, gcs_blob_filename, access_token, allowedNodes, allowedRelationship): file_name, pages = get_documents_from_gcs(gcs_project_id, gcs_bucket_name, gcs_bucket_folder, gcs_blob_filename, access_token) if pages==None or len(pages)==0: raise Exception(f'File content is not available for file : {file_name}') - return processing_source(graph, model, file_name, pages, allowedNodes, allowedRelationship) + return processing_source(uri, userName, password, database, model, file_name, pages, allowedNodes, allowedRelationship) -def processing_source(graph, model, file_name, pages, allowedNodes, allowedRelationship, is_uploaded_from_local=None, merged_file_path=None, uri=None): +def processing_source(uri, userName, password, database, model, file_name, pages, allowedNodes, allowedRelationship, is_uploaded_from_local=None, merged_file_path=None): """ Extracts a Neo4jGraph from a PDF file based on the model. @@ -260,6 +260,7 @@ def processing_source(graph, model, file_name, pages, allowedNodes, allowedRelat status and model as attributes. """ start_time = datetime.now() + graph = create_graph_database_connection(uri, userName, password, database) graphDb_data_Access = graphDBdataAccess(graph) result = graphDb_data_Access.get_current_status_document_node(file_name) @@ -276,99 +277,112 @@ def processing_source(graph, model, file_name, pages, allowedNodes, allowedRelat create_chunks_obj = CreateChunksofDocument(pages, graph) chunks = create_chunks_obj.split_file_into_chunks() chunkId_chunkDoc_list = create_relation_between_chunks(graph,file_name,chunks) - if result[0]['Status'] != 'Processing': - obj_source_node = sourceNode() - status = "Processing" - obj_source_node.file_name = file_name - obj_source_node.status = status - obj_source_node.total_chunks = len(chunks) - obj_source_node.total_pages = len(pages) - obj_source_node.model = model - logging.info(file_name) - logging.info(obj_source_node) - graphDb_data_Access.update_source_node(obj_source_node) - - logging.info('Update the status as Processing') - update_graph_chunk_processed = int(os.environ.get('UPDATE_GRAPH_CHUNKS_PROCESSED')) - # selected_chunks = [] - is_cancelled_status = False - job_status = "Completed" - node_count = 0 - rel_count = 0 - for i in range(0, len(chunkId_chunkDoc_list), update_graph_chunk_processed): - select_chunks_upto = i+update_graph_chunk_processed - logging.info(f'Selected Chunks upto: {select_chunks_upto}') - if len(chunkId_chunkDoc_list) <= select_chunks_upto: - select_chunks_upto = len(chunkId_chunkDoc_list) - selected_chunks = chunkId_chunkDoc_list[i:select_chunks_upto] + + if len(result) > 0: + if result[0]['Status'] != 'Processing': + obj_source_node = sourceNode() + status = "Processing" + obj_source_node.file_name = file_name + obj_source_node.status = status + obj_source_node.total_chunks = len(chunks) + obj_source_node.total_pages = len(pages) + obj_source_node.model = model + logging.info(file_name) + logging.info(obj_source_node) + graphDb_data_Access.update_source_node(obj_source_node) + + logging.info('Update the status as Processing') + update_graph_chunk_processed = int(os.environ.get('UPDATE_GRAPH_CHUNKS_PROCESSED')) + # selected_chunks = [] + is_cancelled_status = False + job_status = "Completed" + node_count = 0 + rel_count = 0 + for i in range(0, len(chunkId_chunkDoc_list), update_graph_chunk_processed): + select_chunks_upto = i+update_graph_chunk_processed + logging.info(f'Selected Chunks upto: {select_chunks_upto}') + if len(chunkId_chunkDoc_list) <= select_chunks_upto: + select_chunks_upto = len(chunkId_chunkDoc_list) + selected_chunks = chunkId_chunkDoc_list[i:select_chunks_upto] + result = graphDb_data_Access.get_current_status_document_node(file_name) + is_cancelled_status = result[0]['is_cancelled'] + logging.info(f"Value of is_cancelled : {result[0]['is_cancelled']}") + if bool(is_cancelled_status) == True: + job_status = "Cancelled" + logging.info('Exit from running loop of processing file') + exit + else: + node_count,rel_count = processing_chunks(selected_chunks,graph,uri, userName, password, database,file_name,model,allowedNodes,allowedRelationship,node_count, rel_count) + end_time = datetime.now() + processed_time = end_time - start_time + + obj_source_node = sourceNode() + obj_source_node.file_name = file_name + obj_source_node.updated_at = end_time + obj_source_node.processing_time = processed_time + obj_source_node.node_count = node_count + obj_source_node.processed_chunk = select_chunks_upto + obj_source_node.relationship_count = rel_count + graphDb_data_Access.update_source_node(obj_source_node) + result = graphDb_data_Access.get_current_status_document_node(file_name) is_cancelled_status = result[0]['is_cancelled'] - logging.info(f"Value of is_cancelled : {result[0]['is_cancelled']}") if bool(is_cancelled_status) == True: - job_status = "Cancelled" - logging.info('Exit from running loop of processing file') - exit - else: - node_count,rel_count = processing_chunks(selected_chunks,graph,file_name,model,allowedNodes,allowedRelationship,node_count, rel_count) - end_time = datetime.now() - processed_time = end_time - start_time - - obj_source_node = sourceNode() - obj_source_node.file_name = file_name - obj_source_node.updated_at = end_time - obj_source_node.processing_time = processed_time - obj_source_node.node_count = node_count - obj_source_node.processed_chunk = select_chunks_upto - obj_source_node.relationship_count = rel_count - graphDb_data_Access.update_source_node(obj_source_node) - - result = graphDb_data_Access.get_current_status_document_node(file_name) - is_cancelled_status = result[0]['is_cancelled'] - if bool(is_cancelled_status) == True: - logging.info(f'Is_cancelled True at the end extraction') - job_status = 'Cancelled' - logging.info(f'Job Status at the end : {job_status}') - end_time = datetime.now() - processed_time = end_time - start_time - obj_source_node = sourceNode() - obj_source_node.file_name = file_name - obj_source_node.status = job_status - obj_source_node.processing_time = processed_time + logging.info(f'Is_cancelled True at the end extraction') + job_status = 'Cancelled' + logging.info(f'Job Status at the end : {job_status}') + end_time = datetime.now() + processed_time = end_time - start_time + obj_source_node = sourceNode() + obj_source_node.file_name = file_name + obj_source_node.status = job_status + obj_source_node.processing_time = processed_time - graphDb_data_Access.update_source_node(obj_source_node) - logging.info('Updated the nodeCount and relCount properties in Document node') - logging.info(f'file:{file_name} extraction has been completed') + graphDb_data_Access.update_source_node(obj_source_node) + logging.info('Updated the nodeCount and relCount properties in Document node') + logging.info(f'file:{file_name} extraction has been completed') - # merged_file_path have value only when file uploaded from local - - if is_uploaded_from_local: - gcs_file_cache = os.environ.get('GCS_FILE_CACHE') - if gcs_file_cache == 'True': - folder_name = create_gcs_bucket_folder_name_hashed(uri, file_name) - delete_file_from_gcs(BUCKET_UPLOAD,folder_name,file_name) - else: - delete_uploaded_local_file(merged_file_path, file_name) + # merged_file_path have value only when file uploaded from local - return { - "fileName": file_name, - "nodeCount": node_count, - "relationshipCount": rel_count, - "processingTime": round(processed_time.total_seconds(),2), - "status" : job_status, - "model" : model, - "success_count" : 1 - } + if is_uploaded_from_local: + gcs_file_cache = os.environ.get('GCS_FILE_CACHE') + if gcs_file_cache == 'True': + folder_name = create_gcs_bucket_folder_name_hashed(uri, file_name) + delete_file_from_gcs(BUCKET_UPLOAD,folder_name,file_name) + else: + delete_uploaded_local_file(merged_file_path, file_name) + + return { + "fileName": file_name, + "nodeCount": node_count, + "relationshipCount": rel_count, + "processingTime": round(processed_time.total_seconds(),2), + "status" : job_status, + "model" : model, + "success_count" : 1 + } + else: + logging.info('File does not process because it\'s already in Processing status') else: - logging.info('File does not process because it\'s already in Processing status') + error_message = "Unable to get the status of docuemnt node." + logging.error(error_message) + raise Exception(error_message) -def processing_chunks(chunkId_chunkDoc_list,graph,file_name,model,allowedNodes,allowedRelationship, node_count, rel_count): +def processing_chunks(chunkId_chunkDoc_list,graph,uri, userName, password, database,file_name,model,allowedNodes,allowedRelationship, node_count, rel_count): #create vector index and update chunk node with embedding + if graph is not None: + if graph._driver._closed: + graph = create_graph_database_connection(uri, userName, password, database) + else: + graph = create_graph_database_connection(uri, userName, password, database) + update_embedding_create_vector_index( graph, chunkId_chunkDoc_list, file_name) logging.info("Get graph document list from models") - graph_documents = generate_graphDocuments(model, graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) - save_graphDocuments_in_neo4j(graph, graph_documents) - chunks_and_graphDocuments_list = get_chunk_and_graphDocument(graph_documents, chunkId_chunkDoc_list) + graph_documents = get_graph_from_llm(model, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) + cleaned_graph_documents = handle_backticks_nodes_relationship_id_type(graph_documents) + save_graphDocuments_in_neo4j(graph, cleaned_graph_documents) + chunks_and_graphDocuments_list = get_chunk_and_graphDocument(cleaned_graph_documents, chunkId_chunkDoc_list) merge_relationship_between_chunk_and_entites(graph, chunks_and_graphDocuments_list) # return graph_documents @@ -420,7 +434,7 @@ def update_graph(graph): graph_DB_dataAccess.update_KNN_graph() -def connection_check(graph): +def connection_check_and_get_vector_dimensions(graph): """ Args: uri: URI of the graph to extract @@ -431,7 +445,7 @@ def connection_check(graph): Returns a status of connection from NEO4j is success or failure """ graph_DB_dataAccess = graphDBdataAccess(graph) - return graph_DB_dataAccess.connection_check() + return graph_DB_dataAccess.connection_check_and_get_vector_dimensions() def merge_chunks_local(file_name, total_chunks, chunk_dir, merged_dir): @@ -447,10 +461,9 @@ def merge_chunks_local(file_name, total_chunks, chunk_dir, merged_dir): shutil.copyfileobj(chunk_file, write_stream) os.unlink(chunk_file_path) # Delete the individual chunk file after merging logging.info("Chunks merged successfully and return file size") - file_name, pages, file_extension = get_documents_from_file_by_path(merged_file_path,file_name) - pdf_total_pages = pages[0].metadata['total_pages'] + file_size = os.path.getsize(merged_file_path) - return pdf_total_pages,file_size + return file_size @@ -476,9 +489,8 @@ def upload_file(graph, model, chunk, chunk_number:int, total_chunks:int, origina # If this is the last chunk, merge all chunks into a single file if gcs_file_cache == 'True': file_size = merge_file_gcs(BUCKET_UPLOAD, originalname, folder_name, int(total_chunks)) - total_pages = 1 else: - total_pages, file_size = merge_chunks_local(originalname, int(total_chunks), chunk_dir, merged_dir) + file_size = merge_chunks_local(originalname, int(total_chunks), chunk_dir, merged_dir) logging.info("File merged successfully") file_extension = originalname.split('.')[-1] @@ -488,12 +500,11 @@ def upload_file(graph, model, chunk, chunk_number:int, total_chunks:int, origina obj_source_node.file_size = file_size obj_source_node.file_source = 'local file' obj_source_node.model = model - obj_source_node.total_pages = total_pages obj_source_node.created_at = datetime.now() graphDb_data_Access = graphDBdataAccess(graph) graphDb_data_Access.create_source_node(obj_source_node) - return {'file_size': file_size, 'total_pages': total_pages, 'file_name': originalname, 'file_extension':file_extension, 'message':f"Chunk {chunk_number}/{total_chunks} saved"} + return {'file_size': file_size, 'file_name': originalname, 'file_extension':file_extension, 'message':f"Chunk {chunk_number}/{total_chunks} saved"} return f"Chunk {chunk_number}/{total_chunks} saved" def get_labels_and_relationtypes(graph): diff --git a/backend/src/make_relationships.py b/backend/src/make_relationships.py index 175fb470a..3045649d5 100644 --- a/backend/src/make_relationships.py +++ b/backend/src/make_relationships.py @@ -66,6 +66,10 @@ def update_embedding_create_vector_index(graph, chunkId_chunkDoc_list, file_name # } # ) # logging.info('create vector index on chunk embedding') + result = graph.query("SHOW INDEXES YIELD * WHERE labelsOrTypes = ['__Chunk__'] and name = 'vector'") + if result: + logging.info(f"vector index dropped for 'Chunk'") + graph.query("DROP INDEX vector IF EXISTS;") graph.query("""CREATE VECTOR INDEX `vector` if not exists for (c:Chunk) on (c.embedding) OPTIONS {indexConfig: { @@ -124,9 +128,9 @@ def create_relation_between_chunks(graph, file_name, chunks: List[Document])->li if 'page_number' in chunk.metadata: chunk_data['page_number'] = chunk.metadata['page_number'] - if 'start_time' in chunk.metadata and 'end_time' in chunk.metadata: - chunk_data['start_time'] = chunk.metadata['start_time'] - chunk_data['end_time'] = chunk.metadata['end_time'] + if 'start_timestamp' in chunk.metadata and 'end_timestamp' in chunk.metadata: + chunk_data['start_time'] = chunk.metadata['start_timestamp'] + chunk_data['end_time'] = chunk.metadata['end_timestamp'] batch_data.append(chunk_data) diff --git a/backend/src/openAI_llm.py b/backend/src/openAI_llm.py deleted file mode 100644 index d9b0c9fb4..000000000 --- a/backend/src/openAI_llm.py +++ /dev/null @@ -1,24 +0,0 @@ -from langchain_community.graphs import Neo4jGraph -import os -from dotenv import load_dotenv -import logging -import concurrent.futures -from concurrent.futures import ThreadPoolExecutor -from langchain_experimental.graph_transformers import LLMGraphTransformer -from src.llm import get_graph_document_list, get_combined_chunks, get_llm - -load_dotenv() -logging.basicConfig(format='%(asctime)s - %(message)s',level='INFO') - -def get_graph_from_OpenAI(model_version, graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship): - futures=[] - graph_document_list=[] - - combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) - - llm,model_name = get_llm(model_version) - return get_graph_document_list(llm, combined_chunk_document_list, allowedNodes, allowedRelationship) - - - - diff --git a/backend/src/post_processing.py b/backend/src/post_processing.py index 2271ffdf8..fa582e107 100644 --- a/backend/src/post_processing.py +++ b/backend/src/post_processing.py @@ -10,7 +10,11 @@ FULL_TEXT_QUERY = "CREATE FULLTEXT INDEX entities FOR (n{labels_str}) ON EACH [n.id, n.description];" FILTER_LABELS = ["Chunk","Document"] -def create_fulltext(uri, username, password, database): + +HYBRID_SEARCH_INDEX_DROP_QUERY = "DROP INDEX keyword IF EXISTS;" +HYBRID_SEARCH_FULL_TEXT_QUERY = "CREATE FULLTEXT INDEX keyword FOR (n:Chunk) ON EACH [n.text]" + +def create_fulltext(uri, username, password, database,type): start_time = time.time() logging.info("Starting the process of creating a full-text index.") @@ -26,28 +30,37 @@ def create_fulltext(uri, username, password, database): with driver.session() as session: try: start_step = time.time() - session.run(DROP_INDEX_QUERY) + if type == "entities": + drop_query = DROP_INDEX_QUERY + else: + drop_query = HYBRID_SEARCH_INDEX_DROP_QUERY + session.run(drop_query) logging.info(f"Dropped existing index (if any) in {time.time() - start_step:.2f} seconds.") except Exception as e: logging.error(f"Failed to drop index: {e}") return try: - start_step = time.time() - result = session.run(LABELS_QUERY) - labels = [record["label"] for record in result] - - for label in FILTER_LABELS: - if label in labels: - labels.remove(label) - - labels_str = ":" + "|".join([f"`{label}`" for label in labels]) - logging.info(f"Fetched labels in {time.time() - start_step:.2f} seconds.") + if type == "entities": + start_step = time.time() + result = session.run(LABELS_QUERY) + labels = [record["label"] for record in result] + + for label in FILTER_LABELS: + if label in labels: + labels.remove(label) + + labels_str = ":" + "|".join([f"`{label}`" for label in labels]) + logging.info(f"Fetched labels in {time.time() - start_step:.2f} seconds.") except Exception as e: logging.error(f"Failed to fetch labels: {e}") return try: start_step = time.time() - session.run(FULL_TEXT_QUERY.format(labels_str=labels_str)) + if type == "entities": + fulltext_query = FULL_TEXT_QUERY.format(labels_str=labels_str) + else: + fulltext_query = HYBRID_SEARCH_FULL_TEXT_QUERY + session.run(fulltext_query) logging.info(f"Created full-text index in {time.time() - start_step:.2f} seconds.") except Exception as e: logging.error(f"Failed to create full-text index: {e}") @@ -59,7 +72,6 @@ def create_fulltext(uri, username, password, database): logging.info("Driver closed.") logging.info(f"Process completed in {time.time() - start_time:.2f} seconds.") - def create_entity_embedding(graph:Neo4jGraph): rows = fetch_entities_for_embedding(graph) for i in range(0, len(rows), 1000): diff --git a/backend/src/shared/common_fn.py b/backend/src/shared/common_fn.py index b549f3b29..b0dbe2f5f 100644 --- a/backend/src/shared/common_fn.py +++ b/backend/src/shared/common_fn.py @@ -96,6 +96,26 @@ def load_embedding_model(embedding_model_name: str): def save_graphDocuments_in_neo4j(graph:Neo4jGraph, graph_document_list:List[GraphDocument]): # graph.add_graph_documents(graph_document_list, baseEntityLabel=True) graph.add_graph_documents(graph_document_list) + +def handle_backticks_nodes_relationship_id_type(graph_document_list:List[GraphDocument]): + for graph_document in graph_document_list: + # Clean node id and types + cleaned_nodes = [] + for node in graph_document.nodes: + if node.type.strip() and node.id.strip(): + node.type = node.type.replace('`', '') + cleaned_nodes.append(node) + # Clean relationship id types and source/target node id and types + cleaned_relationships = [] + for rel in graph_document.relationships: + if rel.type.strip() and rel.source.id.strip() and rel.source.type.strip() and rel.target.id.strip() and rel.target.type.strip(): + rel.type = rel.type.replace('`', '') + rel.source.type = rel.source.type.replace('`', '') + rel.target.type = rel.target.type.replace('`', '') + cleaned_relationships.append(rel) + graph_document.relationships = cleaned_relationships + graph_document.nodes = cleaned_nodes + return graph_document_list def delete_uploaded_local_file(merged_file_path, file_name): file_path = Path(merged_file_path) diff --git a/backend/src/shared/constants.py b/backend/src/shared/constants.py index 7a1983e28..c5f8e98a4 100644 --- a/backend/src/shared/constants.py +++ b/backend/src/shared/constants.py @@ -4,26 +4,67 @@ "gemini-1.5-pro": "gemini-1.5-pro-preview-0514", "openai-gpt-4": "gpt-4-0125-preview", "diffbot" : "gpt-4o", + "openai-gpt-4o-mini": "gpt-4o-mini", "openai-gpt-4o":"gpt-4o", "groq-llama3" : "llama3-70b-8192" } -OPENAI_MODELS = ["openai-gpt-3.5", "openai-gpt-4o"] +OPENAI_MODELS = ["openai-gpt-3.5", "openai-gpt-4o", "openai-gpt-4o-mini"] GEMINI_MODELS = ["gemini-1.0-pro", "gemini-1.5-pro"] GROQ_MODELS = ["groq-llama3"] BUCKET_UPLOAD = 'llm-graph-builder-upload' BUCKET_FAILED_FILE = 'llm-graph-builder-failed' PROJECT_ID = 'llm-experiments-387609' +GRAPH_CHUNK_LIMIT = 50 + +#query +GRAPH_QUERY = """ +MATCH docs = (d:Document) +WHERE d.fileName IN $document_names +WITH docs, d ORDER BY d.createdAt DESC +// fetch chunks for documents, currently with limit +CALL {{ + WITH d + OPTIONAL MATCH chunks=(d)<-[:PART_OF|FIRST_CHUNK]-(c:Chunk) + RETURN c, chunks LIMIT {graph_chunk_limit} +}} + +WITH collect(distinct docs) as docs, collect(distinct chunks) as chunks, collect(distinct c) as selectedChunks +WITH docs, chunks, selectedChunks +// select relationships between selected chunks +WITH *, +[ c in selectedChunks | [p=(c)-[:NEXT_CHUNK|SIMILAR]-(other) WHERE other IN selectedChunks | p]] as chunkRels + +// fetch entities and relationships between entities +CALL {{ + WITH selectedChunks + UNWIND selectedChunks as c + + OPTIONAL MATCH entities=(c:Chunk)-[:HAS_ENTITY]->(e) + OPTIONAL MATCH entityRels=(e)--(e2:!Chunk) WHERE exists {{ + (e2)<-[:HAS_ENTITY]-(other) WHERE other IN selectedChunks + }} + RETURN collect(entities) as entities, collect(entityRels) as entityRels +}} + +WITH apoc.coll.flatten(docs + chunks + chunkRels + entities + entityRels, true) as paths +// distinct nodes and rels +CALL {{ WITH paths UNWIND paths AS path UNWIND nodes(path) as node WITH distinct node + RETURN collect(node /* {{.*, labels:labels(node), elementId:elementId(node), embedding:null, text:null}} */) AS nodes }} +CALL {{ WITH paths UNWIND paths AS path UNWIND relationships(path) as rel RETURN collect(distinct rel) AS rels }} +RETURN nodes, rels + +""" ## CHAT SETUP CHAT_MAX_TOKENS = 1000 CHAT_SEARCH_KWARG_K = 3 -CHAT_SEARCH_KWARG_SCORE_THRESHOLD = 0.7 +CHAT_SEARCH_KWARG_SCORE_THRESHOLD = 0.5 CHAT_DOC_SPLIT_SIZE = 3000 CHAT_EMBEDDING_FILTER_SCORE_THRESHOLD = 0.10 CHAT_TOKEN_CUT_OFF = { ("openai-gpt-3.5",'azure_ai_gpt_35',"gemini-1.0-pro","gemini-1.5-pro","groq-llama3",'groq_llama3_70b','anthropic_claude_3_5_sonnet','fireworks_llama_v3_70b','bedrock_claude_3_5_sonnet', ) : 4, - ("openai-gpt-4","diffbot" ,'azure_ai_gpt_4o',"openai-gpt-4o") : 28, + ("openai-gpt-4","diffbot" ,'azure_ai_gpt_4o',"openai-gpt-4o", "openai-gpt-4o-mini") : 28, ("ollama_llama3") : 2 } @@ -91,10 +132,10 @@ # VECTOR_GRAPH_SEARCH_QUERY=""" # WITH node as chunk, score -# MATCH (chunk)-[:PART_OF]->(d:Document) +# MATCH (chunk)-[:__PART_OF__]->(d:__Document__) # CALL { WITH chunk -# MATCH (chunk)-[:HAS_ENTITY]->(e) -# MATCH path=(e)(()-[rels:!HAS_ENTITY&!PART_OF]-()){0,2}(:!Chunk&!Document) +# MATCH (chunk)-[:__HAS_ENTITY__]->(e) +# MATCH path=(e)(()-[rels:!__HAS_ENTITY__&!__PART_OF__]-()){0,2}(:!__Chunk__&!__Document__) # UNWIND rels as r # RETURN collect(distinct r) as rels # } @@ -114,19 +155,19 @@ # VECTOR_GRAPH_SEARCH_QUERY = """ # WITH node as chunk, score # // find the document of the chunk -# MATCH (chunk)-[:PART_OF]->(d:Document) +# MATCH (chunk)-[:__PART_OF__]->(d:__Document__) # // fetch entities # CALL { WITH chunk # // entities connected to the chunk # // todo only return entities that are actually in the chunk, remember we connect all extracted entities to all chunks -# MATCH (chunk)-[:HAS_ENTITY]->(e) +# MATCH (chunk)-[:__HAS_ENTITY__]->(e) # // depending on match to query embedding either 1 or 2 step expansion # WITH CASE WHEN true // vector.similarity.cosine($embedding, e.embedding ) <= 0.95 # THEN -# collect { MATCH path=(e)(()-[rels:!HAS_ENTITY&!PART_OF]-()){0,1}(:!Chunk&!Document) RETURN path } +# collect { MATCH path=(e)(()-[rels:!__HAS_ENTITY__&!__PART_OF__]-()){0,1}(:!Chunk&!__Document__) RETURN path } # ELSE -# collect { MATCH path=(e)(()-[rels:!HAS_ENTITY&!PART_OF]-()){0,2}(:!Chunk&!Document) RETURN path } +# collect { MATCH path=(e)(()-[rels:!__HAS_ENTITY__&!__PART_OF__]-()){0,2}(:!Chunk&!__Document__) RETURN path } # END as paths # RETURN collect{ unwind paths as p unwind relationships(p) as r return distinct r} as rels, @@ -234,4 +275,5 @@ as text,entities RETURN text, avg_score as score, {{length:size(text), source: COALESCE( CASE WHEN d.url CONTAINS "None" THEN d.fileName ELSE d.url END, d.fileName), chunkdetails: chunkdetails}} AS metadata -""" \ No newline at end of file +""" +YOUTUBE_CHUNK_SIZE_SECONDS = 60 diff --git a/backend/test_integrationqa.py b/backend/test_integrationqa.py index 467b0615f..20f3effb0 100644 --- a/backend/test_integrationqa.py +++ b/backend/test_integrationqa.py @@ -1,190 +1,172 @@ +import os +import shutil +import logging +import pandas as pd +from datetime import datetime as dt +from dotenv import load_dotenv + from score import * from src.main import * -import logging from src.QA_integration_new import QA_RAG from langserve import add_routes -import asyncio -import os +# Load environment variables if needed +load_dotenv() -uri ='' -userName ='' -password ='' -model ='OpenAI GPT 3.5' -database ='' +# Constants +URI = '' +USERNAME = '' +PASSWORD = '' +DATABASE = 'neo4j' CHUNK_DIR = os.path.join(os.path.dirname(__file__), "chunks") MERGED_DIR = os.path.join(os.path.dirname(__file__), "merged_files") -graph = create_graph_database_connection(uri, userName, password, database) -def test_graph_from_file_local_file(): +# Initialize database connection +graph = create_graph_database_connection(URI, USERNAME, PASSWORD, DATABASE) + +def create_source_node_local(graph, model, file_name): + """Creates a source node for a local file.""" + source_node = sourceNode() + source_node.file_name = file_name + source_node.file_type = 'pdf' + source_node.file_size = '1087' + source_node.file_source = 'local file' + source_node.model = model + source_node.created_at = dt.now() + graphDB_data_Access = graphDBdataAccess(graph) + graphDB_data_Access.create_source_node(source_node) + return source_node + +def test_graph_from_file_local(model_name): + """Test graph creation from a local file.""" file_name = 'About Amazon.pdf' - #shutil.copyfile('data/Bank of America Q23.pdf', 'backend/src/merged_files/Bank of America Q23.pdf') - shutil.copyfile('/workspaces/llm-graph-builder/data/About Amazon.pdf', '/workspaces/llm-graph-builder/backend/merged_files/About Amazon.pdf') - obj_source_node = sourceNode() - obj_source_node.file_name = file_name - obj_source_node.file_type = 'pdf' - obj_source_node.file_size = '1087' - obj_source_node.file_source = 'local file' - obj_source_node.model = model - obj_source_node.created_at = datetime.now() - graphDb_data_Access = graphDBdataAccess(graph) - graphDb_data_Access.create_source_node(obj_source_node) - merged_file_path = os.path.join(MERGED_DIR,file_name) - - local_file_result = extract_graph_from_file_local_file(graph, model, file_name,merged_file_path, '', '') + shutil.copyfile('/workspaces/llm-graph-builder/backend/files/About Amazon.pdf', + os.path.join(MERGED_DIR, file_name)) + create_source_node_local(graph, model_name, file_name) + merged_file_path = os.path.join(MERGED_DIR, file_name) + + local_file_result = extract_graph_from_file_local_file( + URI, USERNAME, PASSWORD, DATABASE, model_name, merged_file_path, file_name, '', '' + ) + logging.info("Local file processing complete") print(local_file_result) - - logging.info("Info: ") + try: - assert local_file_result['status'] == 'Completed' and local_file_result['nodeCount']>5 and local_file_result['relationshipCount']>10 + assert local_file_result['status'] == 'Completed' + assert local_file_result['nodeCount'] > 0 + assert local_file_result['relationshipCount'] > 0 print("Success") except AssertionError as e: print("Fail: ", e) -def test_graph_from_file_local_file_failed(): - file_name = 'Not_exist.pdf' - try: - obj_source_node = sourceNode() - obj_source_node.file_name = file_name - obj_source_node.file_type = 'pdf' - obj_source_node.file_size = '0' - obj_source_node.file_source = 'local file' - obj_source_node.model = model - obj_source_node.created_at = datetime.now() - graphDb_data_Access = graphDBdataAccess(graph) - graphDb_data_Access.create_source_node(obj_source_node) - - local_file_result = extract_graph_from_file_local_file(graph, model, file_name,merged_file_path, '', '') - - print(local_file_result) - except AssertionError as e: - print('Failed due to file does not exist means not uploaded or accidentaly deleteled from server') - print("Failed: Error from extract function ", e) + return local_file_result -# Check for Wikipedia file to be success -def test_graph_from_Wikipedia(): - wiki_query = 'Norway' +def test_graph_from_wikipedia(model_name): + """Test graph creation from a Wikipedia page.""" + wiki_query = 'https://en.wikipedia.org/wiki/Ram_Mandir' source_type = 'Wikipedia' - create_source_node_graph_url_wikipedia(graph, model, wiki_query, source_type) - wikiresult = extract_graph_from_file_Wikipedia(graph, model, wiki_query, 1, '', '') - logging.info("Info: Wikipedia test done") - print(wikiresult) - try: - assert wikiresult['status'] == 'Completed' and wikiresult['nodeCount']>10 and wikiresult['relationshipCount']>15 - print("Success") - except AssertionError as e: - print("Fail ", e) - -def test_graph_from_Wikipedia_failed(): - wiki_query = 'Test QA 123456' - source_type = 'Wikipedia' - try: - logging.info("Created source node for wikipedia") - create_source_node_graph_url_wikipedia(graph, model, wiki_query, source_type) - except AssertionError as e: - print("Fail ", e) - - -# Check for Youtube_video to be Success -def test_graph_from_youtube_video(): - url = 'https://www.youtube.com/watch?v=T-qy-zPWgqA' - source_type = 'youtube' + file_name = "Ram_Mandir" + create_source_node_graph_url_wikipedia(graph, model_name, wiki_query, source_type) - create_source_node_graph_url_youtube(graph, model,url , source_type) - youtuberesult = extract_graph_from_file_youtube(graph, model, url, '', '') + wiki_result = extract_graph_from_file_Wikipedia( + URI, USERNAME, PASSWORD, DATABASE, model_name, file_name, 1, 'en', '', '' + ) + logging.info("Wikipedia test done") + print(wiki_result) - logging.info("Info: Youtube Video test done") - print(youtuberesult) try: - assert youtuberesult['status'] == 'Completed' and youtuberesult['nodeCount']>60 and youtuberesult['relationshipCount']>40 + assert wiki_result['status'] == 'Completed' + assert wiki_result['nodeCount'] > 0 + assert wiki_result['relationshipCount'] > 0 print("Success") except AssertionError as e: - print("Failed ", e) - -# Check for Youtube_video to be Failed + print("Fail: ", e) + + return wiki_result -def test_graph_from_youtube_video_failed(): - url = 'https://www.youtube.com/watch?v=U9mJuUkhUzk' +def test_graph_from_youtube_video(model_name): + """Test graph creation from a YouTube video.""" + source_url = 'https://www.youtube.com/watch?v=T-qy-zPWgqA' source_type = 'youtube' - create_source_node_graph_url_youtube(graph, model,url , source_type) - youtuberesult = extract_graph_from_file_youtube(graph, model, url, ',', ',') - # print(result) - print(youtuberesult) - try: - assert youtuberesult['status'] == 'Completed' - print("Success") - except AssertionError as e: - print("Failed ", e) - -# Check for the GCS file to be uploaded, process and completed - -def test_graph_from_file_test_gcs(): - - bucket_name = 'llm_graph_transformer_test' - folder_name = 'technology' - source_type ='gcs bucket' - file_name = 'Neuralink brain chip patient playing chess.pdf' - create_source_node_graph_url_gcs(graph, model, bucket_name, folder_name, source_type) - gcsresult = extract_graph_from_file_gcs(graph, model, bucket_name, folder_name, file_name, '', '') - - logging.info("Info") - print(gcsresult) - + create_source_node_graph_url_youtube(graph, model_name, source_url, source_type) + youtube_result = extract_graph_from_file_youtube( + URI, USERNAME, PASSWORD, DATABASE, model_name, source_url, '', '' + ) + logging.info("YouTube Video test done") + print(youtube_result) + try: - assert gcsresult['status'] == 'Completed' and gcsresult['nodeCount']>10 and gcsresult['relationshipCount']>5 + assert youtube_result['status'] == 'Completed' + assert youtube_result['nodeCount'] > 1 + assert youtube_result['relationshipCount'] > 1 print("Success") except AssertionError as e: - print("Failed ", e) + print("Failed: ", e) -def test_graph_from_file_test_gcs_failed(): + return youtube_result - bucket_name = 'llm_graph_transformer_neo' - folder_name = 'technology' - source_type ='gcs bucket' - # file_name = 'Neuralink brain chip patient playing chess.pdf' - try: - create_source_node_graph_url_gcs(graph, model, bucket_name, folder_name, source_type) - print("GCS: Create source node failed due to bucket not exist") - except AssertionError as e: - print("Failed ", e) - -def test_graph_from_file_test_s3_failed(): - source_url = 's3://development-llm-graph-builder-models/' - try: - create_source_node_graph_url_s3(graph,model,source_url,'test123','pwd123') - # assert result['status'] == 'Failed' - # print("S3 created source node failed die to wrong access key id and secret") - except AssertionError as e: - print("Failed ", e) - -# Check the Functionality of Chatbot QnA -def test_chatbot_QnA(): - QA_n_RAG = QA_RAG(graph, model,'who is patrick pichette',1 ) - +def test_chatbot_qna(model_name, mode='graph+vector'): + """Test chatbot QnA functionality for different modes.""" + QA_n_RAG = QA_RAG(graph, model_name, 'Tell me about amazon', '[]', 1, mode) print(QA_n_RAG) print(len(QA_n_RAG['message'])) + try: assert len(QA_n_RAG['message']) > 20 print("Success") except AssertionError as e: - print("Failed ", e) - + print("Failed: ", e) + + return QA_n_RAG + +def compare_graph_results(results): + """ + Compare graph results across different models. + Add custom logic here to compare graph data, nodes, and relationships. + """ + # Placeholder logic for comparison + print("Comparing results...") + for i in range(len(results) - 1): + result_a = results[i] + result_b = results[i + 1] + if result_a == result_b: + print(f"Result {i} is identical to result {i+1}") + else: + print(f"Result {i} differs from result {i+1}") + +def run_tests(): + final_list = [] + error_list = [] + models = [ + 'openai-gpt-3.5', 'openai-gpt-4o', 'openai-gpt-4o-mini', 'azure_ai_gpt_35', + 'azure_ai_gpt_4o', 'anthropic_claude_3_5_sonnet', 'fireworks_v3p1_405b', + 'fireworks_llama_v3_70b', 'ollama_llama3', 'bedrock_claude_3_5_sonnet' + ] + + for model_name in models: + try: + final_list.append(test_graph_from_file_local(model_name)) + final_list.append(test_graph_from_wikipedia(model_name)) + final_list.append(test_graph_from_youtube_video(model_name)) + final_list.append(test_chatbot_qna(model_name)) + final_list.append(test_chatbot_qna(model_name, mode='vector')) + final_list.append(test_chatbot_qna(model_name, mode='hybrid')) + except Exception as e: + error_list.append((model_name, str(e))) + #Compare and log diffrences in graph results + compare_graph_results(final_list) # Pass the final_list to comapre_graph_results + + # Save final results to CSV + df = pd.DataFrame(final_list) + df['execution_date'] = dt.today().strftime('%Y-%m-%d') + df.to_csv(f"Integration_TestResult_{dt.now().strftime('%Y%m%d_%H%M%S')}.csv", index=False) + + # Save error details to CSV + df_errors = pd.DataFrame(error_list, columns=['Model', 'Error']) + df_errors['execution_date'] = dt.today().strftime('%Y-%m-%d') + df_errors.to_csv(f"Error_details_{dt.now().strftime('%Y%m%d_%H%M%S')}.csv", index=False) if __name__ == "__main__": - - test_graph_from_file_local_file() # local file Success Test Case - #test_graph_from_file_local_file_failed() # local file Failed Test Case - - test_graph_from_Wikipedia() # Wikipedia Success Test Case - #test_graph_from_Wikipedia_failed() # Wikipedia Failed Test Case - - test_graph_from_youtube_video() # Youtube Success Test Case - #test_graph_from_youtube_video_failed # Failed Test case - - test_graph_from_file_test_gcs() # GCS Success Test Case - test_chatbot_QnA() - - #test_graph_from_file_test_s3_failed() # S3 Failed Test Case - \ No newline at end of file + run_tests() \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2578073b0..7761704c7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: context: ./backend dockerfile: Dockerfile volumes: - - ./backend:/app + - ./backend:/code environment: - NEO4J_URI=${NEO4J_URI-neo4j://database:7687} - NEO4J_PASSWORD=${NEO4J_PASSWORD-password} @@ -34,6 +34,8 @@ services: # - LLM_MODEL_CONFIG_bedrock_claude_3_5_sonnet=${LLM_MODEL_CONFIG_bedrock_claude_3_5_sonnet-} # - LLM_MODEL_CONFIG_fireworks_qwen_72b=${LLM_MODEL_CONFIG_fireworks_qwen_72b-} - LLM_MODEL_CONFIG_ollama_llama3=${LLM_MODEL_CONFIG_ollama_llama3-} + # env_file: + # - ./backend/.env container_name: backend extra_hosts: - host.docker.internal:host-gateway @@ -49,19 +51,22 @@ services: context: ./frontend dockerfile: Dockerfile args: - - BACKEND_API_URL=${BACKEND_API_URL-http://localhost:8000} - - REACT_APP_SOURCES=${REACT_APP_SOURCES-local,youtube,wiki,s3} - - LLM_MODELS=${LLM_MODELS-diffbot,openai-gpt-3.5,openai-gpt-4o} - - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID-""} - - BLOOM_URL=${BLOOM_URL-https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true} - - TIME_PER_CHUNK=${TIME_PER_CHUNK-4} - - TIME_PER_PAGE=${TIME_PER_PAGE-50} - - CHUNK_SIZE=${CHUNK_SIZE-5242880} - - ENV=${ENV-DEV} - - CHAT_MODES=${CHAT_MODES-""} + - VITE_BACKEND_API_URL=${VITE_BACKEND_API_URL-http://localhost:8000} + - VITE_REACT_APP_SOURCES=${VITE_REACT_APP_SOURCES-local,youtube,wiki,s3} + - VITE_LLM_MODELS=${VITE_LLM_MODELS-diffbot,openai-gpt-3.5,openai-gpt-4o} + - VITE_GOOGLE_CLIENT_ID=${VITE_GOOGLE_CLIENT_ID-""} + - VITE_BLOOM_URL=${VITE_BLOOM_URL-https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true} + - VITE_TIME_PER_PAGE=${VITE_TIME_PER_PAGE-50} + - VITE_CHUNK_SIZE=${VITE_CHUNK_SIZE-5242880} + - VITE_LARGE_FILE_SIZE=${VITE_LARGE_FILE_SIZE-5242880} + - VITE_ENV=${VITE_ENV-DEV} + - VITE_CHAT_MODES=${VITE_CHAT_MODES-""} + - VITE_BATCH_SIZE=${VITE_BATCH_SIZE-2} volumes: - ./frontend:/app - /app/node_modules + # env_file: + # - ./frontend/.env container_name: frontend ports: - "8080:8080" diff --git a/docs/frontend/frontend_docs.adoc b/docs/frontend/frontend_docs.adoc index e69de29bb..9eaf1e4bc 100644 --- a/docs/frontend/frontend_docs.adoc +++ b/docs/frontend/frontend_docs.adoc @@ -0,0 +1,609 @@ += LLM Knowledge Graph Builder Frontend + +== Objective + +This document provides a comprehensive guide for developers on how we build a React application integrated with Neo4j Aura for graph database functionalities. The application allows users to connect to a Neo4j Aura instance and we show you how to automatically create a graph from the unstructured text. We allow users to upload documents locally and from cloud buckets, YouTube videos, and Wikipedia pages, configure a graph schema, extract the lexical, entity and knowledge graph, visualize the extracted graph, ask questions and see the details that were used to generate the answers. + +== Architecture Structure + +* For Knowledge Graph builder App: + ** React JS – Application logic. + ** Axios – for network calls and handling responses + ** Styled Components – To handle CSS in JS – Where we write all CSS ourselves, Or Tailwind CSS – 3rd party CSS classes to speed up development. + ** LongPooling: Long polling can be conceptualized as the simplest way to maintain a steady connection between a client and a server.It holds the request for a period if it has no response to send it back.It regularly updates clients with new information like updating a status, processed chunks every minute with new data. + ** SSEs are the best options when the server generates the data in a loop and sends multiple events to the clients and if we need real-time traffic from the server to the client. + +== Folders + + . + ├── Components + | ├─ ChatBot + | | ├─ ChatBotInfoModal + | | ├─ ChatModeToggle + | | ├─ ExpandedChatButtonContainer + | ├─ Data Sources + | | ├─ AWS + | | ├─ GCS + | | ├─ Local + | | ├─ WebSources + | | | ├─Web + | | | ├─Wikipedia + | | | ├─Youtube + | ├─ Graph + | | ├─ GraphViewButton + | | ├─ GraphViewModal + | | ├─ LegendsChip + | ├─ Layout + | | ├─ Content + | | ├─ DrawerChatbot + | | ├─ DrawerDropzone + | | ├─ Header + | | ├─ PageLayout + | | ├─ SideNav + | ├─ Popups + | | ├─ ConnectionModal + | | ├─ DeletePopup + | | ├─ GraphEnhancementDialog + | | ├─ LargeFilePopup + | | ├─ Settings + | ├─ UI + | | ├─ Alert + | | ├─ ButtonWithTooltip + | | ├─ CustomButton + | | ├─ CustomModal + | | ├─ CustomProgressBar + | | ├─ CustomSourceInput + | | ├─ Dropdown + | | ├─ ErrorBoundary + | | ├─ FileTable + | | ├─ GenericSourceButton + | | ├─ GenericSourceModal + | | ├─ HoverableLink + | | ├─ IconButtonTooltip + | | ├─ Legend + | | ├─ Menu + | | ├─ QuickStarter + ├── HOC + | ├─ SettingModalHOC + ├── Assets + | ├─ images + | | ├─ Application Images + | ├─ chatbotMessages.json + | ├─ schema.json + ├── Context + | ├─ Alert + | ├─ ThemeWrapper + | ├─ UserCredentials + | ├─ UserMessages + | ├─ UserFiles + ├── Hooks + | ├─ useSourceInput + | ├─ useSpeech + | ├─ useSSE + ├── Services + ├── Styling + | ├─ info + ├── Utils + | ├─ constants + | ├─ FileAPI + | ├─ Loader + | ├─ Types + | ├─ utils + └── README.md + +== Application + +== 1. Setup and Installation: +Added Node.js with version v21.1.0 and npm on the development machine. +Install necessary dependencies by running yarn install, such as axios for making HTTP requests and others to interact with the graph. + +== 2. Connect to the Neo4j Aura instance: +Created a connection modal by adding details including protocol, URI, database name, username, and password. Added a submit button that triggers an API: ***/connect*** and accepts params like uri, password, username and database to establish a connection to the Neo4j Aura instance. Handled the authentication and error scenarios appropriately, by displaying relevant messages. To check whether the backend connection is up and working we hit the API: ***/health*** + +* Before Connection : + +image::images/ConnectionModal.jpg[NoConnection, 600] + + * After connection: + +image::images/NoFiles.jpg[Connection, 600] + + +== 3. File Source integration: +Implemented various file source integrations including drag-and-drop, web sources search that includes YouTube video, Wikipedia link, Amazon S3 file access, and Google Cloud Storage (GCS) file access. This allows users to upload PDF files from local storage or directly from the integrated sources. +The Api’s are as follows: + +* ***/source_list:*** + ** to fetch the list of files in the DB + +image::images/WithFiles.jpg[Connected, 600] + +* ***/upload:*** + ** to upload files from Local + +image::images/UploadLocalFile.jpg[Local File, 600] + + + ** status 'Uploading' while file is get uploaded. + +image::images/UploadingStatus.jpg[Upload Status, 600] + + +* ***/url/scan:*** + ** to scan the link or sources of YouTube, Wikipedia, and Web Sources + +image::images/WebSources.jpg[WebSources, 600] + +* ***/url/scan:*** + ** to scan the files of S3 and GCS. + *** Add the respective Bucket URL, access key and secret key to access ***S3 files***. + +image::images/S3BucketScan.jpg[S3 scan, 600] + + **** Add the respective Project ID, Bucket name, and folder to access ***GCS files***. User gets a redirect to the authentication page to authenticate their google account. + +image::images/GCSbucketFiles.jpg[GCS scan, 600] + +image::images/Gcloud_auth.jpg[auth login scan, 600] + + +== 4. File Source Extraction: +* ***/extract*** + ** to fetch the number of nodes and relationships created. + *** During Extraction the selected files or all files in ‘New’ state go into ‘Processing’ state and then ‘Completed’ state if there are no failures. + +image::images/GenerateGraph.jpg[Generate Graph, 600] + + +== 5. Graph Generation: +* Created a component for generating graphs based on the files in the table, to extract nodes and relationships. When the user clicks on the Preview Graph or on the Table View icon the user can see that the graph model holds three options for viewing: Lexical Graph, Entity Graph and Knowledge Graph. We utilized Neo4j's graph library to visualize the extracted nodes and relationships in the form of a graph query API: ***/graph_query***. There are options for customizing the graph visualization such as layout algorithms [zoom in, zoom out, fit, refresh], node styling, relationship types. + +image::images/KnowledgeGraph.jpg[Knowledge Graph, 600] +image::images/EntityGraph.jpg[Entity Graph, 600] +image::images/EntityGraph.jpg[Entity Graph, 600] + +== 6. Chatbot: +* Created a Chatbot Component which has state variables to manage user input and chat messages. Once the user asks the question and clicks on the Ask button API: ***/chatbot*** is triggered to send user input to the backend and receive the response. The chat also has options for users to see more details about the chat, text to speech and copy the response. + +image::images/ChatResponse.jpg[ChatResponse, 600] + +* ***/chunk_entities:*** + + ** to fetch the number of sources, entities and chunks + +***Sources*** + +image::images/ChatInfoModal.jpg[ChatInfoModal, 600] + +***Entities*** + +image::images/EntitiesInfo.jpg[EntitiesInfo, 600] + +***Chunks*** + +image::images/ChunksInfo.jpg[ChunksInfo, 600] + +* There are three modes ***Vector***, ***Graph***, ***Graph+Vector*** that can be provided to the chat to retrieve the answers. + +image::images/ChatModes.jpg[ChatModes, 600] + + • In Vector mode, we only get the sources and chunks . + +image::images/VectorMode.jpg[VectorMode, 600] + + • Graph Mode: Cypher query and Entities [DEV] + +image::images/GraphModeDetails.png[GraphMode, 600] +image::images/GraphModeQuery.png[GraphMode, 600] + + • Graph+Vector Mode: Sources, Chunks and Entities + +image::images/GraphVectorMode.jpg[GraphVectorMode, 600] + +== 6. Graph Enhancement Settings: +Users can now set their own Schema for nodes and relations or can already be an existing schema. + +* ***/schema:*** + ** to fetch the existing schema that already exists in the db. + +image::images/PredefinedSchema.jpg[PredefinedSchema, 600] + +* ***/populate_graph_schema:*** + ** to fetch the schema from user entered document text + +image::images/UserDefinedSchema.jpg[UserDefinedSchema, 600] + +* ***/delete_unconnected_nodes:*** + ** to remove the lonely entities. + +image::images/DeleteOrphanNodes.jpg[DeleteOrphanNodes, 600] + +== 7. Settings: + +* ***LLM Model*** + +User can select desired LLM models + +image::images/Dropdown.jpg[Dropdown, 600] + +* ***Dark/Light Mode*** + +User can choose the application view : both in dark and light mode + +image::images/DarkMode.jpg[DarkMode, 600] + + +image::images/LightMode.jpg[LightMode, 600] + +* ***Delete Files*** + +User can delete all number/selected files from the table. + +image::images/DeleteFiles.jpg[DeleteFiles, 600] + +== 8. Interface Design: +Designed a user-friendly interface that guides users through the process of connecting to Neo4j Aura, accessing file sources, uploading PDF files, and generating graphs. + +* ***Components:*** @neo4j-ndl/react +* ***Icons:*** @neo4j-ndl/react/icons +* ***Graph Visualization:*** @neo4j-nvl/react. +* ***NVL:*** @neo4j-nvl/core +* ***CSS:*** Inline styling, tailwind CSS + +== 9. Deployment: +Followed best practices for optimizing performance and security of the deployed application. + +* ***Local Deployment:*** + ** Running through docker-compose + ** By default only OpenAI and Diffbot are enabled since Gemini requires extra GCP configurations. + ** In your root folder, create a .env file with your OPENAI and DIFFBOT keys (if you want to use both), + ** By default, the input sources will be: Local files, Youtube, Wikipedia ,AWS S3 and Webpages. As this default config is applied: + ** By default,all of the chat modes will be available: vector, graph+vector and graph. If none of the mode is mentioned in the chat modes variable all modes will be available: + ** You can then run Docker Compose to build and start all components: + +[source,indent=0] +---- + * LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" + * REACT_APP_SOURCES="local,youtube,wiki,s3,gcs,web" + * GOOGLE_CLIENT_ID="xxxx" [For Google GCS integration] + * CHAT_MODES="vector,graph+vector" + * CHUNK_SIZE=5242880 + * TIME_PER_BYTE=2 + * TIME_PER_PAGE=50 + * TIME_PER_CHUNK=4 + * LARGE_FILE_SIZE=5242880 + * ENV="PROD"/ ‘DEV’ + * NEO4J_USER_AGENT="LLM-Graph-Builder/v0.2-dev" + * BACKEND_API_URL= + * BLOOM_URL= + * NPM_TOKEN= + * BACKEND_PROCESSING_URL= +---- +* ***Cloud Deployment:*** + ** To deploy the app install the gcloud cli , run the following command in the terminal specifically from frontend root folder. + *** gcloud run deploy + *** source location current directory > Frontend + *** region : 32 [us-central 1] + *** Allow unauthenticated request : Yes + + +== 10. API Reference +----- +POST /connect +----- + +Neo4j database connection on frontend is done with this API. + +**API Parameters :** + +* `uri`= Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name + +=== Upload Files from Local +---- +POST /upload +---- + +The upload endpoint is designed to handle the uploading of large files by breaking them into smaller chunks. This method ensures that large files can be uploaded efficiently without overloading the server. + +**API Parameters :** + +* `file`=The file to be uploaded, received in chunks, +* `chunkNumber`=The current chunk number being uploaded, +* `totalChunks`=The total number of chunks the file is divided into (each chunk of 1Mb size), +* `originalname`=The original name of the file, +* `model`=The model associated with the file, +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name + + +=== User Defined Schema +---- +POST /schema +---- + +User can set schema for graph generation (i.e. Nodes and relationship labels) in settings panel or get existing db schema through this API. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name + +=== Graph schema from Input Text +---- +POST /populate_graph_schema +---- + +The API is used to populate a graph schema based on the provided input text, model, and schema description flag. + +**API Parameters :** + +* `input_text`=The input text used to populate the graph schema. +* `model`=The model to be used for populating the graph schema. +* `is_schema_description_checked`=A flag indicating whether the schema description should be considered. + +=== Unstructured Sources +---- +POST /url/scan +---- + +Create Document node for other sources - s3 bucket, gcs bucket, wikipedia, youtube url and web pages. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name +* `model`= LLM model, +* `source_url`= , +* `aws_access_key_id`= AWS access key, +* `aws_secret_access_key`= AWS secret key, +* `wiki_query`= Wikipedia query sources, +* `gcs_project_id`= GCS project id, +* `gcs_bucket_name`= GCS bucket name, +* `gcs_bucket_folder`= GCS bucket folder, +* `source_type`= s3 bucket/ gcs bucket/ youtube/Wikipedia as source type +* `gcs_project_id`=Form(None), +* `access_token`=Form(None) + + +=== Extration of Nodes and Relations from Data +---- +POST /extract +---- + +This API is responsible for - + +** Reading the content of source provided in the form of langchain Document object from respective langchain loaders + +** Dividing the document into multiple chunks, and make below relations - +*** PART_OF - relation from Document node to all chunk nodes +*** FIRST_CHUNK - relation from document node to first chunk node +*** NEXT_CHUNK - relation from a chunk pointing to next chunk of the document. +*** HAS_ENTITY - relation between chunk node and entities extracted from LLM. + +** Extracting nodes and relations in the form of GraphDocument from respective LLM. + +** Update embedding of chunks and create vector index. + +** Update K-Nearest Neighbors graph for similar chunks. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name +* `model`= LLM model, +* `file_name` = File uploaded from device +* `source_url`= , +* `aws_access_key_id`= AWS access key, +* `aws_secret_access_key`= AWS secret key, +* `wiki_query`= Wikipedia query sources, +* `gcs_project_id`=GCS project id, +* `gcs_bucket_name`= GCS bucket name, +* `gcs_bucket_folder`= GCS bucket folder, +* `gcs_blob_filename` = GCS file name, +* `source_type`= local file/ s3 bucket/ gcs bucket/ youtube/ Wikipedia as source, +allowedNodes=Node labels passed from settings panel, +* `allowedRelationship`=Relationship labels passed from settings panel, +* `language`=Language in which wikipedia content will be extracted + +=== Get list of sources +---- +GET /sources_list +---- + +List all sources (Document nodes) present in Neo4j graph database. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name + + +=== Post processing after graph generation +---- +POST /post_processing : +---- + +This API is called at the end of processing of whole document to get create k-nearest neighbor relations between similar chunks of document based on KNN_MIN_SCORE which is 0.8 by default and to drop and create a full text index on db labels. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name +* `tasks`= List of tasks to perform + +=== Chat with Data +---- +POST /chat_bot +---- + +The API responsible for a chatbot system designed to leverage multiple AI models and a Neo4j graph database, providing answers to user queries. It interacts with AI models from OpenAI and Google's Vertex AI and utilizes embedding models to enhance the retrieval of relevant information. + +**Components :** + +** Embedding Models - Includes OpenAI Embeddings, VertexAI Embeddings, and SentenceTransformer Embeddings to support vector-based query operations. +** AI Models - OpenAI GPT 3.5, GPT 4o, Gemini Pro, Gemini 1.5 Pro and Groq llama3 can be configured for the chatbot backend to generate responses and process natural language. +** Graph Database (Neo4jGraph) - Manages interactions with the Neo4j database, retrieving, and storing conversation histories. +** Response Generation - Utilizes Vector Embeddings from the Neo4j database, chat history, and the knowledge base of the LLM used. + +**API Parameters :** + +* `uri`= Neo4j uri +* `userName`= Neo4j database username +* `password`= Neo4j database password +* `model`= LLM model +* `question`= User query for the chatbot +* `session_id`= Session ID used to maintain the history of chats during the user's connection + +=== Get entities from chunks +---- +POST/chunk_entities +---- + +This API is used to get the entities and relations associated with a particular chunk and chunk metadata. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name +* `chunk_ids` = Chunk ids of document + + +=== Clear chat history +---- +POST /clear_chat_bot +---- + +This API is used to clear the chat history which is saved in Neo4j DB. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name, +* `session_id` = User session id for QA chat + +=== View graph for a file +---- +POST /graph_query +---- + +This API is used to view graph for a particular file. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `query_type`= Neo4j database name +* `document_names` = File name for which user wants to view graph + +=== SSE event to update processing status +---- +GET /update_extract_status +---- + +The API provides a continuous update on the extraction status of a specified file. It uses Server-Sent Events (SSE) to stream updates to the client. + +**API Parameters :** + +* `file_name`=The name of the file whose extraction status is being tracked, +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name + +---- +GET /document_status +---- + +The API gives the extraction status of a specified file. It uses Server-Sent Events (SSE) to stream updates to the client. + +**API Parameters :** + +* `file_name`=The name of the file whose extraction status is being tracked, +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name + +=== Delete selected documents +---- +POST /delete_document_and_entities +---- + +Deleteion of nodes and relations for multiple files is done through this API. User can choose multiple documents to be deleted, also user have option to delete only 'Document' and 'Chunk' nodes and keep the entities extracted from that document. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name, +* `filenames`= List of files to be deleted, +* `source_types`= Document sources(Wikipedia, youtube, etc.), +* `deleteEntities`= Boolean value to check entities deletion is requested or not + +=== Cancel processing job +---- +POST/cancelled_job +---- + +This API is responsible for cancelling an in process job. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name, +* `filenames`= Name of the file whose processing need to be stopped, +* `source_types`= Source of the file + +=== Deletion of orpahn nodes +---- +POST /delete_unconnected_nodes +---- + +The API is used to delete unconnected entities from database. + +**API Parameters :** + +* `uri`=Neo4j uri, +* `userName`= Neo4j db username, +* `password`= Neo4j db password, +* `database`= Neo4j database name, +* `unconnected_entities_list`=selected entities list to delete of unconnected entities. + + +== 11. Conclusion: +In conclusion, this technical document outlines the process of building a React application with Neo4j Aura integration for graph database functionalities. + + +== 12. Referral Links: +* Dev env : https://dev-frontend-dcavk67s4a-uc.a.run.app/ +* Staging env: https://staging-frontend-dcavk67s4a-uc.a.run.app/ +* Prod env: https://prod-frontend-dcavk67s4a-uc.a.run.app/ + + + + + + diff --git a/docs/frontend/images/ChatInfoModal.jpg b/docs/frontend/images/ChatInfoModal.jpg new file mode 100644 index 000000000..72c119800 Binary files /dev/null and b/docs/frontend/images/ChatInfoModal.jpg differ diff --git a/docs/frontend/images/ChatModes.jpg b/docs/frontend/images/ChatModes.jpg new file mode 100644 index 000000000..1dd835e24 Binary files /dev/null and b/docs/frontend/images/ChatModes.jpg differ diff --git a/docs/frontend/images/ChatResponse.jpg b/docs/frontend/images/ChatResponse.jpg new file mode 100644 index 000000000..72c119800 Binary files /dev/null and b/docs/frontend/images/ChatResponse.jpg differ diff --git a/docs/frontend/images/ChunksInfo.jpg b/docs/frontend/images/ChunksInfo.jpg new file mode 100644 index 000000000..62d360caa Binary files /dev/null and b/docs/frontend/images/ChunksInfo.jpg differ diff --git a/docs/frontend/images/ConnectionModal.jpg b/docs/frontend/images/ConnectionModal.jpg new file mode 100644 index 000000000..594ca25af Binary files /dev/null and b/docs/frontend/images/ConnectionModal.jpg differ diff --git a/docs/frontend/images/DarkMode.jpg b/docs/frontend/images/DarkMode.jpg new file mode 100644 index 000000000..cbc714e1d Binary files /dev/null and b/docs/frontend/images/DarkMode.jpg differ diff --git a/docs/frontend/images/DeleteFiles.jpg b/docs/frontend/images/DeleteFiles.jpg new file mode 100644 index 000000000..62c9b514e Binary files /dev/null and b/docs/frontend/images/DeleteFiles.jpg differ diff --git a/docs/frontend/images/DeleteOrphanNodes.jpg b/docs/frontend/images/DeleteOrphanNodes.jpg new file mode 100644 index 000000000..e397cb4a7 Binary files /dev/null and b/docs/frontend/images/DeleteOrphanNodes.jpg differ diff --git a/docs/frontend/images/Dropdown.jpg b/docs/frontend/images/Dropdown.jpg new file mode 100644 index 000000000..85b5631ea Binary files /dev/null and b/docs/frontend/images/Dropdown.jpg differ diff --git a/docs/frontend/images/EntitiesInfo.jpg b/docs/frontend/images/EntitiesInfo.jpg new file mode 100644 index 000000000..b15146e7a Binary files /dev/null and b/docs/frontend/images/EntitiesInfo.jpg differ diff --git a/docs/frontend/images/EntityGraph.jpg b/docs/frontend/images/EntityGraph.jpg new file mode 100644 index 000000000..9e25473a9 Binary files /dev/null and b/docs/frontend/images/EntityGraph.jpg differ diff --git a/docs/frontend/images/ExistingSchema.jpg b/docs/frontend/images/ExistingSchema.jpg new file mode 100644 index 000000000..94c5380ae Binary files /dev/null and b/docs/frontend/images/ExistingSchema.jpg differ diff --git a/docs/frontend/images/GCSbucketFiles.jpg b/docs/frontend/images/GCSbucketFiles.jpg new file mode 100644 index 000000000..4b17ba226 Binary files /dev/null and b/docs/frontend/images/GCSbucketFiles.jpg differ diff --git a/docs/frontend/images/GEDeleteOrphanNodes.jpg b/docs/frontend/images/GEDeleteOrphanNodes.jpg new file mode 100644 index 000000000..203aacc57 Binary files /dev/null and b/docs/frontend/images/GEDeleteOrphanNodes.jpg differ diff --git a/docs/frontend/images/Gcloud_auth.jpg b/docs/frontend/images/Gcloud_auth.jpg new file mode 100644 index 000000000..99d257222 Binary files /dev/null and b/docs/frontend/images/Gcloud_auth.jpg differ diff --git a/docs/frontend/images/GenerateGraph.jpg b/docs/frontend/images/GenerateGraph.jpg new file mode 100644 index 000000000..cc006d969 Binary files /dev/null and b/docs/frontend/images/GenerateGraph.jpg differ diff --git a/docs/frontend/images/GraphEnhacements.jpg b/docs/frontend/images/GraphEnhacements.jpg new file mode 100644 index 000000000..8fb3d4fe2 Binary files /dev/null and b/docs/frontend/images/GraphEnhacements.jpg differ diff --git a/docs/frontend/images/GraphModeDetails.png b/docs/frontend/images/GraphModeDetails.png new file mode 100644 index 000000000..d11e7dcd1 Binary files /dev/null and b/docs/frontend/images/GraphModeDetails.png differ diff --git a/docs/frontend/images/GraphModeQuery.png b/docs/frontend/images/GraphModeQuery.png new file mode 100644 index 000000000..cfd7fbaf8 Binary files /dev/null and b/docs/frontend/images/GraphModeQuery.png differ diff --git a/docs/frontend/images/GraphVectorMode.jpg b/docs/frontend/images/GraphVectorMode.jpg new file mode 100644 index 000000000..d378b860f Binary files /dev/null and b/docs/frontend/images/GraphVectorMode.jpg differ diff --git a/docs/frontend/images/KnowledgeGraph.jpg b/docs/frontend/images/KnowledgeGraph.jpg new file mode 100644 index 000000000..eeb20a627 Binary files /dev/null and b/docs/frontend/images/KnowledgeGraph.jpg differ diff --git a/docs/frontend/images/LexicalGraph.jpg b/docs/frontend/images/LexicalGraph.jpg new file mode 100644 index 000000000..7de1543ac Binary files /dev/null and b/docs/frontend/images/LexicalGraph.jpg differ diff --git a/docs/frontend/images/LightMode.jpg b/docs/frontend/images/LightMode.jpg new file mode 100644 index 000000000..0d5b9830d Binary files /dev/null and b/docs/frontend/images/LightMode.jpg differ diff --git a/docs/frontend/images/NoFiles.jpg b/docs/frontend/images/NoFiles.jpg new file mode 100644 index 000000000..7494026a4 Binary files /dev/null and b/docs/frontend/images/NoFiles.jpg differ diff --git a/docs/frontend/images/PredefinedSchema.jpg b/docs/frontend/images/PredefinedSchema.jpg new file mode 100644 index 000000000..6b89ab137 Binary files /dev/null and b/docs/frontend/images/PredefinedSchema.jpg differ diff --git a/docs/frontend/images/S3BucketScan.jpg b/docs/frontend/images/S3BucketScan.jpg new file mode 100644 index 000000000..967a98605 Binary files /dev/null and b/docs/frontend/images/S3BucketScan.jpg differ diff --git a/docs/frontend/images/ScanningSource.jpg b/docs/frontend/images/ScanningSource.jpg new file mode 100644 index 000000000..bd689db10 Binary files /dev/null and b/docs/frontend/images/ScanningSource.jpg differ diff --git a/docs/frontend/images/SourcesInfo.jpg b/docs/frontend/images/SourcesInfo.jpg new file mode 100644 index 000000000..80c8cfded Binary files /dev/null and b/docs/frontend/images/SourcesInfo.jpg differ diff --git a/docs/frontend/images/UploadLocalFile.jpg b/docs/frontend/images/UploadLocalFile.jpg new file mode 100644 index 000000000..0bd6f0edf Binary files /dev/null and b/docs/frontend/images/UploadLocalFile.jpg differ diff --git a/docs/frontend/images/UploadingStatus.jpg b/docs/frontend/images/UploadingStatus.jpg new file mode 100644 index 000000000..779daf239 Binary files /dev/null and b/docs/frontend/images/UploadingStatus.jpg differ diff --git a/docs/frontend/images/UserDefinedSchema.jpg b/docs/frontend/images/UserDefinedSchema.jpg new file mode 100644 index 000000000..73cdc6067 Binary files /dev/null and b/docs/frontend/images/UserDefinedSchema.jpg differ diff --git a/docs/frontend/images/VectorMode.jpg b/docs/frontend/images/VectorMode.jpg new file mode 100644 index 000000000..f0ebf4e37 Binary files /dev/null and b/docs/frontend/images/VectorMode.jpg differ diff --git a/docs/frontend/images/WebSources.jpg b/docs/frontend/images/WebSources.jpg new file mode 100644 index 000000000..7d20485cc Binary files /dev/null and b/docs/frontend/images/WebSources.jpg differ diff --git a/docs/frontend/images/WithFiles.jpg b/docs/frontend/images/WithFiles.jpg new file mode 100644 index 000000000..a789c03bb Binary files /dev/null and b/docs/frontend/images/WithFiles.jpg differ diff --git a/example.env b/example.env index d9bc8a2da..ed33101fb 100644 --- a/example.env +++ b/example.env @@ -25,13 +25,13 @@ GCS_FILE_CACHE = False ENTITY_EMBEDDING=True # Optional Frontend -BACKEND_API_URL="http://localhost:8000" -BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" -REACT_APP_SOURCES="local,youtube,wiki,s3,web" -LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" # ",ollama_llama3" -ENV="DEV" -TIME_PER_CHUNK=4 -TIME_PER_PAGE=50 -CHUNK_SIZE=5242880 -GOOGLE_CLIENT_ID="" -CHAT_MODES="" +VITE_BACKEND_API_URL="http://localhost:8000" +VITE_BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" +VITE_REACT_APP_SOURCES="local,youtube,wiki,s3,web" +VITE_LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" # ",ollama_llama3" +VITE_ENV="DEV" +VITE_TIME_PER_PAGE=50 +VITE_CHUNK_SIZE=5242880 +VITE_GOOGLE_CLIENT_ID="" +VITE_CHAT_MODES="" +VITE_BATCH_SIZE=2 diff --git a/experiments/multiple_models.ipynb b/experiments/multiple_models.ipynb index a4841a9f8..92db7b86a 100644 --- a/experiments/multiple_models.ipynb +++ b/experiments/multiple_models.ipynb @@ -9,7 +9,11 @@ }, { "cell_type": "code", +<<<<<<< HEAD + "execution_count": 1, +======= "execution_count": 27, +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 "metadata": {}, "outputs": [], "source": [ @@ -157,6 +161,20 @@ }, { "cell_type": "code", +<<<<<<< HEAD + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[GraphDocument(nodes=[Node(id='Stephen Hawking', type='Person', properties={'description': 'English theoretical physicist'}), Node(id='Oxford', type='Place'), Node(id='Cambridge', type='Place'), Node(id='University College, Oxford', type='Organization'), Node(id='Trinity Hall, Cambridge', type='Organization'), Node(id='Gonville And Caius College', type='Organization'), Node(id='Amyotrophic Lateral Sclerosis', type='Disease'), Node(id='General Relativity', type='Concept'), Node(id='Black Holes', type='Concept'), Node(id='Mini Black Holes', type='Concept'), Node(id='Quantum Mechanics', type='Concept')], relationships=[], source=Document(page_content='Stephen Hawking (born January 8, 1942, Oxford, Oxfordshire, England—died March 14, 2018, Cambridge, \\nCambridgeshire) was an English theoretical physicist whose theory of exploding black holes drew upon both relativity \\ntheory and quantum mechanics. He also worked with space-time singularities.\\nHawking studied physics at University College, Oxford (B.A., 1962), and Trinity Hall, Cambridge (Ph.D., 1966). \\nHe was elected a research fellow at Gonville and Caius College at Cambridge. In the early 1960s Hawking contracted \\namyotrophic lateral sclerosis, an incurable degenerative neuromuscular disease. He continued to work despite the \\ndisease’s progressively disabling effects.Hawking worked primarily in the field of general relativity and particularly \\non the physics of black holes. In 1971 he suggested the formation, following the big bang, of numerous objects \\ncontaining as much as one billion tons of mass but occupying only the space of a proton. These objects, called \\nmini black holes, are unique in that their immense mass and gravity require that they be ruled by the laws of \\nrelativity, while their minute size requires that the laws of quantum mechanics apply to them also.'))]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" +======= "execution_count": 37, "metadata": {}, "outputs": [ @@ -200,13 +218,18 @@ "text": [ "Failed to batch ingest runs: LangSmithRateLimitError('Rate limit exceeded for https://api.smith.langchain.com/runs/batch. HTTPError(\\'429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs/batch\\', \\'{\"detail\":\"Usage limit monthly_traces of 10000 exceeded\"}\\')')\n" ] +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 } ], "source": [ "#anthropic\n", "from langchain_anthropic import ChatAnthropic\n", "\n", +<<<<<<< HEAD + "model_name, api_key = os.environ.get(\"LLM_MODEL_CONFIG_anthropic_claude_3_5_sonnet\").split(',')\n", +======= "model_name, api_key = os.environ.get(\"LLM_MODEL_CONFIG_anthropic-claude-3-5-sonnet\").split(',')\n", +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 "anthropic_llm = ChatAnthropic(\n", " api_key=api_key,\n", " model=model_name, #claude-3-5-sonnet-20240620\n", @@ -291,34 +314,80 @@ }, { "cell_type": "code", +<<<<<<< HEAD + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Failed to batch ingest runs: LangSmithRateLimitError('Rate limit exceeded for https://api.smith.langchain.com/runs/batch. HTTPError(\\'429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs/batch\\', \\'{\"detail\":\"Usage limit monthly_traces of 10000 exceeded\"}\\')')\n" + ] + }, + { +======= "execution_count": 40, "metadata": {}, "outputs": [ { +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 "data": { "text/plain": [ "[GraphDocument(nodes=[], relationships=[], source=Document(page_content='Stephen Hawking (born January 8, 1942, Oxford, Oxfordshire, England—died March 14, 2018, Cambridge, \\nCambridgeshire) was an English theoretical physicist whose theory of exploding black holes drew upon both relativity \\ntheory and quantum mechanics. He also worked with space-time singularities.\\nHawking studied physics at University College, Oxford (B.A., 1962), and Trinity Hall, Cambridge (Ph.D., 1966). \\nHe was elected a research fellow at Gonville and Caius College at Cambridge. In the early 1960s Hawking contracted \\namyotrophic lateral sclerosis, an incurable degenerative neuromuscular disease. He continued to work despite the \\ndisease’s progressively disabling effects.Hawking worked primarily in the field of general relativity and particularly \\non the physics of black holes. In 1971 he suggested the formation, following the big bang, of numerous objects \\ncontaining as much as one billion tons of mass but occupying only the space of a proton. These objects, called \\nmini black holes, are unique in that their immense mass and gravity require that they be ruled by the laws of \\nrelativity, while their minute size requires that the laws of quantum mechanics apply to them also.'))]" ] }, +<<<<<<< HEAD + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Failed to batch ingest runs: LangSmithRateLimitError('Rate limit exceeded for https://api.smith.langchain.com/runs/batch. HTTPError(\\'429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs/batch\\', \\'{\"detail\":\"Usage limit monthly_traces of 10000 exceeded\"}\\')')\n", + "Failed to batch ingest runs: LangSmithRateLimitError('Rate limit exceeded for https://api.smith.langchain.com/runs/batch. HTTPError(\\'429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs/batch\\', \\'{\"detail\":\"Usage limit monthly_traces of 10000 exceeded\"}\\')')\n" + ] +======= "execution_count": 40, "metadata": {}, "output_type": "execute_result" +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 } ], "source": [ "#fireworks\n", "from langchain_fireworks import ChatFireworks\n", "\n", +<<<<<<< HEAD + "model_name, api_key = os.environ.get(\"LLM_MODEL_CONFIG_fireworks_llama_v3_70b\").split(',')\n", +======= "model_name, api_key = os.environ.get(\"LLM_MODEL_CONFIG_fireworks-llama-v3-70b\").split(',')\n", +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 "fireworks_llm = ChatFireworks(\n", " api_key=api_key,\n", " model=model_name #accounts/fireworks/models/llama-v3-70b-instruct\n", " ) \n", +<<<<<<< HEAD + "prompt = \"\"\n", +======= +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 "llm_transformer = LLMGraphTransformer(llm=fireworks_llm, node_properties=[\"description\"])\n", "llm_transformer.convert_to_graph_documents(docs)" ] }, { +<<<<<<< HEAD + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { +======= +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 "cell_type": "markdown", "metadata": {}, "source": [ @@ -401,6 +470,45 @@ ] }, { +<<<<<<< HEAD + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "not enough values to unpack (expected 3, got 2)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[4], line 8\u001b[0m\n\u001b[1;32m 5\u001b[0m load_dotenv()\n\u001b[1;32m 6\u001b[0m \u001b[38;5;66;03m#https://api.groq.com/openai/v1\u001b[39;00m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;66;03m#http://localhost:11434/v1\u001b[39;00m\n\u001b[0;32m----> 8\u001b[0m model_name, api_endpoint, api_key \u001b[38;5;241m=\u001b[39m os\u001b[38;5;241m.\u001b[39menviron\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mLLM_MODEL_CONFIG_ollama_llama3\u001b[39m\u001b[38;5;124m'\u001b[39m)\u001b[38;5;241m.\u001b[39msplit(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m,\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 9\u001b[0m llm \u001b[38;5;241m=\u001b[39m ChatOpenAI(\n\u001b[1;32m 10\u001b[0m api_key\u001b[38;5;241m=\u001b[39mapi_key,\n\u001b[1;32m 11\u001b[0m base_url\u001b[38;5;241m=\u001b[39mapi_endpoint,\n\u001b[1;32m 12\u001b[0m model\u001b[38;5;241m=\u001b[39mmodel_name,\n\u001b[1;32m 13\u001b[0m temperature\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0\u001b[39m,\n\u001b[1;32m 14\u001b[0m )\n\u001b[1;32m 15\u001b[0m llm_transformer \u001b[38;5;241m=\u001b[39m LLMGraphTransformer(llm\u001b[38;5;241m=\u001b[39mllm, node_properties\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n", + "\u001b[0;31mValueError\u001b[0m: not enough values to unpack (expected 3, got 2)" + ] + } + ], + "source": [ + "from langchain_openai import ChatOpenAI, OpenAI\n", + "import os\n", + "from dotenv import load_dotenv\n", + "\n", + "load_dotenv()\n", + "#https://api.groq.com/openai/v1\n", + "#http://localhost:11434/v1\n", + "model_name, api_endpoint, api_key = os.environ.get('LLM_MODEL_CONFIG_ollama_llama3').split(\",\")\n", + "llm = ChatOpenAI(\n", + " api_key=api_key,\n", + " base_url=api_endpoint,\n", + " model=model_name,\n", + " temperature=0,\n", + ")\n", + "llm_transformer = LLMGraphTransformer(llm=llm, node_properties=False)\n", + "llm_transformer.convert_to_graph_documents(docs)" + ] + }, + { +======= +>>>>>>> bd0ca440021ab0b3eaca20ee6458f87c562be4e0 "cell_type": "markdown", "metadata": {}, "source": [ diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 3bf2e9409..c3a7c1c82 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,33 +1,33 @@ # Step 1: Build the React application FROM node:20 AS build -ARG BACKEND_API_URL="http://localhost:8000" -ARG REACT_APP_SOURCES="" -ARG LLM_MODELS="" -ARG GOOGLE_CLIENT_ID="" -ARG BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" -ARG TIME_PER_CHUNK=4 -ARG TIME_PER_PAGE=50 -ARG LARGE_FILE_SIZE=5242880 -ARG CHUNK_SIZE=5242880 -ARG CHAT_MODES="" -ARG ENV="DEV" +ARG VITE_BACKEND_API_URL="http://localhost:8000" +ARG VITE_REACT_APP_SOURCES="" +ARG VITE_LLM_MODELS="" +ARG VITE_GOOGLE_CLIENT_ID="" +ARG VITE_BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" +ARG VITE_TIME_PER_PAGE=50 +ARG VITE_LARGE_FILE_SIZE=5242880 +ARG VITE_CHUNK_SIZE=5242880 +ARG VITE_CHAT_MODES="" +ARG VITE_ENV="DEV" +ARG VITE_BATCH_SIZE=2 WORKDIR /app COPY package.json yarn.lock ./ -RUN yarn add @neo4j-nvl/base @neo4j-nvl/react RUN yarn install COPY . ./ -RUN BACKEND_API_URL=$BACKEND_API_URL \ - REACT_APP_SOURCES=$REACT_APP_SOURCES \ - LLM_MODELS=$LLM_MODELS \ - GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID \ - BLOOM_URL=$BLOOM_URL \ - TIME_PER_CHUNK=$TIME_PER_CHUNK \ - CHUNK_SIZE=$CHUNK_SIZE \ - ENV=$ENV \ - LARGE_FILE_SIZE=${LARGE_FILE_SIZE} \ - CHAT_MODES=$CHAT_MODES \ +RUN VITE_BACKEND_API_URL=$VITE_BACKEND_API_URL \ + VITE_REACT_APP_SOURCES=$VITE_REACT_APP_SOURCES \ + VITE_LLM_MODELS=$VITE_LLM_MODELS \ + VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID \ + VITE_BLOOM_URL=$VITE_BLOOM_URL \ + VITE_CHUNK_SIZE=$VITE_CHUNK_SIZE \ + VITE_TIME_PER_PAGE=$VITE_TIME_PER_PAGE \ + VITE_ENV=$VITE_ENV \ + VITE_LARGE_FILE_SIZE=${VITE_LARGE_FILE_SIZE} \ + VITE_CHAT_MODES=$VITE_CHAT_MODES \ + VITE_BATCH_SIZE=$VITE_BATCH_SIZE \ yarn run build # Step 2: Serve the application using Nginx diff --git a/frontend/example.env b/frontend/example.env index 0c11aa4ef..63bd3e7c3 100644 --- a/frontend/example.env +++ b/frontend/example.env @@ -1,11 +1,11 @@ -BACKEND_API_URL="http://localhost:8000" -BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" -REACT_APP_SOURCES="local,youtube,wiki,s3,web" -LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" -ENV="DEV" -TIME_PER_CHUNK=4 -TIME_PER_PAGE=50 -CHUNK_SIZE=5242880 -LARGE_FILE_SIZE=5242880 -GOOGLE_CLIENT_ID="" -CHAT_MODES="" \ No newline at end of file +VITE_BACKEND_API_URL="http://localhost:8000" +VITE_BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" +VITE_REACT_APP_SOURCES="local,youtube,wiki,s3,web" +VITE_LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" +VITE_ENV="DEV" +VITE_TIME_PER_PAGE=50 +VITE_CHUNK_SIZE=5242880 +VITE_LARGE_FILE_SIZE=5242880 +VITE_GOOGLE_CLIENT_ID="" +VITE_CHAT_MODES="" +VITE_BATCH_SIZE=2 \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 5e83ede6b..9e51f89fa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,10 +15,10 @@ "@mui/material": "^5.15.10", "@mui/styled-engine": "^5.15.9", "@neo4j-devtools/word-color": "^0.0.8", - "@neo4j-ndl/base": "^2.11.6", - "@neo4j-ndl/react": "^2.15.10", - "@neo4j-nvl/base": "^0.3.1", - "@neo4j-nvl/react": "^0.3.1", + "@neo4j-ndl/base": "^2.12.7", + "@neo4j-ndl/react": "^2.16.9", + "@neo4j-nvl/base": "^0.3.3", + "@neo4j-nvl/react": "^0.3.3", "@react-oauth/google": "^0.12.1", "@types/uuid": "^9.0.7", "axios": "^1.6.5", diff --git a/frontend/src/API/Index.ts b/frontend/src/API/Index.ts new file mode 100644 index 000000000..f4ad15cbe --- /dev/null +++ b/frontend/src/API/Index.ts @@ -0,0 +1,7 @@ +import axios from 'axios'; +import { url } from '../utils/Utils'; + +const api = axios.create({ + baseURL: url(), +}); +export default api; diff --git a/frontend/src/App.css b/frontend/src/App.css index 5e3926703..fe285c972 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,18 +1,18 @@ .filetable { /* width: calc(-360px + 100dvw); */ - height: calc(-400px + 100dvh); + height: calc(-350px + 100dvh); border: 1px solid #d1d5db; } .fileTableWithExpansion { width: calc(-640px + 100dvw) !important; - height: calc(-400px + 100dvh); + height: calc(-350px + 100dvh); border: 1px solid #d1d5db; } .fileTableWithBothDrawers { width: calc(-650px + 100dvw) !important; - height: calc(-400px + 100dvh); + height: calc(-350px + 100dvh); border: 1px solid #d1d5db; } @@ -233,10 +233,8 @@ letter-spacing: 0; line-height: 1.25rem; width: max-content; - height: 30px; text-overflow: ellipsis; white-space: nowrap; - overflow: hidden; } .ndl-widget-content>div { @@ -347,14 +345,31 @@ margin-bottom: 0 !important; } -.node_label__value-container--has-value { +.node_label__value-container--has-value ,.relationship_label__value-container--has-value{ max-height: 215px; overflow-y: scroll !important; scrollbar-width: thin; } - -.relationship_label__value-container--has-value { - max-height: 215px; +.entity_extraction_Tab_node_label__value-container--has-value,.entity_extraction_Tab_relationship_label__value-container--has-value{ + max-height: 100px; + overflow-y: scroll !important; + scrollbar-width: thin; +} +.tablet_entity_extraction_Tab_node_label__value-container--has-value,.tablet_entity_extraction_Tab_relationship_label__value-container--has-value{ + max-height: 80px; overflow-y: scroll !important; - scrollbar-width: thin + scrollbar-width: thin; +} +.widthunset{ + width: initial !important; + height: initial !important; +} + +.text-input-container { + transition: width 1.5s ease; + /* width: 100dvh; */ +} + +.text-input-container.search-initiated { + width: 60dvh; } \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0caa501e3..f35599b98 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,7 +11,7 @@ const App: React.FC = () => { <> {APP_SOURCES != undefined && APP_SOURCES.includes('gcs') ? ( - + diff --git a/frontend/src/HOC/SettingModalHOC.tsx b/frontend/src/HOC/SettingModalHOC.tsx index 7afe46d0e..24d84bbff 100644 --- a/frontend/src/HOC/SettingModalHOC.tsx +++ b/frontend/src/HOC/SettingModalHOC.tsx @@ -1,28 +1,28 @@ -import React from 'react'; -import { SettingsModalProps } from '../types'; -import SettingsModal from '../components/Popups/Settings/SettingModal'; +// import React from 'react'; +// import { SettingsModalProps } from '../types'; +// import SettingsModal from '../components/Popups/Settings/SettingModal'; -const SettingModalHOC: React.FC = ({ - openTextSchema, - open, - onClose, - isSchema, - settingView, - setIsSchema, - onContinue, - onClear, -}) => { - return ( - - ); -}; -export default SettingModalHOC; +// const SettingModalHOC: React.FC = ({ +// openTextSchema, +// open, +// onClose, +// isSchema, +// settingView, +// setIsSchema, +// onContinue, +// onClear, +// }) => { +// return ( +// +// ); +// }; +// export default SettingModalHOC; diff --git a/frontend/src/components/ChatBot/ChatInfoModal.tsx b/frontend/src/components/ChatBot/ChatInfoModal.tsx index e82693eb2..89c15ea72 100644 --- a/frontend/src/components/ChatBot/ChatInfoModal.tsx +++ b/frontend/src/components/ChatBot/ChatInfoModal.tsx @@ -8,6 +8,8 @@ import { CypherCodeBlock, CypherCodeBlockProps, useCopyToClipboard, + Banner, + useMediaQuery, } from '@neo4j-ndl/react'; import { DocumentDuplicateIconOutline, DocumentTextIconOutline } from '@neo4j-ndl/react/icons'; import '../../styling/info.css'; @@ -16,19 +18,27 @@ import wikipedialogo from '../../assets/images/wikipedia.svg'; import youtubelogo from '../../assets/images/youtube.svg'; import gcslogo from '../../assets/images/gcs.webp'; import s3logo from '../../assets/images/s3logo.png'; -import { Chunk, Entity, GroupedEntity, UserCredentials, chatInfoMessage } from '../../types'; +import { + Chunk, + Entity, + ExtendedNode, + ExtendedRelationship, + GroupedEntity, + UserCredentials, + chatInfoMessage, +} from '../../types'; import { useContext, useEffect, useMemo, useState } from 'react'; import HoverableLink from '../UI/HoverableLink'; import GraphViewButton from '../Graph/GraphViewButton'; import { chunkEntitiesAPI } from '../../services/ChunkEntitiesInfo'; import { useCredentials } from '../../context/UserCredentials'; -import type { Node, Relationship } from '@neo4j-nvl/base'; import { calcWordColor } from '@neo4j-devtools/word-color'; import ReactMarkdown from 'react-markdown'; import { GlobeAltIconOutline } from '@neo4j-ndl/react/icons'; -import { youtubeLinkValidation } from '../../utils/Utils'; +import { parseEntity, youtubeLinkValidation } from '../../utils/Utils'; import { ThemeWrapperContext } from '../../context/ThemeWrapper'; import { ClipboardDocumentCheckIconOutline } from '@neo4j-ndl/react/icons'; +import { tokens } from '@neo4j-ndl/base'; const ChatInfoModal: React.FC = ({ sources, @@ -39,24 +49,21 @@ const ChatInfoModal: React.FC = ({ mode, cypher_query, graphonly_entities, + error, }) => { - const [activeTab, setActiveTab] = useState(mode === 'graph' ? 4 : 3); + const { breakpoints } = tokens; + const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); + const [activeTab, setActiveTab] = useState(error.length ? 10 : mode === 'graph' ? 4 : 3); const [infoEntities, setInfoEntities] = useState([]); const [loading, setLoading] = useState(false); const { userCredentials } = useCredentials(); - const [nodes, setNodes] = useState([]); - const [relationships, setRelationships] = useState([]); + const [nodes, setNodes] = useState([]); + const [relationships, setRelationships] = useState([]); const [chunks, setChunks] = useState([]); const themeUtils = useContext(ThemeWrapperContext); const [, copy] = useCopyToClipboard(); const [copiedText, setcopiedText] = useState(false); - const parseEntity = (entity: Entity) => { - const { labels, properties } = entity; - const label = labels[0]; - const text = properties.id; - return { label, text }; - }; const actions: CypherCodeBlockProps['actions'] = useMemo( () => [ { @@ -80,7 +87,7 @@ const ChatInfoModal: React.FC = ({ [copiedText, cypher_query] ); useEffect(() => { - if (mode != 'graph') { + if (mode != 'graph' || error?.trim() !== '') { setLoading(true); chunkEntitiesAPI(userCredentials as UserCredentials, chunk_ids.map((c) => c.id).join(',')) .then((response) => { @@ -107,7 +114,7 @@ const ChatInfoModal: React.FC = ({ () => { setcopiedText(false); }; - }, [chunk_ids, mode]); + }, [chunk_ids, mode, error]); const groupedEntities = useMemo<{ [key: string]: GroupedEntity }>(() => { return infoEntities.reduce((acc, entity) => { const { label, text } = parseEntity(entity); @@ -126,7 +133,7 @@ const ChatInfoModal: React.FC = ({ const counts: { [label: string]: number } = {}; infoEntities.forEach((entity) => { const { labels } = entity; - const label = labels[0]; + const [label] = labels; counts[label] = counts[label] ? counts[label] + 1 : 1; }); return counts; @@ -148,7 +155,11 @@ const ChatInfoModal: React.FC = ({ return ( - + Retrieval information @@ -159,16 +170,24 @@ const ChatInfoModal: React.FC = ({ - - {mode != 'graph' ? Sources used : <>} - {mode === 'graph+vector' || mode === 'graph' ? Top Entities used : <>} - {mode === 'graph' && cypher_query?.trim().length ? ( - Generated Cypher Query - ) : ( - <> - )} - {mode != 'graph' ? Chunks : <>} - + {error?.length > 0 ? ( + {error} + ) : ( + + {mode != 'graph' ? Sources used : <>} + {mode === 'graph+vector' || mode === 'graph' || mode === 'graph+vector+fulltext' ? ( + Top Entities used + ) : ( + <> + )} + {mode === 'graph' && cypher_query?.trim().length ? ( + Generated Cypher Query + ) : ( + <> + )} + {mode != 'graph' ? Chunks : <>} + + )} {sources.length ? ( @@ -210,17 +229,6 @@ const ChatInfoModal: React.FC = ({ )} - {link?.startsWith('s3://') && ( -
- S3 Logo - - {decodeURIComponent(link).split('/').at(-1) ?? 'S3 File'} - -
- )} {youtubeLinkValidation(link) && ( <>
@@ -250,6 +258,16 @@ const ChatInfoModal: React.FC = ({
)} + ) : link?.startsWith('s3://') ? ( +
+ S3 Logo + + {decodeURIComponent(link).split('/').at(-1) ?? 'S3 File'} + +
) : (
@@ -259,15 +277,6 @@ const ChatInfoModal: React.FC = ({ > {link} - {/* {chunks?.length > 0 && ( - - - Page{' '} - {chunks - .map((c) => c.page_number as number) - .sort((a, b) => a - b) - .join(', ')} - - )} */}
)} @@ -293,7 +302,7 @@ const ChatInfoModal: React.FC = ({ >
{ - //@ts-ignore + // @ts-ignore label[Object.keys(label)[0]].id ?? Object.keys(label)[0] }
@@ -385,6 +394,7 @@ const ChatInfoModal: React.FC = ({ {chunk?.fileName} + Similarity Score: {chunk?.score} ) : chunk?.url && !chunk?.url.startsWith('s3://') && @@ -400,6 +410,10 @@ const ChatInfoModal: React.FC = ({ Similarity Score: {chunk?.score} + ) : chunk.fileSource === 'local file' ? ( + <> + Similarity Score: {chunk?.score} + ) : ( <> )} diff --git a/frontend/src/components/ChatBot/ChatModeToggle.tsx b/frontend/src/components/ChatBot/ChatModeToggle.tsx index a2becd6c4..e82dfea4d 100644 --- a/frontend/src/components/ChatBot/ChatModeToggle.tsx +++ b/frontend/src/components/ChatBot/ChatModeToggle.tsx @@ -31,7 +31,12 @@ export default function ChatModeToggle({ () => chatModes?.map((m) => { return { - title: m.includes('+') ? 'Graph + Vector' : capitalize(m), + title: m.includes('+') + ? m + .split('+') + .map((s) => capitalize(s)) + .join('+') + : capitalize(m), onClick: () => { setchatMode(m); }, diff --git a/frontend/src/components/ChatBot/Chatbot.tsx b/frontend/src/components/ChatBot/Chatbot.tsx index 70c81135c..da5aaaf29 100644 --- a/frontend/src/components/ChatBot/Chatbot.tsx +++ b/frontend/src/components/ChatBot/Chatbot.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Button, Widget, Typography, Avatar, TextInput, IconButton, Modal, useCopyToClipboard } from '@neo4j-ndl/react'; +import React, { FC, lazy, Suspense, useEffect, useRef, useState } from 'react'; +import { Widget, Typography, Avatar, TextInput, IconButton, Modal, useCopyToClipboard } from '@neo4j-ndl/react'; import { XMarkIconOutline, ClipboardDocumentIconOutline, @@ -12,15 +12,16 @@ import { useCredentials } from '../../context/UserCredentials'; import { chatBotAPI } from '../../services/QnaAPI'; import { v4 as uuidv4 } from 'uuid'; import { useFileContext } from '../../context/UsersFiles'; -import InfoModal from './ChatInfoModal'; import clsx from 'clsx'; import ReactMarkdown from 'react-markdown'; import IconButtonWithToolTip from '../UI/IconButtonToolTip'; import { buttonCaptions, tooltips } from '../../utils/Constants'; import useSpeechSynthesis from '../../hooks/useSpeech'; import ButtonWithToolTip from '../UI/ButtonWithToolTip'; +import FallBackDialog from '../UI/FallBackDialog'; +const InfoModal = lazy(() => import('./ChatInfoModal')); -const Chatbot: React.FC = (props) => { +const Chatbot: FC = (props) => { const { messages: listMessages, setMessages: setListMessages, isLoading, isFullScreen, clear } = props; const [inputMessage, setInputMessage] = useState(''); const [loading, setLoading] = useState(isLoading); @@ -38,6 +39,7 @@ const Chatbot: React.FC = (props) => { const [copyMessageId, setCopyMessageId] = useState(null); const [chatsMode, setChatsMode] = useState('graph+vector'); const [graphEntitites, setgraphEntitites] = useState<[]>([]); + const [messageError, setmessageError] = useState(''); const [value, copy] = useCopyToClipboard(); const { speak, cancel } = useSpeechSynthesis({ @@ -77,6 +79,7 @@ const Chatbot: React.FC = (props) => { mode?: string; cypher_query?: string; graphonly_entities?: []; + error?: string; }, index = 0 ) => { @@ -106,6 +109,7 @@ const Chatbot: React.FC = (props) => { mode: response?.mode, cypher_query: response?.cypher_query, graphonly_entities: response?.graphonly_entities, + error: response.error, }, ]); } else { @@ -127,6 +131,7 @@ const Chatbot: React.FC = (props) => { lastmsg.mode = response?.mode; lastmsg.cypher_query = response.cypher_query; lastmsg.graphonly_entities = response.graphonly_entities; + lastmsg.error = response.error; return msgs.map((msg, index) => { if (index === msgs.length - 1) { return lastmsg; @@ -159,6 +164,7 @@ const Chatbot: React.FC = (props) => { let chatingMode; let cypher_query; let graphonly_entities; + let error; const datetime = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`; const userMessage = { id: Date.now(), user: 'user', message: inputMessage, datetime: datetime }; setListMessages([...listMessages, userMessage]); @@ -183,6 +189,7 @@ const Chatbot: React.FC = (props) => { chatingMode = chatresponse?.data?.data?.info?.mode; cypher_query = chatresponse?.data?.data?.info?.cypher_query ?? ''; graphonly_entities = chatresponse?.data.data.info.context ?? []; + error = chatresponse.data.data.info.error ?? ''; const finalbotReply = { reply: chatbotReply, sources: chatSources, @@ -195,6 +202,7 @@ const Chatbot: React.FC = (props) => { mode: chatingMode, cypher_query, graphonly_entities, + error, }; simulateTypingEffect(finalbotReply); } catch (error) { @@ -318,73 +326,70 @@ const Chatbot: React.FC = (props) => { {chat.datetime} - {chat.user === 'chatbot' && - chat.id !== 2 && - chat.sources?.length !== 0 && - !chat.isLoading && - !chat.isTyping && ( -
- { - setModelModal(chat.model ?? ''); - setSourcesModal(chat.sources ?? []); - setResponseTime(chat.response_time ?? 0); - setChunkModal(chat.chunk_ids ?? []); - setTokensUsed(chat.total_tokens ?? 0); - setcypherQuery(chat.cypher_query ?? ''); - setShowInfoModal(true); - setChatsMode(chat.mode ?? ''); - setgraphEntitites(chat.graphonly_entities ?? []); - }} - > - {' '} - {buttonCaptions.details} - - handleCopy(chat.message, chat.id)} - disabled={chat.isTyping || chat.isLoading} - > - - - {copyMessageId === chat.id && ( - <> - Copied! - {value} - + {chat.user === 'chatbot' && chat.id !== 2 && !chat.isLoading && !chat.isTyping && ( +
+ { + setModelModal(chat.model ?? ''); + setSourcesModal(chat.sources ?? []); + setResponseTime(chat.response_time ?? 0); + setChunkModal(chat.chunk_ids ?? []); + setTokensUsed(chat.total_tokens ?? 0); + setcypherQuery(chat.cypher_query ?? ''); + setShowInfoModal(true); + setChatsMode(chat.mode ?? ''); + setgraphEntitites(chat.graphonly_entities ?? []); + setmessageError(chat.error ?? ''); + }} + > + {' '} + {buttonCaptions.details} + + handleCopy(chat.message, chat.id)} + disabled={chat.isTyping || chat.isLoading} + > + + + {copyMessageId === chat.id && ( + <> + Copied! + {value} + + )} + { + if (chat.speaking) { + handleCancel(chat.id); + } else { + handleSpeak(chat.message, chat.id); + } + }} + text={chat.speaking ? tooltips.stopSpeaking : tooltips.textTospeech} + disabled={listMessages.some((msg) => msg.speaking && msg.id !== chat.id)} + label={chat.speaking ? 'stop speaking' : 'text to speech'} + > + {chat.speaking ? ( + + ) : ( + )} - { - if (chat.speaking) { - handleCancel(chat.id); - } else { - handleSpeak(chat.message, chat.id); - } - }} - text={chat.speaking ? tooltips.stopSpeaking : tooltips.textTospeech} - disabled={listMessages.some((msg) => msg.speaking && msg.id !== chat.id)} - label={chat.speaking ? 'stop speaking' : 'text to speech'} - > - {chat.speaking ? ( - - ) : ( - - )} - -
- )} + +
+ )} @@ -393,50 +398,63 @@ const Chatbot: React.FC = (props) => {
-
+ - +
- setShowInfoModal(false)} - open={showInfoModal} - > -
- setShowInfoModal(false)} - > - - -
- -
+ }> + setShowInfoModal(false)} + open={showInfoModal} + > +
+ setShowInfoModal(false)} + > + + +
+ +
+
); }; diff --git a/frontend/src/components/ChatBot/ExpandedChatButtonContainer.tsx b/frontend/src/components/ChatBot/ExpandedChatButtonContainer.tsx index d22f3c8e0..49925466f 100644 --- a/frontend/src/components/ChatBot/ExpandedChatButtonContainer.tsx +++ b/frontend/src/components/ChatBot/ExpandedChatButtonContainer.tsx @@ -1,18 +1,12 @@ import { TrashIconOutline, XMarkIconOutline } from '@neo4j-ndl/react/icons'; import ChatModeToggle from './ChatModeToggle'; import { Box, IconButton } from '@neo4j-ndl/react'; -import { Messages } from '../../types'; +import { IconProps } from '../../types'; import IconButtonWithToolTip from '../UI/IconButtonToolTip'; import { tooltips } from '../../utils/Constants'; import { useState } from 'react'; import { RiChatSettingsLine } from 'react-icons/ri'; -interface IconProps { - closeChatBot: () => void; - deleteOnClick?: () => void; - messages: Messages[]; -} - const ExpandedChatButtonContainer: React.FC = ({ closeChatBot, deleteOnClick, messages }) => { const [chatAnchor, setchatAnchor] = useState(null); const [showChatModeOption, setshowChatModeOption] = useState(false); diff --git a/frontend/src/components/ChatBot/Info/InfoModal.tsx b/frontend/src/components/ChatBot/Info/InfoModal.tsx index 4519fd476..d9cc709d3 100644 --- a/frontend/src/components/ChatBot/Info/InfoModal.tsx +++ b/frontend/src/components/ChatBot/Info/InfoModal.tsx @@ -6,7 +6,16 @@ import wikipedialogo from '../../../assets/images/Wikipedia-logo-v2.svg'; import youtubelogo from '../../../assets/images/youtube.png'; import gcslogo from '../../../assets/images/gcs.webp'; import s3logo from '../../../assets/images/s3logo.png'; -import { Chunk, Entity, GroupedEntity, UserCredentials, chatInfoMessage } from '../../../types'; + +import { + Chunk, + Entity, + ExtendedNode, + ExtendedRelationship, + GroupedEntity, + UserCredentials, + chatInfoMessage, +} from '../../../types'; import { useEffect, useMemo, useState } from 'react'; import HoverableLink from '../../UI/HoverableLink'; import GraphViewButton from '../../Graph/GraphViewButton'; @@ -22,12 +31,12 @@ const InfoModal: React.FC = ({ sources, model, total_tokens, re const [infoEntities, setInfoEntities] = useState([]); const [loading, setLoading] = useState(false); const { userCredentials } = useCredentials(); - const [nodes, setNodes] = useState([]); - const [relationships, setRelationships] = useState([]); + const [nodes, setNodes] = useState([]); + const [relationships, setRelationships] = useState([]); const [chunks, setChunks] = useState([]); const parseEntity = (entity: Entity) => { const { labels, properties } = entity; - const label = labels[0]; + const [label] = labels; const text = properties.id; return { label, text }; }; @@ -72,7 +81,7 @@ const InfoModal: React.FC = ({ sources, model, total_tokens, re const counts: { [label: string]: number } = {}; infoEntities.forEach((entity) => { const { labels } = entity; - const label = labels[0]; + const [label] = labels; counts[label] = counts[label] ? counts[label] + 1 : 1; }); return counts; diff --git a/frontend/src/components/Content.tsx b/frontend/src/components/Content.tsx index da71cc9bd..64945d5d5 100644 --- a/frontend/src/components/Content.tsx +++ b/frontend/src/components/Content.tsx @@ -1,49 +1,64 @@ -import { useEffect, useState, useMemo, useRef } from 'react'; -import ConnectionModal from './Popups/ConnectionModal/ConnectionModal'; -import FileTable, { ChildRef } from './FileTable'; -import { Button, Typography, Flex, StatusIndicator } from '@neo4j-ndl/react'; +import { useEffect, useState, useMemo, useRef, Suspense } from 'react'; +import FileTable from './FileTable'; +import { Button, Typography, Flex, StatusIndicator, useMediaQuery } from '@neo4j-ndl/react'; import { useCredentials } from '../context/UserCredentials'; import { useFileContext } from '../context/UsersFiles'; import CustomAlert from './UI/Alert'; import { extractAPI } from '../utils/FileAPI'; -import { ContentProps, CustomFile, OptionType, UserCredentials, alertStateType } from '../types'; +import { + ChildRef, + ContentProps, + CustomFile, + OptionType, + UserCredentials, + alertStateType, + connectionState, +} from '../types'; import deleteAPI from '../services/DeleteFiles'; import { postProcessing } from '../services/PostProcessing'; -import DeletePopUp from './Popups/DeletePopUp/DeletePopUp'; import { triggerStatusUpdateAPI } from '../services/ServerSideStatusUpdateAPI'; import useServerSideEvent from '../hooks/useSse'; import { useSearchParams } from 'react-router-dom'; -import ConfirmationDialog from './Popups/LargeFilePopUp/ConfirmationDialog'; -import { buttonCaptions, defaultLLM, largeFileSize, llms, taskParam, tooltips } from '../utils/Constants'; +import { batchSize, buttonCaptions, defaultLLM, largeFileSize, llms, tooltips } from '../utils/Constants'; import ButtonWithToolTip from './UI/ButtonWithToolTip'; import connectAPI from '../services/ConnectAPI'; -import SettingModalHOC from '../HOC/SettingModalHOC'; import DropdownComponent from './Dropdown'; import GraphViewModal from './Graph/GraphViewModal'; -import GraphEnhancementDialog from './Popups/GraphEnhancementDialog'; import { OverridableStringUnion } from '@mui/types'; import { AlertColor, AlertPropsColorOverrides } from '@mui/material'; +import { lazy } from 'react'; +import FallBackDialog from './UI/FallBackDialog'; +import DeletePopUp from './Popups/DeletePopUp/DeletePopUp'; +import GraphEnhancementDialog from './Popups/GraphEnhancementDialog'; +import { tokens } from '@neo4j-ndl/base'; +const ConnectionModal = lazy(() => import('./Popups/ConnectionModal/ConnectionModal')); +const ConfirmationDialog = lazy(() => import('./Popups/LargeFilePopUp/ConfirmationDialog')); +let afterFirstRender = false; const Content: React.FC = ({ isLeftExpanded, isRightExpanded, - openTextSchema, isSchema, setIsSchema, showEnhancementDialog, - setshowEnhancementDialog, - closeSettingModal + toggleEnhancementDialog, + closeSettingModal, }) => { + const { breakpoints } = tokens; + const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); const [init, setInit] = useState(false); - const [openConnection, setOpenConnection] = useState(false); + const [openConnection, setOpenConnection] = useState({ + openPopUp: false, + chunksExists: false, + vectorIndexMisMatch: false, + chunksExistsWithDifferentDimension: false, + }); const [openGraphView, setOpenGraphView] = useState(false); const [inspectedName, setInspectedName] = useState(''); const [connectionStatus, setConnectionStatus] = useState(false); const { setUserCredentials, userCredentials } = useCredentials(); const [showConfirmationModal, setshowConfirmationModal] = useState(false); const [extractLoading, setextractLoading] = useState(false); - const [isLargeFile, setIsLargeFile] = useState(false); - const [showSettingnModal, setshowSettingModal] = useState(false); const { filesData, @@ -55,6 +70,10 @@ const Content: React.FC = ({ setSelectedNodes, setRowSelection, setSelectedRels, + postProcessingTasks, + queue, + processedCount, + setProcessedCount, } = useFileContext(); const [viewPoint, setViewPoint] = useState<'tableView' | 'showGraphView' | 'chatInfoView'>('tableView'); const [showDeletePopUp, setshowDeletePopUp] = useState(false); @@ -83,12 +102,6 @@ const Content: React.FC = ({ } ); const childRef = useRef(null); - const openGraphEnhancementDialog = () => { - setshowEnhancementDialog(true); - }; - const closeGraphEnhancementDialog = () => { - setshowEnhancementDialog(false); - }; const showAlert = ( alertmsg: string, alerttype: OverridableStringUnion | undefined @@ -112,11 +125,11 @@ const Content: React.FC = ({ port: neo4jConnection.uri.split(':')[2], }); } else { - setOpenConnection(true); + setOpenConnection((prev) => ({ ...prev, openPopUp: true })); } setInit(true); } else { - setOpenConnection(true); + setOpenConnection((prev) => ({ ...prev, openPopUp: true })); } }, []); @@ -128,13 +141,29 @@ const Content: React.FC = ({ }); }, [model]); + useEffect(() => { + if (afterFirstRender) { + localStorage.setItem('processedCount', JSON.stringify({ db: userCredentials?.uri, count: processedCount })); + } + if (processedCount == batchSize) { + handleGenerateGraph([], true); + } + }, [processedCount, userCredentials]); + + useEffect(() => { + if (afterFirstRender) { + localStorage.setItem('waitingQueue', JSON.stringify({ db: userCredentials?.uri, queue: queue.items })); + } + afterFirstRender = true; + }, [queue.items.length, userCredentials]); + const handleDropdownChange = (selectedOption: OptionType | null | void) => { if (selectedOption?.value) { setModel(selectedOption?.value); } }; - const extractData = async (uid: string, isselectedRows = false) => { + const extractData = async (uid: string, isselectedRows = false, filesTobeProcess: CustomFile[]) => { if (!isselectedRows) { const fileItem = filesData.find((f) => f.id == uid); if (fileItem) { @@ -142,7 +171,7 @@ const Content: React.FC = ({ await extractHandler(fileItem, uid); } } else { - const fileItem = childRef.current?.getSelectedRows().find((f) => f.id == uid); + const fileItem = filesTobeProcess.find((f) => f.id == uid); if (fileItem) { setextractLoading(true); await extractHandler(fileItem, uid); @@ -249,44 +278,146 @@ const Content: React.FC = ({ } }; - const handleGenerateGraph = (allowLargeFiles: boolean, selectedFilesFromAllfiles: CustomFile[]) => { - setIsLargeFile(false); + const triggerBatchProcessing = ( + batch: CustomFile[], + selectedFiles: CustomFile[], + isSelectedFiles: boolean, + newCheck: boolean + ) => { const data = []; - if (selectedfileslength && allowLargeFiles) { - for (let i = 0; i < selectedfileslength; i++) { - const row = childRef.current?.getSelectedRows()[i]; - if (row?.status === 'New') { - data.push(extractData(row.id, true)); + setalertDetails({ + showAlert: true, + alertMessage: `Processing ${batch.length} files at a time.`, + alertType: 'info', + }); + for (let i = 0; i < batch.length; i++) { + if (newCheck) { + if (batch[i]?.status === 'New') { + data.push(extractData(batch[i].id, isSelectedFiles, selectedFiles as CustomFile[])); } + } else { + data.push(extractData(batch[i].id, isSelectedFiles, selectedFiles as CustomFile[])); + } + } + return data; + }; + + const addFilesToQueue = (remainingFiles: CustomFile[]) => { + remainingFiles.forEach((f) => { + setFilesData((prev) => + prev.map((pf) => { + if (pf.id === f.id) { + return { + ...pf, + status: 'Waiting', + }; + } + return pf; + }) + ); + queue.enqueue(f); + }); + }; + + const scheduleBatchWiseProcess = (selectedRows: CustomFile[], isSelectedFiles: boolean) => { + let data = []; + if (queue.size() > batchSize) { + const batch = queue.items.slice(0, batchSize); + data = triggerBatchProcessing(batch, selectedRows as CustomFile[], isSelectedFiles, false); + } else { + let mergedfiles = [...selectedRows]; + let filesToProcess: CustomFile[] = []; + if (mergedfiles.length > batchSize) { + filesToProcess = mergedfiles.slice(0, batchSize); + const remainingFiles = [...(mergedfiles as CustomFile[])].splice(batchSize); + addFilesToQueue(remainingFiles); + } else { + filesToProcess = mergedfiles; + } + data = triggerBatchProcessing(filesToProcess, selectedRows as CustomFile[], isSelectedFiles, false); + } + return data; + }; + + /** + * Processes files in batches, respecting a maximum batch size. + * + * This function prioritizes processing files from the queue if it's not empty. + * If the queue is empty, it processes the provided `filesTobeProcessed`: + * - If the number of files exceeds the batch size, it processes a batch and queues the rest. + * - If the number of files is within the batch size, it processes them all. + * - If there are already files being processed, it adjusts the batch size to avoid exceeding the limit. + * + * @param filesTobeProcessed - The files to be processed. + * @param queueFiles - Whether to prioritize processing files from the queue. Defaults to false. + */ + const handleGenerateGraph = (filesTobeProcessed: CustomFile[], queueFiles: boolean = false) => { + let data = []; + const processingFilesCount = filesData.filter((f) => f.status === 'Processing').length; + if (filesTobeProcessed.length && !queueFiles && processingFilesCount < batchSize) { + if (!queue.isEmpty()) { + data = scheduleBatchWiseProcess(filesTobeProcessed as CustomFile[], true); + } else if (filesTobeProcessed.length > batchSize) { + const filesToProcess = filesTobeProcessed?.slice(0, batchSize) as CustomFile[]; + data = triggerBatchProcessing(filesToProcess, filesTobeProcessed as CustomFile[], true, false); + const remainingFiles = [...(filesTobeProcessed as CustomFile[])].splice(batchSize); + addFilesToQueue(remainingFiles); + } else { + let filesTobeSchedule: CustomFile[] = filesTobeProcessed; + if (filesTobeProcessed.length + processingFilesCount > batchSize) { + filesTobeSchedule = filesTobeProcessed.slice( + 0, + filesTobeProcessed.length + processingFilesCount - batchSize + ) as CustomFile[]; + const idstoexclude = new Set(filesTobeSchedule.map((f) => f.id)); + const remainingFiles = [...(childRef.current?.getSelectedRows() as CustomFile[])].filter( + (f) => !idstoexclude.has(f.id) + ); + addFilesToQueue(remainingFiles); + } + data = triggerBatchProcessing(filesTobeSchedule, filesTobeProcessed, true, true); } Promise.allSettled(data).then(async (_) => { setextractLoading(false); - await postProcessing(userCredentials as UserCredentials, taskParam); + await postProcessing(userCredentials as UserCredentials, postProcessingTasks); }); - } else if (selectedFilesFromAllfiles.length && allowLargeFiles) { - // @ts-ignore - for (let i = 0; i < selectedFilesFromAllfiles.length; i++) { - if (selectedFilesFromAllfiles[i]?.status === 'New') { - data.push(extractData(selectedFilesFromAllfiles[i].id as string)); - } - } + } else if (queueFiles && !queue.isEmpty() && processingFilesCount < batchSize) { + data = scheduleBatchWiseProcess(queue.items, true); Promise.allSettled(data).then(async (_) => { setextractLoading(false); - await postProcessing(userCredentials as UserCredentials, taskParam); + await postProcessing(userCredentials as UserCredentials, postProcessingTasks); }); + } else { + addFilesToQueue(filesTobeProcessed as CustomFile[]); } }; + function processWaitingFilesOnRefresh() { + let data = []; + const processingFilesCount = filesData.filter((f) => f.status === 'Processing').length; + + if (!queue.isEmpty() && processingFilesCount < batchSize) { + if (queue.size() > batchSize) { + const batch = queue.items.slice(0, batchSize); + data = triggerBatchProcessing(batch, queue.items as CustomFile[], true, false); + } else { + data = triggerBatchProcessing(queue.items, queue.items as CustomFile[], true, false); + } + Promise.allSettled(data).then(async (_) => { + setextractLoading(false); + await postProcessing(userCredentials as UserCredentials, postProcessingTasks); + }); + } else { + const selectedNewFiles = childRef.current?.getSelectedRows().filter((f) => f.status === 'New'); + addFilesToQueue(selectedNewFiles as CustomFile[]); + } + } const handleClose = () => { - setalertDetails({ - showAlert: false, - alertType: 'info', - alertMessage: '', - }); + setalertDetails((prev) => ({ ...prev, showAlert: false, alertMessage: '' })); }; const handleOpenGraphClick = () => { - const bloomUrl = process.env.BLOOM_URL; + const bloomUrl = process.env.VITE_BLOOM_URL; const uriCoded = userCredentials?.uri.replace(/:\d+$/, ''); const connectURL = `${uriCoded?.split('//')[0]}//${userCredentials?.userName}@${uriCoded?.split('//')[1]}:${ userCredentials?.port ?? '7687' @@ -311,6 +442,8 @@ const Content: React.FC = ({ }; const disconnect = () => { + queue.clear(); + setProcessedCount(0); setConnectionStatus(false); localStorage.removeItem('password'); setUserCredentials({ uri: '', password: '', userName: '', database: '' }); @@ -333,7 +466,10 @@ const Content: React.FC = ({ [childRef.current?.getSelectedRows()] ); - const dropdowncheck = useMemo(() => !filesData.some((f) => f.status === 'New'), [filesData]); + const dropdowncheck = useMemo( + () => !filesData.some((f) => f.status === 'New' || f.status === 'Waiting'), + [filesData] + ); const disableCheck = useMemo( () => (!selectedfileslength ? dropdowncheck : !newFilecheck), @@ -345,14 +481,6 @@ const Content: React.FC = ({ [selectedfileslength, completedfileNo] ); - // const processingCheck = () => { - // const processingFiles = filesData.some((file) => file.status === 'Processing'); - // const selectedRowProcessing = childRef.current?.getSelectedRows().some((row) => - // filesData.some((file) => file.name === row && file.status === 'Processing') - // ); - // return processingFiles || selectedRowProcessing; - // }; - const filesForProcessing = useMemo(() => { let newstatusfiles: CustomFile[] = []; if (childRef.current?.getSelectedRows().length) { @@ -376,6 +504,8 @@ const Content: React.FC = ({ childRef.current?.getSelectedRows() as CustomFile[], deleteEntities ); + queue.clear(); + setProcessedCount(0); setRowSelection({}); setdeleteLoading(false); if (response.data.status == 'Success') { @@ -416,10 +546,36 @@ const Content: React.FC = ({ console.log(parsedData.uri); const response = await connectAPI(parsedData.uri, parsedData.user, parsedData.password, parsedData.database); if (response?.data?.status === 'Success') { - setConnectionStatus(true); - setOpenConnection(false); + localStorage.setItem( + 'neo4j.connection', + JSON.stringify({ + ...parsedData, + userDbVectorIndex: response.data.data.db_vector_dimension, + }) + ); + if ( + (response.data.data.application_dimension === response.data.data.db_vector_dimension || + response.data.data.db_vector_dimension == 0) && + !response.data.data.chunks_exists + ) { + setConnectionStatus(true); + setOpenConnection((prev) => ({ ...prev, openPopUp: false })); + } else { + setOpenConnection({ + openPopUp: true, + chunksExists: response.data.data.chunks_exists as boolean, + vectorIndexMisMatch: + response.data.data.db_vector_dimension > 0 && + response.data.data.db_vector_dimension != response.data.data.application_dimension, + chunksExistsWithDifferentDimension: + response.data.data.db_vector_dimension > 0 && + response.data.data.db_vector_dimension != response.data.data.application_dimension && + (response.data.data.chunks_exists ?? true), + }); + setConnectionStatus(false); + } } else { - setOpenConnection(true); + setOpenConnection((prev) => ({ ...prev, openPopUp: true })); setConnectionStatus(false); } })(); @@ -434,109 +590,47 @@ const Content: React.FC = ({ }, [isSchema]); const onClickHandler = () => { - if (isSchema) { - if (childRef.current?.getSelectedRows().length) { - let selectedLargeFiles: CustomFile[] = []; - childRef.current?.getSelectedRows().forEach((f) => { - const parsedData: CustomFile = f; - if (parsedData.fileSource === 'local file') { - if (typeof parsedData.size === 'number' && parsedData.status === 'New' && parsedData.size > largeFileSize) { - selectedLargeFiles.push(parsedData); - } - } - }); - // @ts-ignore - if (selectedLargeFiles.length) { - setIsLargeFile(true); - setshowConfirmationModal(true); - handleGenerateGraph(false, []); - } else { - setIsLargeFile(false); - handleGenerateGraph(true, filesData); - } - } else if (filesData.length) { - const largefiles = filesData.filter((f) => { - if (typeof f.size === 'number' && f.status === 'New' && f.size > largeFileSize) { - return true; - } - return false; - }); - const selectAllNewFiles = filesData.filter((f) => f.status === 'New'); - const stringified = selectAllNewFiles.reduce((accu, f) => { - const key = f.id; - // @ts-ignore - accu[key] = true; - return accu; - }, {}); - setRowSelection(stringified); - if (largefiles.length) { - setIsLargeFile(true); - setshowConfirmationModal(true); - handleGenerateGraph(false, []); - } else { - setIsLargeFile(false); - handleGenerateGraph(true, filesData); + if (childRef.current?.getSelectedRows().length) { + let selectedLargeFiles: CustomFile[] = []; + childRef.current?.getSelectedRows().forEach((f) => { + const parsedData: CustomFile = f; + if ( + parsedData.fileSource === 'local file' && + typeof parsedData.size === 'number' && + parsedData.status === 'New' && + parsedData.size > largeFileSize + ) { + selectedLargeFiles.push(parsedData); } + }); + if (selectedLargeFiles.length) { + setshowConfirmationModal(true); + } else { + handleGenerateGraph(childRef.current?.getSelectedRows().filter((f) => f.status === 'New')); } - } else { - if (childRef.current?.getSelectedRows().length) { - let selectedLargeFiles: CustomFile[] = []; - childRef.current?.getSelectedRows().forEach((f) => { - const parsedData: CustomFile = f; - if (parsedData.fileSource === 'local file') { - if (typeof parsedData.size === 'number' && parsedData.status === 'New' && parsedData.size > largeFileSize) { - selectedLargeFiles.push(parsedData); - } - } - }); - // @ts-ignore - if (selectedLargeFiles.length) { - setIsLargeFile(true); - } else { - setIsLargeFile(false); - } - } else if (filesData.length) { - const largefiles = filesData.filter((f) => { - if (typeof f.size === 'number' && f.status === 'New' && f.size > largeFileSize) { - return true; - } - return false; - }); - const selectAllNewFiles = filesData.filter((f) => f.status === 'New'); - const stringified = selectAllNewFiles.reduce((accu, f) => { - const key = f.id; - // @ts-ignore - accu[key] = true; - return accu; - }, {}); - setRowSelection(stringified); - if (largefiles.length) { - setIsLargeFile(true); - } else { - setIsLargeFile(false); + } else if (filesData.length) { + const largefiles = filesData.filter((f) => { + if (typeof f.size === 'number' && f.status === 'New' && f.size > largeFileSize) { + return true; } + return false; + }); + const selectAllNewFiles = filesData.filter((f) => f.status === 'New'); + const stringified = selectAllNewFiles.reduce((accu, f) => { + const key = f.id; + // @ts-ignore + accu[key] = true; + return accu; + }, {}); + setRowSelection(stringified); + if (largefiles.length) { + setshowConfirmationModal(true); + } else { + handleGenerateGraph(filesData.filter((f) => f.status === 'New')); } - setshowSettingModal(true); } }; - const handleContinue = () => { - if (!isLargeFile) { - handleGenerateGraph(true, filesData); - setshowSettingModal(false); - } else { - setshowSettingModal(false); - setshowConfirmationModal(true); - handleGenerateGraph(false, []); - } - setIsSchema(true); - setalertDetails({ - showAlert: true, - alertType: 'success', - alertMessage: 'Schema is set successfully', - }); - localStorage.setItem('isSchema', JSON.stringify(true)); - }; return ( <> {alertDetails.showAlert && ( @@ -556,13 +650,16 @@ const Content: React.FC = ({ /> )} {showConfirmationModal && filesForProcessing.length && ( - setshowConfirmationModal(false)} - loading={extractLoading} - > + }> + setshowConfirmationModal(false)} + loading={extractLoading} + selectedRows={childRef.current?.getSelectedRows() as CustomFile[]} + > + )} {showDeletePopUp && ( = ({ view='contentView' > )} - {showSettingnModal && ( - setshowSettingModal(false)} - onContinue={handleContinue} - open={showSettingnModal} - openTextSchema={openTextSchema} - isSchema={isSchema} - setIsSchema={setIsSchema} - /> - )} {showEnhancementDialog && ( )}
- - + + }> + + +
Neo4j connection @@ -631,15 +723,27 @@ const Content: React.FC = ({
- + {!connectionStatus ? ( - ) : ( - )} @@ -655,13 +759,14 @@ const Content: React.FC = ({ setViewPoint('tableView'); }} ref={childRef} + handleGenerateGraph={processWaitingFilesOnRefresh} > = ({ view='ContentView' isDisabled={false} /> - + = ({ onClick={onClickHandler} disabled={disableCheck} className='mr-0.5' + size={isTablet ? 'small' : 'medium'} > {buttonCaptions.generateGraph}{' '} {selectedfileslength && !disableCheck && newFilecheck ? `(${newFilecheck})` : ''} @@ -690,6 +796,7 @@ const Content: React.FC = ({ disabled={showGraphCheck} className='mr-0.5' label='show graph' + size={isTablet ? 'small' : 'medium'} > {buttonCaptions.showPreviewGraph} {selectedfileslength && completedfileNo ? `(${completedfileNo})` : ''} @@ -700,6 +807,7 @@ const Content: React.FC = ({ disabled={!filesData.some((f) => f?.status === 'Completed')} className='ml-0.5' label='Open Graph with Bloom' + size={isTablet ? 'small' : 'medium'} > {buttonCaptions.exploreGraphWithBloom} @@ -712,6 +820,7 @@ const Content: React.FC = ({ disabled={!selectedfileslength} className='ml-0.5' label='Delete Files' + size={isTablet ? 'small' : 'medium'} > {buttonCaptions.deleteFiles} {selectedfileslength != undefined && selectedfileslength > 0 && `(${selectedfileslength})`} diff --git a/frontend/src/components/DataSources/AWS/S3Bucket.tsx b/frontend/src/components/DataSources/AWS/S3Bucket.tsx index e39dc949f..da2d0ca29 100644 --- a/frontend/src/components/DataSources/AWS/S3Bucket.tsx +++ b/frontend/src/components/DataSources/AWS/S3Bucket.tsx @@ -3,9 +3,15 @@ import s3logo from '../../../assets/images/s3logo.png'; import CustomButton from '../../UI/CustomButton'; import { buttonCaptions } from '../../../utils/Constants'; -const S3Component: React.FC = ({ openModal }) => { +const S3Component: React.FC = ({ openModal, isLargeDesktop = true }) => { return ( - + ); }; diff --git a/frontend/src/components/DataSources/AWS/S3Modal.tsx b/frontend/src/components/DataSources/AWS/S3Modal.tsx index e9d11fd8a..2f40c9709 100644 --- a/frontend/src/components/DataSources/AWS/S3Modal.tsx +++ b/frontend/src/components/DataSources/AWS/S3Modal.tsx @@ -1,6 +1,6 @@ import { TextInput } from '@neo4j-ndl/react'; import React, { useState } from 'react'; -import { CustomFile, CustomFileBase, S3ModalProps, UserCredentials } from '../../../types'; +import { CustomFile, CustomFileBase, S3File, S3ModalProps, UserCredentials } from '../../../types'; import { urlScanAPI } from '../../../services/URLScan'; import { useCredentials } from '../../../context/UserCredentials'; import { validation } from '../../../utils/Utils'; @@ -8,11 +8,7 @@ import { useFileContext } from '../../../context/UsersFiles'; import { v4 as uuidv4 } from 'uuid'; import CustomModal from '../../../HOC/CustomModal'; import { buttonCaptions } from '../../../utils/Constants'; -interface S3File { - fileName: string; - fileSize: number; - url: string; -} + const S3Modal: React.FC = ({ hideModal, open }) => { const [bucketUrl, setBucketUrl] = useState(''); const [accessKey, setAccessKey] = useState(''); @@ -100,7 +96,6 @@ const S3Modal: React.FC = ({ hideModal, open }) => { model: defaultValues.model, fileSource: defaultValues.fileSource, processingProgress: defaultValues.processingProgress, - // total_pages: 'N/A', }); } }); diff --git a/frontend/src/components/DataSources/GCS/GCSButton.tsx b/frontend/src/components/DataSources/GCS/GCSButton.tsx index d81539032..1ad31fdfd 100644 --- a/frontend/src/components/DataSources/GCS/GCSButton.tsx +++ b/frontend/src/components/DataSources/GCS/GCSButton.tsx @@ -2,9 +2,15 @@ import gcslogo from '../../../assets/images/gcs.webp'; import { DataComponentProps } from '../../../types'; import { buttonCaptions } from '../../../utils/Constants'; import CustomButton from '../../UI/CustomButton'; -const GCSButton: React.FC = ({ openModal }) => { +const GCSButton: React.FC = ({ openModal, isLargeDesktop = true }) => { return ( - + ); }; export default GCSButton; diff --git a/frontend/src/components/DataSources/GCS/GCSModal.tsx b/frontend/src/components/DataSources/GCS/GCSModal.tsx index 08c4282d8..a4cd3d8f9 100644 --- a/frontend/src/components/DataSources/GCS/GCSModal.tsx +++ b/frontend/src/components/DataSources/GCS/GCSModal.tsx @@ -98,7 +98,6 @@ const GCSModal: React.FC = ({ hideModal, open, openGCSModal }) => gcsBucket: item.gcsBucketName, gcsBucketFolder: item.gcsBucketFolder, google_project_id: item.gcsProjectId, - // total_pages: 'N/A', id: uuidv4(), access_token: codeResponse.access_token, ...defaultValues, @@ -116,7 +115,6 @@ const GCSModal: React.FC = ({ hideModal, open, openGCSModal }) => fileSource: defaultValues.fileSource, processingProgress: defaultValues.processingProgress, access_token: codeResponse.access_token, - // total_pages: 'N/A', }); } }); @@ -145,7 +143,7 @@ const GCSModal: React.FC = ({ hideModal, open, openGCSModal }) => }, }); - const submitHandler = async () => { + const submitHandler = () => { if (bucketName.trim() === '' || projectId.trim() === '') { setStatus('danger'); setStatusMessage('Please Fill the Credentials'); diff --git a/frontend/src/components/DataSources/Local/DropZone.tsx b/frontend/src/components/DataSources/Local/DropZone.tsx index b775e4231..63ff08e37 100644 --- a/frontend/src/components/DataSources/Local/DropZone.tsx +++ b/frontend/src/components/DataSources/Local/DropZone.tsx @@ -1,4 +1,3 @@ -import axios from 'axios'; import { Dropzone, Flex, Typography } from '@neo4j-ndl/react'; import React, { useState, useEffect, FunctionComponent } from 'react'; import Loader from '../../../utils/Loader'; @@ -6,11 +5,11 @@ import { v4 as uuidv4 } from 'uuid'; import { useCredentials } from '../../../context/UserCredentials'; import { useFileContext } from '../../../context/UsersFiles'; import CustomAlert from '../../UI/Alert'; -import { CustomFile, CustomFileBase, UploadResponse, alertStateType } from '../../../types'; +import { CustomFile, CustomFileBase, UserCredentials, alertStateType } from '../../../types'; import { buttonCaptions, chunkSize } from '../../../utils/Constants'; -import { url } from '../../../utils/Utils'; import { InformationCircleIconOutline } from '@neo4j-ndl/react/icons'; import IconButtonWithToolTip from '../../UI/IconButtonToolTip'; +import { uploadAPI } from '../../../utils/FileAPI'; const DropZone: FunctionComponent = () => { const { filesData, setFilesData, model } = useFileContext(); @@ -74,11 +73,7 @@ const DropZone: FunctionComponent = () => { }; const handleClose = () => { - setalertDetails({ - showAlert: false, - alertMessage: '', - alertType: 'error', - }); + setalertDetails((prev) => ({ ...prev, showAlert: false, alertMessage: '' })); }; useEffect(() => { @@ -121,16 +116,18 @@ const DropZone: FunctionComponent = () => { }) ); try { - const apiResponse = await axios.post(`${url()}/upload`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - if (apiResponse?.data.status === 'Failed') { + const apiResponse = await uploadAPI( + chunk, + userCredentials as UserCredentials, + model, + chunkNumber, + totalChunks, + file.name + ); + if (apiResponse?.status === 'Failed') { throw new Error(`message:${apiResponse.data.message},fileName:${apiResponse.data.file_name}`); } else { - if (apiResponse.data.data) { + if (apiResponse.data) { setFilesData((prevfiles) => prevfiles.map((curfile) => { if (curfile.name == file.name) { diff --git a/frontend/src/components/DataSources/Local/DropZoneForSmallLayouts.tsx b/frontend/src/components/DataSources/Local/DropZoneForSmallLayouts.tsx new file mode 100644 index 000000000..06a16b639 --- /dev/null +++ b/frontend/src/components/DataSources/Local/DropZoneForSmallLayouts.tsx @@ -0,0 +1,247 @@ +import { CloudArrowUpIconSolid } from '@neo4j-ndl/react/icons'; +import { useDropzone } from 'react-dropzone'; +import { useFileContext } from '../../../context/UsersFiles'; +import { useEffect, useState } from 'react'; +import { useCredentials } from '../../../context/UserCredentials'; +import { alertStateType, CustomFile, CustomFileBase, UserCredentials } from '../../../types'; +import { chunkSize } from '../../../utils/Constants'; +import { uploadAPI } from '../../../utils/FileAPI'; +import CustomAlert from '../../UI/Alert'; +import { v4 as uuidv4 } from 'uuid'; +import { LoadingSpinner } from '@neo4j-ndl/react'; + +export default function DropZoneForSmallLayouts() { + const { filesData, setFilesData, model } = useFileContext(); + const [isLoading, setIsLoading] = useState(false); + const [isClicked, setIsClicked] = useState(false); + const { userCredentials } = useCredentials(); + const [alertDetails, setalertDetails] = useState({ + showAlert: false, + alertType: 'error', + alertMessage: '', + }); + const [selectedFiles, setSelectedFiles] = useState([]); + useEffect(() => { + if (selectedFiles.length > 0) { + selectedFiles.forEach((file, uid) => { + if (filesData[uid]?.status == 'None' && isClicked) { + uploadFileInChunks(file); + } + }); + } + }, [selectedFiles]); + const handleClose = () => { + setalertDetails((prev) => ({ ...prev, showAlert: false, alertMessage: '' })); + }; + const uploadFileInChunks = (file: File) => { + const totalChunks = Math.ceil(file.size / chunkSize); + const chunkProgressIncrement = 100 / totalChunks; + let chunkNumber = 1; + let start = 0; + let end = chunkSize; + const uploadNextChunk = async () => { + if (chunkNumber <= totalChunks) { + const chunk = file.slice(start, end); + const formData = new FormData(); + formData.append('file', chunk); + formData.append('chunkNumber', chunkNumber.toString()); + formData.append('totalChunks', totalChunks.toString()); + formData.append('originalname', file.name); + formData.append('model', model); + for (const key in userCredentials) { + formData.append(key, userCredentials[key]); + } + setIsLoading(true); + setFilesData((prevfiles) => + prevfiles.map((curfile) => { + if (curfile.name == file.name) { + return { + ...curfile, + status: 'Uploading', + }; + } + return curfile; + }) + ); + try { + const apiResponse = await uploadAPI( + chunk, + userCredentials as UserCredentials, + model, + chunkNumber, + totalChunks, + file.name + ); + if (apiResponse?.status === 'Failed') { + throw new Error(`message:${apiResponse.data.message},fileName:${apiResponse.data.file_name}`); + } else { + if (apiResponse.data) { + setFilesData((prevfiles) => + prevfiles.map((curfile) => { + if (curfile.name == file.name) { + return { + ...curfile, + uploadprogess: chunkNumber * chunkProgressIncrement, + }; + } + return curfile; + }) + ); + } + setFilesData((prevfiles) => + prevfiles.map((curfile) => { + if (curfile.name == file.name) { + return { + ...curfile, + uploadprogess: chunkNumber * chunkProgressIncrement, + }; + } + return curfile; + }) + ); + chunkNumber++; + start = end; + if (start + chunkSize < file.size) { + end = start + chunkSize; + } else { + end = file.size + 1; + } + uploadNextChunk(); + } + } catch (error) { + setIsLoading(false); + setalertDetails({ + showAlert: true, + alertType: 'error', + alertMessage: 'Error Occurred', + }); + setFilesData((prevfiles) => + prevfiles.map((curfile) => { + if (curfile.name == file.name) { + return { + ...curfile, + status: 'Failed', + type: `${file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length).toUpperCase()}`, + }; + } + return curfile; + }) + ); + } + } else { + setFilesData((prevfiles) => + prevfiles.map((curfile) => { + if (curfile.name == file.name) { + return { + ...curfile, + status: 'New', + uploadprogess: 100, + }; + } + return curfile; + }) + ); + setIsClicked(false); + setIsLoading(false); + setalertDetails({ + showAlert: true, + alertType: 'success', + alertMessage: `${file.name} uploaded successfully`, + }); + } + }; + + uploadNextChunk(); + }; + const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ + accept: { + 'application/pdf': ['.pdf'], + 'image/*': ['.jpeg', '.jpg', '.png', '.svg'], + 'text/html': ['.html'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'], + 'text/plain': ['.txt'], + 'application/vnd.ms-powerpoint': ['.pptx'], + 'application/vnd.ms-excel': ['.xls'], + 'text/markdown': ['.md'], + }, + onDrop: (f: Partial[]) => { + onDropHandler(f); + }, + onDropRejected: (e) => { + if (e.length) { + setalertDetails({ + showAlert: true, + alertType: 'error', + alertMessage: 'Failed To Upload, Unsupported file extention', + }); + } + }, + disabled: isLoading, + }); + + const onDropHandler = (f: Partial[]) => { + setIsClicked(true); + setSelectedFiles(f.map((f) => f as File)); + setIsLoading(false); + if (f.length) { + const defaultValues: CustomFileBase = { + processing: 0, + status: 'None', + NodesCount: 0, + relationshipCount: 0, + model: model, + fileSource: 'local file', + uploadprogess: 0, + processingProgress: undefined, + }; + + const copiedFilesData: CustomFile[] = [...filesData]; + + f.forEach((file) => { + const filedataIndex = copiedFilesData.findIndex((filedataitem) => filedataitem?.name === file?.name); + if (filedataIndex == -1) { + copiedFilesData.unshift({ + name: file.name, + // @ts-ignore + type: `${file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length).toUpperCase()}`, + size: file.size, + uploadprogess: file.size && file?.size < chunkSize ? 100 : 0, + id: uuidv4(), + ...defaultValues, + }); + } else { + const tempFileData = copiedFilesData[filedataIndex]; + copiedFilesData.splice(filedataIndex, 1); + copiedFilesData.unshift({ + ...tempFileData, + status: defaultValues.status, + NodesCount: defaultValues.NodesCount, + relationshipCount: defaultValues.relationshipCount, + processing: defaultValues.processing, + model: defaultValues.model, + fileSource: defaultValues.fileSource, + processingProgress: defaultValues.processingProgress, + }); + } + }); + setFilesData(copiedFilesData); + } + }; + console.log(acceptedFiles); + return ( + <> + {alertDetails.showAlert && ( + + )} +
+ + {isLoading ? : } +
+ + ); +} diff --git a/frontend/src/components/DataSources/Web/WebButton.tsx b/frontend/src/components/DataSources/Web/WebButton.tsx new file mode 100644 index 000000000..6e1d85f4b --- /dev/null +++ b/frontend/src/components/DataSources/Web/WebButton.tsx @@ -0,0 +1,20 @@ +import React, { useContext } from 'react'; +import CustomButton from '../../UI/CustomButton'; +import { DataComponentProps } from '../../../types'; +import { ThemeWrapperContext } from '../../../context/ThemeWrapper'; +import internet from '../../../assets/images/web-search-svgrepo-com.svg'; +import internetdarkmode from '../../../assets/images/web-search-darkmode-final2.svg'; + +const WebButton: React.FC = ({ openModal, isLargeDesktop = true }) => { + const themeUtils = useContext(ThemeWrapperContext); + + return ( + + ); +}; +export default WebButton; diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx index db1a8f768..ba949aec0 100644 --- a/frontend/src/components/Dropdown.tsx +++ b/frontend/src/components/Dropdown.tsx @@ -1,16 +1,8 @@ -import { Dropdown } from '@neo4j-ndl/react'; -import { DropdownProps, OptionType } from '../types'; -import { useMemo } from 'react'; +import { Dropdown, Tip } from '@neo4j-ndl/react'; +import { OptionType, ReusableDropdownProps } from '../types'; +import { useMemo, useReducer } from 'react'; import { capitalize } from '../utils/Utils'; -interface ReusableDropdownProps extends DropdownProps { - options: string[] | OptionType[]; - placeholder?: string; - defaultValue?: string; - children?: React.ReactNode; - view?: 'ContentView' | 'GraphView'; - isDisabled: boolean; - value?: OptionType; -} + const DropdownComponent: React.FC = ({ options, placeholder, @@ -21,6 +13,7 @@ const DropdownComponent: React.FC = ({ isDisabled, value, }) => { + const [disableTooltip, toggleDisableState] = useReducer((state) => !state, false); const handleChange = (selectedOption: OptionType | null | void) => { onSelect(selectedOption); }; @@ -28,36 +21,48 @@ const DropdownComponent: React.FC = ({ return ( <>
- { - const label = - typeof option === 'string' - ? (option.includes('LLM_MODEL_CONFIG_') - ? capitalize(option.split('LLM_MODEL_CONFIG_').at(-1) as string) - : capitalize(option) - ) - .split('_') - .join(' ') - : capitalize(option.label); - const value = typeof option === 'string' ? option : option.value; - return { - label, - value, - }; - }), - placeholder: placeholder || 'Select an option', - defaultValue: defaultValue ? { label: capitalize(defaultValue), value: defaultValue } : undefined, - menuPlacement: 'auto', - isDisabled: isDisabled, - value: value, - }} - size='medium' - fluid - /> + + + { + const label = + typeof option === 'string' + ? (option.includes('LLM_MODEL_CONFIG_') + ? capitalize(option.split('LLM_MODEL_CONFIG_').at(-1) as string) + : capitalize(option) + ) + .split('_') + .join(' ') + : capitalize(option.label); + const value = typeof option === 'string' ? option : option.value; + return { + label, + value, + }; + }), + placeholder: placeholder || 'Select an option', + defaultValue: defaultValue ? { label: capitalize(defaultValue), value: defaultValue } : undefined, + menuPlacement: 'auto', + isDisabled: isDisabled, + value: value, + onMenuOpen: () => { + toggleDisableState(); + }, + onMenuClose: () => { + toggleDisableState(); + }, + }} + size='medium' + fluid + /> + + LLM Model used for Extraction & Chat + {children}
diff --git a/frontend/src/components/FileTable.tsx b/frontend/src/components/FileTable.tsx index 202777187..23310eaf4 100644 --- a/frontend/src/components/FileTable.tsx +++ b/frontend/src/components/FileTable.tsx @@ -9,8 +9,7 @@ import { TextLink, Typography, } from '@neo4j-ndl/react'; -import { forwardRef, HTMLProps, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; -import React from 'react'; +import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { useReactTable, getCoreRowModel, @@ -27,7 +26,15 @@ import { useFileContext } from '../context/UsersFiles'; import { getSourceNodes } from '../services/GetFiles'; import { v4 as uuidv4 } from 'uuid'; import { statusCheck, capitalize } from '../utils/Utils'; -import { SourceNode, CustomFile, FileTableProps, UserCredentials, statusupdate, alertStateType } from '../types'; +import { + SourceNode, + CustomFile, + FileTableProps, + UserCredentials, + statusupdate, + alertStateType, + ChildRef, +} from '../types'; import { useCredentials } from '../context/UserCredentials'; import { MagnifyingGlassCircleIconSolid } from '@neo4j-ndl/react/icons'; import CustomAlert from './UI/Alert'; @@ -39,20 +46,22 @@ import { AxiosError } from 'axios'; import { XMarkIconOutline } from '@neo4j-ndl/react/icons'; import cancelAPI from '../services/CancelAPI'; import IconButtonWithToolTip from './UI/IconButtonToolTip'; -import { largeFileSize } from '../utils/Constants'; - -export interface ChildRef { - getSelectedRows: () => CustomFile[]; -} - +import { batchSize, largeFileSize, llms } from '../utils/Constants'; +import IndeterminateCheckbox from './UI/CustomCheckBox'; +let onlyfortheFirstRender = true; const FileTable = forwardRef((props, ref) => { const { isExpanded, connectionStatus, setConnectionStatus, onInspect } = props; - const { filesData, setFilesData, model, rowSelection, setRowSelection, setSelectedRows } = useFileContext(); + const { filesData, setFilesData, model, rowSelection, setRowSelection, setSelectedRows, setProcessedCount, queue } = + useFileContext(); const { userCredentials } = useCredentials(); const columnHelper = createColumnHelper(); const [columnFilters, setColumnFilters] = useState([]); const [isLoading, setIsLoading] = useState(false); - // const [currentOuterHeight, setcurrentOuterHeight] = useState(window.outerHeight); + const [statusFilter, setstatusFilter] = useState(''); + const [filetypeFilter, setFiletypeFilter] = useState(''); + const [fileSourceFilter, setFileSourceFilter] = useState(''); + const [llmtypeFilter, setLLmtypeFilter] = useState(''); + const skipPageResetRef = useRef(false); const [alertDetails, setalertDetails] = useState({ showAlert: false, alertType: 'error', @@ -78,7 +87,6 @@ const FileTable = forwardRef((props, ref) => { }); } ); - const columns = useMemo( () => [ { @@ -215,6 +223,61 @@ const FileTable = forwardRef((props, ref) => { footer: (info) => info.column.id, filterFn: 'statusFilter' as any, size: 200, + meta: { + columnActions: { + actions: [ + { + title: ( + + All Files + + ), + onClick: () => { + setstatusFilter('All'); + table.getColumn('status')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }, + { + title: ( + + Completed Files + + ), + onClick: () => { + setstatusFilter('Completed'); + table.getColumn('status')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }, + { + title: ( + + New Files + + ), + onClick: () => { + setstatusFilter('New'); + table.getColumn('status')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }, + { + title: ( + + Failed Files + + ), + onClick: () => { + setstatusFilter('Failed'); + table.getColumn('status')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }, + ], + defaultSortingActions: false, + }, + }, }), columnHelper.accessor((row) => row.uploadprogess, { id: 'uploadprogess', @@ -265,22 +328,95 @@ const FileTable = forwardRef((props, ref) => { {info.row.original.fileSource} - {' '} - / + - {info.row.original.type}
); } return (
- {info.row.original.fileSource} / + {info.row.original.fileSource} +
+ ); + }, + header: () => Source, + footer: (info) => info.column.id, + filterFn: 'fileSourceFilter' as any, + meta: { + columnActions: { + actions: [ + { + title: ( + + All Sources + + ), + onClick: () => { + setFileSourceFilter('All'); + table.getColumn('source')?.setFilterValue(true); + }, + }, + ...Array.from(new Set(filesData.map((f) => f.fileSource))).map((t) => { + return { + title: ( + + {t} + + ), + onClick: () => { + setFileSourceFilter(t as string); + table.getColumn('source')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }; + }), + ], + defaultSortingActions: false, + }, + }, + }), + columnHelper.accessor((row) => row, { + id: 'type', + cell: (info) => { + return ( +
{info.row.original.type}
); }, - header: () => Source/Type, + header: () => Type, footer: (info) => info.column.id, + filterFn: 'fileTypeFilter' as any, + meta: { + columnActions: { + actions: [ + { + title: ( + + All Types + + ), + onClick: () => { + setFiletypeFilter('All'); + table.getColumn('type')?.setFilterValue(true); + }, + }, + ...Array.from(new Set(filesData.map((f) => f.type))).map((t) => { + return { + title: ( + {t} + ), + onClick: () => { + setFiletypeFilter(t as string); + table.getColumn('type')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }; + }), + ], + defaultSortingActions: false, + }, + }, }), columnHelper.accessor((row) => row.model, { id: 'model', @@ -299,6 +435,38 @@ const FileTable = forwardRef((props, ref) => { }, header: () => Model, footer: (info) => info.column.id, + filterFn: 'llmTypeFilter' as any, + meta: { + columnActions: { + actions: [ + { + title: ( + + All + + ), + onClick: () => { + setLLmtypeFilter('All'); + table.getColumn('model')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }, + ...llms.map((m) => { + return { + title: ( + {m} + ), + onClick: () => { + setLLmtypeFilter(m); + table.getColumn('model')?.setFilterValue(true); + skipPageResetRef.current = true; + }, + }; + }), + ], + defaultSortingActions: false, + }, + }, }), columnHelper.accessor((row) => row.NodesCount, { id: 'NodesCount', @@ -312,12 +480,6 @@ const FileTable = forwardRef((props, ref) => { header: () => Relations, footer: (info) => info.column.id, }), - // columnHelper.accessor((row) => row.total_pages, { - // id: 'Total pages', - // cell: (info) => {info.getValue()}, - // header: () => Total pages, - // footer: (info) => info.column.id, - // }), columnHelper.accessor((row) => row.status, { id: 'inspect', cell: (info) => ( @@ -339,10 +501,65 @@ const FileTable = forwardRef((props, ref) => { footer: (info) => info.column.id, }), ], - [] + [filesData.length, statusFilter, filetypeFilter, llmtypeFilter, fileSourceFilter] ); + const table = useReactTable({ + data: filesData, + columns, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onColumnFiltersChange: setColumnFilters, + state: { + columnFilters, + rowSelection, + }, + onRowSelectionChange: setRowSelection, + filterFns: { + statusFilter: (row, columnId, filterValue) => { + if (statusFilter === 'All') { + return row; + } + const value = filterValue ? row.original[columnId] === statusFilter : row.original[columnId]; + return value; + }, + fileTypeFilter: (row) => { + if (filetypeFilter === 'All') { + return true; + } + return row.original.type === filetypeFilter; + }, + fileSourceFilter: (row) => { + if (fileSourceFilter === 'All') { + return true; + } + return row.original.fileSource === fileSourceFilter; + }, + llmTypeFilter: (row) => { + if (llmtypeFilter === 'All') { + return true; + } + return row.original.model === llmtypeFilter; + }, + }, + enableGlobalFilter: false, + autoResetPageIndex: skipPageResetRef.current, + enableRowSelection: true, + enableMultiRowSelection: true, + getRowId: (row) => row.id, + enableSorting: true, + getSortedRowModel: getSortedRowModel(), + }); + + useEffect(() => { + skipPageResetRef.current = false; + }, [filesData.length]); + useEffect(() => { + const waitingQueue: CustomFile[] = JSON.parse( + localStorage.getItem('waitingQueue') ?? JSON.stringify({ queue: [] }) + ).queue; const fetchFiles = async () => { try { setIsLoading(true); @@ -350,11 +567,29 @@ const FileTable = forwardRef((props, ref) => { if (!res.data) { throw new Error('Please check backend connection'); } + const stringified = waitingQueue.reduce((accu, f) => { + const key = f.id; + // @ts-ignore + accu[key] = true; + return accu; + }, {}); + setRowSelection(stringified); if (res.data.status !== 'Failed') { const prefiles: CustomFile[] = []; if (res.data.data.length) { res.data.data.forEach((item: SourceNode) => { if (item.fileName != undefined && item.fileName.length) { + const waitingFile = + waitingQueue.length && waitingQueue.find((f: CustomFile) => f.name === item.fileName); + if (waitingFile && item.status === 'Completed') { + setProcessedCount((prev) => { + if (prev === batchSize) { + return batchSize - 1; + } + return prev + 1; + }); + queue.remove(item.fileName); + } prefiles.push({ name: item?.fileName, size: item?.fileSize ?? 0, @@ -364,20 +599,22 @@ const FileTable = forwardRef((props, ref) => { NodesCount: item?.nodeCount ?? 0, processing: item?.processingTime ?? 'None', relationshipCount: item?.relationshipCount ?? 0, - status: - item?.fileSource === 's3 bucket' && localStorage.getItem('accesskey') === item?.awsAccessKeyId - ? item?.status - : item?.fileSource === 'local file' - ? item?.status - : item?.status === 'Completed' || item.status === 'Failed' - ? item?.status - : item?.fileSource == 'Wikipedia' || - item?.fileSource == 'youtube' || - item?.fileSource == 'gcs bucket' - ? item?.status - : 'N/A', - model: item?.model ?? model, - id: uuidv4(), + status: waitingFile + ? 'Waiting' + : item?.fileSource === 's3 bucket' && localStorage.getItem('accesskey') === item?.awsAccessKeyId + ? item?.status + : item?.fileSource === 'local file' + ? item?.status + : item?.status === 'Completed' || item.status === 'Failed' + ? item?.status + : item?.fileSource === 'Wikipedia' || + item?.fileSource === 'youtube' || + item?.fileSource === 'gcs bucket' || + item?.fileSource === 'web-url' + ? item?.status + : 'N/A', + model: waitingFile ? waitingFile.model : item?.model ?? model, + id: !waitingFile ? uuidv4() : waitingFile.id, source_url: item?.url != 'None' && item?.url != '' ? item.url : '', fileSource: item?.fileSource ?? 'None', gcsBucket: item?.gcsBucket, @@ -397,52 +634,60 @@ const FileTable = forwardRef((props, ref) => { }); } }); - } - setIsLoading(false); - setFilesData(prefiles); - res.data.data.forEach((item) => { - if ( - item.status === 'Processing' && - item.fileName != undefined && - userCredentials && - userCredentials.database - ) { - if (item?.fileSize < largeFileSize) { - subscribe( - item.fileName, - userCredentials?.uri, - userCredentials?.userName, - userCredentials?.database, - userCredentials?.password, - updatestatus, - updateProgress - ).catch((error: AxiosError) => { - // @ts-ignore - const errorfile = decodeURI(error?.config?.url?.split('?')[0].split('/').at(-1)); - setFilesData((prevfiles) => { - return prevfiles.map((curfile) => { - if (curfile.name == errorfile) { - return { - ...curfile, - status: 'Failed', - }; + res.data.data.forEach((item) => { + if ( + item.status === 'Processing' && + item.fileName != undefined && + userCredentials && + userCredentials.database + ) { + if (item?.fileSize < largeFileSize) { + subscribe( + item.fileName, + userCredentials?.uri, + userCredentials?.userName, + userCredentials?.database, + userCredentials?.password, + updatestatus, + updateProgress + ).catch((error: AxiosError) => { + // @ts-ignore + const errorfile = decodeURI(error?.config?.url?.split('?')[0].split('/').at(-1)); + setProcessedCount((prev) => { + if (prev == batchSize) { + return batchSize - 1; } - return curfile; + return prev + 1; + }); + setFilesData((prevfiles) => { + return prevfiles.map((curfile) => { + if (curfile.name == errorfile) { + return { + ...curfile, + status: 'Failed', + }; + } + return curfile; + }); }); }); - }); - } else { - triggerStatusUpdateAPI( - item.fileName, - userCredentials.uri, - userCredentials.userName, - userCredentials.password, - userCredentials.database, - updateStatusForLargeFiles - ); + } else { + triggerStatusUpdateAPI( + item.fileName, + userCredentials.uri, + userCredentials.userName, + userCredentials.password, + userCredentials.database, + updateStatusForLargeFiles + ); + } } - } - }); + }); + } else { + queue.clear(); + } + setIsLoading(false); + setFilesData(prefiles); } else { throw new Error(res?.data?.error); } @@ -465,6 +710,30 @@ const FileTable = forwardRef((props, ref) => { } }, [connectionStatus, userCredentials]); + useEffect(() => { + if (connectionStatus && filesData.length && onlyfortheFirstRender) { + const processingFilesCount = filesData.filter((f) => f.status === 'Processing').length; + if (processingFilesCount) { + if (processingFilesCount === 1) { + setProcessedCount(1); + } + setalertDetails({ + showAlert: true, + alertType: 'info', + alertMessage: `Files are in processing please wait till previous batch completes`, + }); + } else { + const waitingQueue: CustomFile[] = JSON.parse( + localStorage.getItem('waitingQueue') ?? JSON.stringify({ queue: [] }) + ).queue; + if (waitingQueue.length) { + props.handleGenerateGraph(); + } + } + onlyfortheFirstRender = false; + } + }, [connectionStatus, filesData.length]); + const cancelHandler = async (fileName: string, id: string, fileSource: string) => { setFilesData((prevfiles) => prevfiles.map((curfile) => { @@ -492,6 +761,13 @@ const FileTable = forwardRef((props, ref) => { return curfile; }) ); + setProcessedCount((prev) => { + if (prev == batchSize) { + return batchSize - 1; + } + return prev + 1; + }); + queue.remove(fileName); } else { let errorobj = { error: res.data.error, message: res.data.message, fileName }; throw new Error(JSON.stringify(errorobj)); @@ -551,6 +827,13 @@ const FileTable = forwardRef((props, ref) => { return curfile; }) ); + setProcessedCount((prev) => { + if (prev == batchSize) { + return batchSize - 1; + } + return prev + 1; + }); + queue.remove(fileName); } }; @@ -575,39 +858,6 @@ const FileTable = forwardRef((props, ref) => { } }; - // const pageSizeCalculation = Math.floor((currentOuterHeight - 402) / 45); - - const table = useReactTable({ - data: filesData, - columns, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onColumnFiltersChange: setColumnFilters, - // initialState: { - // pagination: { - // pageSize: pageSizeCalculation < 0 ? 9 : pageSizeCalculation, - // }, - // }, - state: { - columnFilters, - rowSelection, - }, - onRowSelectionChange: setRowSelection, - filterFns: { - statusFilter: (row, columnId, filterValue) => { - const value = filterValue ? row.original[columnId] === 'New' : row.original[columnId]; - return value; - }, - }, - enableGlobalFilter: false, - autoResetPageIndex: false, - enableRowSelection: true, - enableMultiRowSelection: true, - getRowId: (row) => row.id, - enableSorting: true, - getSortedRowModel: getSortedRowModel(), - }); useImperativeHandle( ref, () => ({ @@ -622,13 +872,12 @@ const FileTable = forwardRef((props, ref) => { const resizeObserver = new ResizeObserver((entries) => { entries.forEach((entry) => { const { height } = entry.contentRect; - // setcurrentOuterHeight(height); const rowHeight = document?.getElementsByClassName('ndl-data-grid-td')?.[0]?.clientHeight ?? 69; table.setPageSize(Math.floor(height / rowHeight)); }); }); - const contentElement = document.getElementsByClassName('ndl-data-grid-scrollable')[0]; + const [contentElement] = document.getElementsByClassName('ndl-data-grid-scrollable'); resizeObserver.observe(contentElement); return () => { @@ -638,13 +887,6 @@ const FileTable = forwardRef((props, ref) => { } }, []); - const handleChange = (e: React.ChangeEvent) => { - table.getColumn('status')?.setFilterValue(e.target.checked); - if (!table.getCanNextPage() || table.getPrePaginationRowModel().rows.length) { - table.setPageIndex(0); - } - }; - const classNameCheck = isExpanded ? 'fileTableWithExpansion' : `filetable`; const handleClose = () => { @@ -667,10 +909,6 @@ const FileTable = forwardRef((props, ref) => { )} {filesData ? ( <> -
- - -
((props, ref) => { }); export default FileTable; -function IndeterminateCheckbox({ - indeterminate, - className = '', - ...rest -}: { indeterminate?: boolean } & HTMLProps) { - const ref = React.useRef(null!); - - React.useEffect(() => { - if (typeof indeterminate === 'boolean') { - ref.current.indeterminate = !rest.checked && indeterminate; - } - }, [ref, indeterminate]); - - return ( - - ); -} diff --git a/frontend/src/components/Graph/CheckboxSelection.tsx b/frontend/src/components/Graph/CheckboxSelection.tsx index 20e15142b..7e3096daa 100644 --- a/frontend/src/components/Graph/CheckboxSelection.tsx +++ b/frontend/src/components/Graph/CheckboxSelection.tsx @@ -1,28 +1,21 @@ import { Checkbox } from '@neo4j-ndl/react'; import React from 'react'; import { CheckboxSectionProps } from '../../types'; - const CheckboxSelection: React.FC = ({ graphType, loading, handleChange }) => (
handleChange('Document')} + checked={graphType.includes('DocumentChunk')} + label='Document & Chunk' + disabled={loading} + onChange={() => handleChange('DocumentChunk')} /> handleChange('Entities')} /> - handleChange('Chunk')} - />
); diff --git a/frontend/src/components/Graph/GraphViewButton.tsx b/frontend/src/components/Graph/GraphViewButton.tsx index af038d6b2..50eb13972 100644 --- a/frontend/src/components/Graph/GraphViewButton.tsx +++ b/frontend/src/components/Graph/GraphViewButton.tsx @@ -1,12 +1,8 @@ import React, { useState } from 'react'; import { Button } from '@neo4j-ndl/react'; import GraphViewModal from './GraphViewModal'; -import { Node, Relationship } from '@neo4j-nvl/base'; +import { GraphViewButtonProps } from '../../types'; -interface GraphViewButtonProps { - nodeValues?: Node[]; - relationshipValues?: Relationship[]; -} const GraphViewButton: React.FC = ({ nodeValues, relationshipValues }) => { const [openGraphView, setOpenGraphView] = useState(false); const [viewPoint, setViewPoint] = useState(''); diff --git a/frontend/src/components/Graph/GraphViewModal.tsx b/frontend/src/components/Graph/GraphViewModal.tsx index a556628d5..97d01dcb2 100644 --- a/frontend/src/components/Graph/GraphViewModal.tsx +++ b/frontend/src/components/Graph/GraphViewModal.tsx @@ -1,6 +1,23 @@ -import { Banner, Dialog, Flex, IconButtonArray, LoadingSpinner } from '@neo4j-ndl/react'; +import { + Banner, + Dialog, + Flex, + IconButton, + IconButtonArray, + LoadingSpinner, + TextInput, + Typography, + useDebounce, +} from '@neo4j-ndl/react'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { GraphType, GraphViewModalProps, OptionType, Scheme, UserCredentials } from '../../types'; +import { + ExtendedNode, + ExtendedRelationship, + GraphType, + GraphViewModalProps, + Scheme, + UserCredentials, +} from '../../types'; import { InteractiveNvlWrapper } from '@neo4j-nvl/react'; import NVL from '@neo4j-nvl/base'; import type { Node, Relationship } from '@neo4j-nvl/base'; @@ -9,27 +26,26 @@ import { ArrowPathIconOutline, DragIcon, FitToScreenIcon, + MagnifyingGlassIconOutline, MagnifyingGlassMinusIconOutline, MagnifyingGlassPlusIconOutline, } from '@neo4j-ndl/react/icons'; import IconButtonWithToolTip from '../UI/IconButtonToolTip'; -import { filterData, processGraphData } from '../../utils/Utils'; +import { filterData, processGraphData, sortAlphabetically } from '../../utils/Utils'; import { useCredentials } from '../../context/UserCredentials'; import { LegendsChip } from './LegendsChip'; import graphQueryAPI from '../../services/GraphQuery'; import { - entityGraph, - graphQuery, - graphView, + graphLabels, intitalGraphType, - knowledgeGraph, - lexicalGraph, mouseEventCallbacks, nvlOptions, queryMap, + RESULT_STEP_SIZE, } from '../../utils/Constants'; -// import CheckboxSelection from './CheckboxSelection'; -import DropdownComponent from '../Dropdown'; +import CheckboxSelection from './CheckboxSelection'; +import { ShowAll } from '../UI/ShowAll'; + const GraphViewModal: React.FunctionComponent = ({ open, inspectedName, @@ -40,10 +56,10 @@ const GraphViewModal: React.FunctionComponent = ({ selectedRows, }) => { const nvlRef = useRef(null); - const [nodes, setNodes] = useState([]); + const [nodes, setNodes] = useState([]); const [relationships, setRelationships] = useState([]); const [graphType, setGraphType] = useState(intitalGraphType); - const [allNodes, setAllNodes] = useState([]); + const [allNodes, setAllNodes] = useState([]); const [allRelationships, setAllRelationships] = useState([]); const [loading, setLoading] = useState(false); const [status, setStatus] = useState<'unknown' | 'success' | 'danger'>('unknown'); @@ -51,22 +67,42 @@ const GraphViewModal: React.FunctionComponent = ({ const { userCredentials } = useCredentials(); const [scheme, setScheme] = useState({}); const [newScheme, setNewScheme] = useState({}); - const [dropdownVal, setDropdownVal] = useState({ - label: 'Knowledge Graph', - value: queryMap.DocChunkEntities, - }); + const [searchQuery, setSearchQuery] = useState(''); + const debouncedQuery = useDebounce(searchQuery, 300); - // const handleCheckboxChange = (graph: GraphType) => { - // const currentIndex = graphType.indexOf(graph); - // const newGraphSelected = [...graphType]; - // if (currentIndex === -1) { - // newGraphSelected.push(graph); - // } else { - // newGraphSelected.splice(currentIndex, 1); - // } - // setGraphType(newGraphSelected); - // }; + // the checkbox selection + const handleCheckboxChange = (graph: GraphType) => { + const currentIndex = graphType.indexOf(graph); + const newGraphSelected = [...graphType]; + if (currentIndex === -1) { + newGraphSelected.push(graph); + initGraph(newGraphSelected, allNodes, allRelationships, scheme); + } else { + newGraphSelected.splice(currentIndex, 1); + initGraph(newGraphSelected, allNodes, allRelationships, scheme); + } + setSearchQuery(''); + setGraphType(newGraphSelected); + }; + + const nodeCount = (nodes: ExtendedNode[], label: string): number => { + return [...new Set(nodes?.filter((n) => n.labels?.includes(label)).map((i) => i.id))].length; + }; + const relationshipCount = (relationships: ExtendedRelationship[], label: string): number => { + return [...new Set(relationships?.filter((r) => r.caption?.includes(label)).map((i) => i.id))].length; + }; + + const graphQuery: string = + graphType.includes('DocumentChunk') && graphType.includes('Entities') + ? queryMap.DocChunkEntities + : graphType.includes('DocumentChunk') + ? queryMap.DocChunks + : graphType.includes('Entities') + ? queryMap.Entities + : ''; + + // fit graph to original position const handleZoomToFit = () => { nvlRef.current?.fit( allNodes.map((node) => node.id), @@ -74,13 +110,15 @@ const GraphViewModal: React.FunctionComponent = ({ ); }; - // Destroy the component + // Unmounting the component useEffect(() => { const timeoutId = setTimeout(() => { handleZoomToFit(); }, 10); return () => { - nvlRef.current?.destroy(); + if (nvlRef.current) { + nvlRef.current?.destroy(); + } setGraphType(intitalGraphType); clearTimeout(timeoutId); setScheme({}); @@ -88,15 +126,14 @@ const GraphViewModal: React.FunctionComponent = ({ setRelationships([]); setAllNodes([]); setAllRelationships([]); - setDropdownVal({ label: 'Knowledge Graph', value: queryMap.DocChunkEntities }); + setSearchQuery(''); }; }, []); - // To get nodes and relations on basis of view const fetchData = useCallback(async () => { try { const nodeRelationshipData = - viewPoint === 'showGraphView' + viewPoint === graphLabels.showGraphView ? await graphQueryAPI( userCredentials as UserCredentials, graphQuery, @@ -110,20 +147,24 @@ const GraphViewModal: React.FunctionComponent = ({ }, [viewPoint, selectedRows, graphQuery, inspectedName, userCredentials]); // Api call to get the nodes and relations - const graphApi = async () => { + const graphApi = async (mode?: string) => { try { const result = await fetchData(); if (result && result.data.data.nodes.length > 0) { const neoNodes = result.data.data.nodes.map((f: Node) => f); const neoRels = result.data.data.relationships.map((f: Relationship) => f); const { finalNodes, finalRels, schemeVal } = processGraphData(neoNodes, neoRels); + if (mode === 'refreshMode') { + initGraph(graphType, finalNodes, finalRels, schemeVal); + } else { + setNodes(finalNodes); + setRelationships(finalRels); + setNewScheme(schemeVal); + setLoading(false); + } setAllNodes(finalNodes); setAllRelationships(finalRels); setScheme(schemeVal); - setNodes(finalNodes); - setRelationships(finalRels); - setNewScheme(schemeVal); - setLoading(false); } else { setLoading(false); setStatus('danger'); @@ -154,16 +195,82 @@ const GraphViewModal: React.FunctionComponent = ({ } }, [open]); + // The search and update nodes + const handleSearch = useCallback( + (value: string) => { + const query = value.toLowerCase(); + const updatedNodes = nodes.map((node) => { + if (query === '') { + return { + ...node, + activated: false, + selected: false, + size: graphLabels.nodeSize, + }; + } + const { id, properties, caption } = node; + const propertiesMatch = properties?.id?.toLowerCase().includes(query); + const match = id.toLowerCase().includes(query) || propertiesMatch || caption?.toLowerCase().includes(query); + return { + ...node, + activated: match, + selected: match, + size: + match && viewPoint === graphLabels.showGraphView + ? 100 + : match && viewPoint !== graphLabels.showGraphView + ? 50 + : graphLabels.nodeSize, + }; + }); + // deactivating any active relationships + const updatedRelationships = relationships.map((rel) => { + return { + ...rel, + activated: false, + selected: false, + }; + }); + setNodes(updatedNodes); + setRelationships(updatedRelationships); + }, + [nodes] + ); + + useEffect(() => { + handleSearch(debouncedQuery); + }, [debouncedQuery]); + + const initGraph = ( + graphType: GraphType[], + finalNodes: ExtendedNode[], + finalRels: Relationship[], + schemeVal: Scheme + ) => { + if (allNodes.length > 0 && allRelationships.length > 0) { + const { filteredNodes, filteredRelations, filteredScheme } = filterData( + graphType, + finalNodes ?? [], + finalRels ?? [], + schemeVal + ); + setNodes(filteredNodes); + setRelationships(filteredRelations); + setNewScheme(filteredScheme); + } + }; + + // Unmounting the component if (!open) { return <>; } const headerTitle = - viewPoint === 'showGraphView' || viewPoint === 'chatInfoView' - ? 'Generated Graph' - : `Inspect Generated Graph from ${inspectedName}`; + viewPoint === graphLabels.showGraphView || viewPoint === graphLabels.chatInfoView + ? graphLabels.generateGraph + : `${graphLabels.inspectGeneratedGraphFrom} ${inspectedName}`; - const dropDownView = viewPoint !== 'chatInfoView'; + const checkBoxView = viewPoint !== graphLabels.chatInfoView; const nvlCallbacks = { onLayoutComputing(isComputing: boolean) { @@ -185,9 +292,11 @@ const GraphViewModal: React.FunctionComponent = ({ // Refresh the graph with nodes and relations if file is processing const handleRefresh = () => { - graphApi(); - setGraphType(intitalGraphType); - setDropdownVal({ label: 'Knowledge Graph', value: queryMap.DocChunkEntities }); + graphApi('refreshMode'); + setGraphType(graphType); + setNodes(nodes); + setRelationships(relationships); + setScheme(newScheme); }; // when modal closes reset all states to default @@ -199,65 +308,92 @@ const GraphViewModal: React.FunctionComponent = ({ setGraphType(intitalGraphType); setNodes([]); setRelationships([]); - setDropdownVal({ label: 'Knowledge Graph', value: queryMap.DocChunkEntities }); + setAllNodes([]); + setAllRelationships([]); + setSearchQuery(''); }; // sort the legends in with Chunk and Document always the first two values - const legendCheck = Object.keys(newScheme).sort((a, b) => { - if (a === 'Document' || a === 'Chunk') { + const nodeCheck = Object.keys(newScheme).sort((a, b) => { + if (a === graphLabels.document || a === graphLabels.chunk) { return -1; - } else if (b === 'Document' || b === 'Chunk') { + } else if (b === graphLabels.document || b === graphLabels.chunk) { return 1; } return a.localeCompare(b); }); - // setting the default dropdown values - const getDropdownDefaultValue = () => { - if (graphType.includes('Document') && graphType.includes('Chunk') && graphType.includes('Entities')) { - return knowledgeGraph; - } - if (graphType.includes('Document') && graphType.includes('Chunk')) { - return lexicalGraph; - } - if (graphType.includes('Entities')) { - return entityGraph; - } - return ''; - }; + // get sorted relationships + const relationshipsSorted = relationships.sort(sortAlphabetically); - // Make a function call to store the nodes and relations in their states - const initGraph = (graphType: GraphType[], finalNodes: Node[], finalRels: Relationship[], schemeVal: Scheme) => { - if (allNodes.length > 0 && allRelationships.length > 0) { - const { filteredNodes, filteredRelations, filteredScheme } = filterData( - graphType, - finalNodes ?? [], - finalRels ?? [], - schemeVal - ); - setNodes(filteredNodes); - setRelationships(filteredRelations); - setNewScheme(filteredScheme); + // To get the relationship count + const groupedAndSortedRelationships: ExtendedRelationship[] = Object.values( + relationshipsSorted.reduce((acc: { [key: string]: ExtendedRelationship }, relType: Relationship) => { + const key = relType.caption || ''; + if (!acc[key]) { + acc[key] = { ...relType, count: 0 }; + } + + acc[key]!.count += relationshipCount(relationships as ExtendedRelationship[], key); + return acc; + }, {}) + ); + + // On Node Click, highlighting the nodes and deactivating any active relationships + const handleNodeClick = (nodeLabel: string) => { + const updatedNodes = nodes.map((node) => { + const isActive = node.labels.includes(nodeLabel); + return { + ...node, + activated: isActive, + selected: isActive, + size: + isActive && viewPoint === graphLabels.showGraphView + ? 100 + : isActive && viewPoint !== graphLabels.showGraphView + ? 50 + : graphLabels.nodeSize, + }; + }); + // deactivating any active relationships + const updatedRelationships = relationships.map((rel) => { + return { + ...rel, + activated: false, + selected: false, + }; + }); + if (searchQuery !== '') { + setSearchQuery(''); } + setNodes(updatedNodes); + setRelationships(updatedRelationships); }; - - // handle dropdown value change and call the init graph method - const handleDropdownChange = (selectedOption: OptionType | null | void) => { - if (selectedOption?.value) { - const selectedValue = selectedOption.value; - let newGraphType: GraphType[] = []; - if (selectedValue === 'entities') { - newGraphType = ['Entities']; - } else if (selectedValue === queryMap.DocChunks) { - newGraphType = ['Document', 'Chunk']; - } else if (selectedValue === queryMap.DocChunkEntities) { - newGraphType = ['Document', 'Entities', 'Chunk']; - } - setGraphType(newGraphType); - setDropdownVal(selectedOption); - initGraph(newGraphType, allNodes, allRelationships, scheme); + // On Relationship Legend Click, highlight the relationships and deactivating any active nodes + const handleRelationshipClick = (nodeLabel: string) => { + const updatedRelations = relationships.map((rel) => { + return { + ...rel, + activated: rel?.caption?.includes(nodeLabel), + selected: rel?.caption?.includes(nodeLabel), + }; + }); + // // deactivating any active nodes + const updatedNodes = nodes.map((node) => { + return { + ...node, + activated: false, + selected: false, + size: graphLabels.nodeSize, + }; + }); + if (searchQuery !== '') { + setSearchQuery(''); } + setRelationships(updatedRelations); + setNodes(updatedNodes); }; + return ( <> = ({ > {headerTitle} - - {/* {checkBoxView && ( - - )} */} - {dropDownView && ( - + + {checkBoxView && ( + )} @@ -300,9 +425,13 @@ const GraphViewModal: React.FunctionComponent = ({
- ) : nodes.length === 0 || relationships.length === 0 ? ( + ) : nodes.length === 0 && relationships.length === 0 && graphType.length !== 0 ? (
- + +
+ ) : graphType.length === 0 ? ( +
+
) : ( <> @@ -367,12 +496,79 @@ const GraphViewModal: React.FunctionComponent = ({ handleClasses={{ left: 'ml-1' }} >
-

Result Overview

-
- {legendCheck.map((key, index) => ( - - ))} -
+ {nodeCheck.length > 0 && ( + <> + + {graphLabels.resultOverview} +
+ { + setSearchQuery(e.target.value); + }} + placeholder='Search On Node Properties' + fluid={true} + leftIcon={ + + + + } + /> +
+ + {graphLabels.totalNodes} ({nodes.length}) + +
+
+ + {nodeCheck.map((nodeLabel, index) => ( + handleNodeClick(nodeLabel)} + /> + ))} + +
+ + )} + {relationshipsSorted.length > 0 && ( + <> + + + {graphLabels.totalRelationships} ({relationships.length}) + + +
+ + {groupedAndSortedRelationships.map((relType, index) => ( + handleRelationshipClick(relType.caption || '')} + /> + ))} + +
+ + )}
diff --git a/frontend/src/components/Graph/LegendsChip.tsx b/frontend/src/components/Graph/LegendsChip.tsx index f1d2900e3..2ab9c7b40 100644 --- a/frontend/src/components/Graph/LegendsChip.tsx +++ b/frontend/src/components/Graph/LegendsChip.tsx @@ -1,12 +1,6 @@ -import { useMemo } from 'react'; import { LegendChipProps } from '../../types'; import Legend from '../UI/Legend'; -export const LegendsChip: React.FunctionComponent = ({ scheme, title, nodes }) => { - const chunkcount = useMemo( - // @ts-ignore - () => [...new Set(nodes?.filter((n) => n?.labels?.includes(title)).map((i) => i.id))].length, - [] - ); - return ; +export const LegendsChip: React.FunctionComponent = ({ scheme, label, type, count, onClick }) => { + return ; }; diff --git a/frontend/src/components/Layout/DrawerChatbot.tsx b/frontend/src/components/Layout/DrawerChatbot.tsx index 470b296dd..d08b2d8d1 100644 --- a/frontend/src/components/Layout/DrawerChatbot.tsx +++ b/frontend/src/components/Layout/DrawerChatbot.tsx @@ -1,12 +1,8 @@ import { Drawer } from '@neo4j-ndl/react'; import Chatbot from '../ChatBot/Chatbot'; -import { Messages } from '../../types'; +import { DrawerChatbotProps, Messages } from '../../types'; import { useMessageContext } from '../../context/UserMessages'; -interface DrawerChatbotProps { - isExpanded: boolean; - clearHistoryData: boolean; - messages: Messages[]; -} + const DrawerChatbot: React.FC = ({ isExpanded, clearHistoryData, messages }) => { const { setMessages } = useMessageContext(); diff --git a/frontend/src/components/Layout/DrawerDropzone.tsx b/frontend/src/components/Layout/DrawerDropzone.tsx index 6bc2e3ea8..f423f62f9 100644 --- a/frontend/src/components/Layout/DrawerDropzone.tsx +++ b/frontend/src/components/Layout/DrawerDropzone.tsx @@ -1,23 +1,29 @@ import { Drawer, Flex, StatusIndicator, Typography } from '@neo4j-ndl/react'; import DropZone from '../DataSources/Local/DropZone'; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, Suspense, lazy } from 'react'; import { healthStatus } from '../../services/HealthStatus'; import S3Component from '../DataSources/AWS/S3Bucket'; -import S3Modal from '../DataSources/AWS/S3Modal'; import { DrawerProps } from '../../types'; import GCSButton from '../DataSources/GCS/GCSButton'; -import GCSModal from '../DataSources/GCS/GCSModal'; import CustomAlert from '../UI/Alert'; import { useAlertContext } from '../../context/Alert'; import { APP_SOURCES } from '../../utils/Constants'; import GenericButton from '../WebSources/GenericSourceButton'; import GenericModal from '../WebSources/GenericSourceModal'; +import FallBackDialog from '../UI/FallBackDialog'; +const S3Modal = lazy(() => import('../DataSources/AWS/S3Modal')); +const GCSModal = lazy(() => import('../DataSources/GCS/GCSModal')); -const DrawerDropzone: React.FC = ({ isExpanded }) => { +const DrawerDropzone: React.FC = ({ + isExpanded, + toggleS3Modal, + toggleGCSModal, + toggleGenericModal, + shows3Modal, + showGCSModal, + showGenericModal, +}) => { const [isBackendConnected, setIsBackendConnected] = useState(false); - const [showModal, setshowModal] = useState(false); - const [showGCSModal, setShowGCSModal] = useState(false); - const [showGenericModal, setshowGenericModal] = useState(false); const { closeAlert, alertState } = useAlertContext(); useEffect(() => { @@ -32,25 +38,6 @@ const DrawerDropzone: React.FC = ({ isExpanded }) => { getHealthStatus(); }, []); - const openModal = useCallback(() => { - setshowModal(true); - }, []); - const hideModal = useCallback(() => { - setshowModal(false); - }, []); - const openGCSModal = useCallback(() => { - setShowGCSModal(true); - }, []); - const hideGCSModal = useCallback(() => { - setShowGCSModal(false); - }, []); - const openGenericModal = useCallback(() => { - setshowGenericModal(true); - }, []); - const closeGenericModal = useCallback(() => { - setshowGenericModal(false); - }, []); - const isYoutubeOnlyCheck = useMemo( () => APP_SOURCES?.includes('youtube') && !APP_SOURCES.includes('wiki') && !APP_SOURCES.includes('web'), [APP_SOURCES] @@ -81,10 +68,10 @@ const DrawerDropzone: React.FC = ({ isExpanded }) => {
- {process.env.ENV != 'PROD' && ( + {process.env.VITE_ENV != 'PROD' && ( {!isBackendConnected ? : } @@ -93,7 +80,7 @@ const DrawerDropzone: React.FC = ({ isExpanded }) => { )}
- {process.env.ENV != 'PROD' ? ( + {process.env.VITE_ENV != 'PROD' ? ( <> {APP_SOURCES != undefined && APP_SOURCES.includes('local') && ( @@ -107,27 +94,41 @@ const DrawerDropzone: React.FC = ({ isExpanded }) => { {(APP_SOURCES.includes('youtube') || APP_SOURCES.includes('wiki') || APP_SOURCES.includes('web')) && ( -
- +
+
)} {APP_SOURCES.includes('s3') && ( -
- - {' '} +
+ + }> + +
)} {APP_SOURCES.includes('gcs') && ( -
- - +
+ + }> + +
)} @@ -147,14 +148,14 @@ const DrawerDropzone: React.FC = ({ isExpanded }) => { {((APP_SOURCES != undefined && APP_SOURCES.includes('youtube')) || (APP_SOURCES != undefined && APP_SOURCES.includes('wiki')) || (APP_SOURCES != undefined && APP_SOURCES.includes('web'))) && ( -
- +
+
)} @@ -162,15 +163,21 @@ const DrawerDropzone: React.FC = ({ isExpanded }) => { (APP_SOURCES != undefined && APP_SOURCES.includes('gcs')) ? ( <> {APP_SOURCES != undefined && APP_SOURCES.includes('s3') && ( -
- - {' '} +
+ + }> + +
)} {APP_SOURCES != undefined && APP_SOURCES.includes('gcs') && ( -
- - +
+ +
)} diff --git a/frontend/src/components/Layout/PageLayout.tsx b/frontend/src/components/Layout/PageLayout.tsx index f46eff186..1a2f755b4 100644 --- a/frontend/src/components/Layout/PageLayout.tsx +++ b/frontend/src/components/Layout/PageLayout.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useReducer, useState } from 'react'; import SideNav from './SideNav'; import DrawerDropzone from './DrawerDropzone'; import DrawerChatbot from './DrawerChatbot'; @@ -8,7 +8,7 @@ import { clearChatAPI } from '../../services/QnaAPI'; import { useCredentials } from '../../context/UserCredentials'; import { UserCredentials, alertStateType } from '../../types'; import { useMessageContext } from '../../context/UserMessages'; -import { AlertColor, AlertPropsColorOverrides } from '@mui/material'; +import { AlertColor, AlertPropsColorOverrides, useMediaQuery } from '@mui/material'; import { OverridableStringUnion } from '@mui/types'; import { useFileContext } from '../../context/UsersFiles'; import SchemaFromTextDialog from '../Popups/Settings/SchemaFromText'; @@ -23,15 +23,31 @@ export default function PageLayoutNew({ closeSettingModal: () => void; openSettingsDialog: () => void; }) { - const [isLeftExpanded, setIsLeftExpanded] = useState(true); - const [isRightExpanded, setIsRightExpanded] = useState(true); + const largedesktops = useMediaQuery(`(min-width:1440px )`); + const [isLeftExpanded, setIsLeftExpanded] = useState(Boolean(largedesktops)); + const [isRightExpanded, setIsRightExpanded] = useState(Boolean(largedesktops)); const [showChatBot, setShowChatBot] = useState(false); const [showDrawerChatbot, setShowDrawerChatbot] = useState(true); const [clearHistoryData, setClearHistoryData] = useState(false); - const [showEnhancementDialog, setshowEnhancementDialog] = useState(false); + const [showEnhancementDialog, toggleEnhancementDialog] = useReducer((s) => !s, false); + const [shows3Modal, toggleS3Modal] = useReducer((s) => !s, false); + const [showGCSModal, toggleGCSModal] = useReducer((s) => !s, false); + const [showGenericModal, toggleGenericModal] = useReducer((s) => !s, false); const { userCredentials } = useCredentials(); - const toggleLeftDrawer = () => setIsLeftExpanded(!isLeftExpanded); - const toggleRightDrawer = () => setIsRightExpanded(!isRightExpanded); + const toggleLeftDrawer = () => { + if (largedesktops) { + setIsLeftExpanded(!isLeftExpanded); + } else { + setIsLeftExpanded(false); + } + }; + const toggleRightDrawer = () => { + if (largedesktops) { + setIsRightExpanded(!isRightExpanded); + } else { + setIsRightExpanded(false); + } + }; const [alertDetails, setalertDetails] = useState({ showAlert: false, alertType: 'error', @@ -67,11 +83,7 @@ export default function PageLayoutNew({ }); }; const handleClose = () => { - setalertDetails({ - showAlert: false, - alertType: 'info', - alertMessage: '', - }); + setalertDetails((prev) => ({ ...prev, showAlert: false, alertMessage: '' })); }; return ( @@ -84,8 +96,23 @@ export default function PageLayoutNew({ alertMessage={alertDetails.alertMessage} /> )} - - + + {showDrawerChatbot && ( @@ -142,6 +168,10 @@ export default function PageLayoutNew({ setShowDrawerChatbot={setShowDrawerChatbot} setIsRightExpanded={setIsRightExpanded} clearHistoryData={clearHistoryData} + toggleGCSModal={toggleGCSModal} + toggles3Modal={toggleS3Modal} + toggleGenericModal={toggleGenericModal} + setIsleftExpanded={setIsLeftExpanded} />
); diff --git a/frontend/src/components/Layout/SideNav.tsx b/frontend/src/components/Layout/SideNav.tsx index 9364e6698..edbfb4d29 100644 --- a/frontend/src/components/Layout/SideNav.tsx +++ b/frontend/src/components/Layout/SideNav.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Dialog, SideNavigation, Tip } from '@neo4j-ndl/react'; +import { Dialog, SideNavigation, Tip, useMediaQuery } from '@neo4j-ndl/react'; import { ArrowRightIconOutline, ArrowLeftIconOutline, @@ -14,10 +14,14 @@ import { createPortal } from 'react-dom'; import { useMessageContext } from '../../context/UserMessages'; import { getIsLoading } from '../../utils/Utils'; import ExpandedChatButtonContainer from '../ChatBot/ExpandedChatButtonContainer'; -import { tooltips } from '../../utils/Constants'; +import { APP_SOURCES, tooltips } from '../../utils/Constants'; import ChatModeToggle from '../ChatBot/ChatModeToggle'; import { RiChatSettingsLine } from 'react-icons/ri'; import IconButtonWithToolTip from '../UI/IconButtonToolTip'; +import GCSButton from '../DataSources/GCS/GCSButton'; +import S3Component from '../DataSources/AWS/S3Bucket'; +import WebButton from '../DataSources/Web/WebButton'; +import DropZoneForSmallLayouts from '../DataSources/Local/DropZoneForSmallLayouts'; const SideNav: React.FC = ({ position, @@ -28,12 +32,18 @@ const SideNav: React.FC = ({ setIsRightExpanded, messages, clearHistoryData, + toggleGCSModal, + toggleGenericModal, + toggles3Modal, + setIsleftExpanded, }) => { const [isChatModalOpen, setIsChatModalOpen] = useState(false); const [isFullScreen, setIsFullScreen] = useState(false); const { setMessages } = useMessageContext(); const [chatModeAnchor, setchatModeAnchor] = useState(null); const [showChatMode, setshowChatMode] = useState(false); + const largedesktops = useMediaQuery(`(min-width:1440px )`); + const date = new Date(); useEffect(() => { if (clearHistoryData) { @@ -60,49 +70,107 @@ const SideNav: React.FC = ({ const handleShrinkClick = () => { setIsChatModalOpen(false); setIsFullScreen(false); - if (setShowDrawerChatbot && setIsRightExpanded) { + if (setShowDrawerChatbot && setIsRightExpanded && largedesktops) { setShowDrawerChatbot(true); setIsRightExpanded(true); } }; const handleClick = () => { - toggleDrawer(); + if (!largedesktops && position === 'right') { + setIsChatModalOpen(true); + setIsFullScreen(true); + } else if (!largedesktops && position === 'left') { + setIsleftExpanded && setIsleftExpanded(false); + } else { + toggleDrawer(); + } }; return (
- - ) : ( - - ) - ) : position === 'left' ? ( - <> - - - - - {tooltips.sources} - - - ) : ( - <> - - - - - {tooltips.chat} - - - ) - } - /> + {isExpanded && largedesktops && ( + : } + /> + )} + {!isExpanded && position === 'left' && largedesktops && ( + + + + + {tooltips.sources} + + } + /> + )} + {position === 'right' && !isExpanded && ( + + + + + {tooltips.chat} + + } + /> + )} + + {!largedesktops && position === 'left' && ( + + + + + Local files + + } + /> + )} + {!largedesktops && APP_SOURCES.includes('gcs') && position === 'left' && ( + + + + + GCS Files + + } + /> + )} + {!largedesktops && APP_SOURCES.includes('s3') && position === 'left' && ( + + + + + S3 Files + + } + /> + )} + {!largedesktops && APP_SOURCES.includes('web') && position === 'left' && ( + + + + + Web Sources + + } + > + )} {position === 'right' && isExpanded && ( <> diff --git a/frontend/src/components/Popups/ConnectionModal/ConnectionModal.tsx b/frontend/src/components/Popups/ConnectionModal/ConnectionModal.tsx index 2e183cfc6..63e2d7883 100644 --- a/frontend/src/components/Popups/ConnectionModal/ConnectionModal.tsx +++ b/frontend/src/components/Popups/ConnectionModal/ConnectionModal.tsx @@ -1,30 +1,31 @@ -import { Button, Dialog, TextInput, Dropdown, Banner, Dropzone, Typography, TextLink } from '@neo4j-ndl/react'; -import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; +import { Button, Dialog, TextInput, Dropdown, Banner, Dropzone, Typography, TextLink, Flex } from '@neo4j-ndl/react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import connectAPI from '../../../services/ConnectAPI'; import { useCredentials } from '../../../context/UserCredentials'; import { useSearchParams } from 'react-router-dom'; import { buttonCaptions } from '../../../utils/Constants'; +import { createVectorIndex } from '../../../services/vectorIndexCreation'; +import { ConnectionModalProps, Message, UserCredentials } from '../../../types'; +import VectorIndexMisMatchAlert from './VectorIndexMisMatchAlert'; -interface Message { - type: 'success' | 'info' | 'warning' | 'danger' | 'unknown'; - content: string; -} - -interface ConnectionModalProps { - open: boolean; - setOpenConnection: Dispatch>; - setConnectionStatus: Dispatch>; -} - -export default function ConnectionModal({ open, setOpenConnection, setConnectionStatus }: ConnectionModalProps) { +export default function ConnectionModal({ + open, + setOpenConnection, + setConnectionStatus, + isVectorIndexMatch, + chunksExistsWithoutEmbedding, + chunksExistsWithDifferentEmbedding, +}: ConnectionModalProps) { let prefilledconnection = localStorage.getItem('neo4j.connection'); let initialuri; let initialdb; let initialusername; let initialport; let initialprotocol; + let initialuserdbvectorindex; if (prefilledconnection) { let parsedcontent = JSON.parse(prefilledconnection); + initialuserdbvectorindex = parsedcontent.userDbVectorIndex; let urisplit = parsedcontent?.uri?.split('://'); initialuri = urisplit[1]; initialdb = parsedcontent?.database; @@ -40,9 +41,11 @@ export default function ConnectionModal({ open, setOpenConnection, setConnection const [username, setUsername] = useState(initialusername ?? 'neo4j'); const [password, setPassword] = useState(''); const [connectionMessage, setMessage] = useState({ type: 'unknown', content: '' }); - const { setUserCredentials } = useCredentials(); + const { setUserCredentials, userCredentials } = useCredentials(); const [isLoading, setIsLoading] = useState(false); const [searchParams, setSearchParams] = useSearchParams(); + const [userDbVectorIndex, setUserDbVectorIndex] = useState(initialuserdbvectorindex ?? undefined); + const [vectorIndexLoading, setVectorIndexLoading] = useState(false); useEffect(() => { if (searchParams.has('connectURL')) { @@ -51,8 +54,67 @@ export default function ConnectionModal({ open, setOpenConnection, setConnection searchParams.delete('connectURL'); setSearchParams(searchParams); } + return () => { + setUserDbVectorIndex(undefined); + }; }, [open]); + const recreateVectorIndex = useCallback( + async (isNewVectorIndex: boolean) => { + try { + setVectorIndexLoading(true); + const response = await createVectorIndex(userCredentials as UserCredentials, isNewVectorIndex); + setVectorIndexLoading(false); + if (response.data.status === 'Failed') { + throw new Error(response.data.error); + } else { + setMessage({ + type: 'success', + content: 'Successfully created the vector index', + }); + setConnectionStatus(true); + localStorage.setItem( + 'neo4j.connection', + JSON.stringify({ + uri: userCredentials?.uri, + user: userCredentials?.userName, + password: userCredentials?.password, + database: userCredentials?.database, + userDbVectorIndex: 384, + }) + ); + } + } catch (error) { + setVectorIndexLoading(false); + if (error instanceof Error) { + console.log('Error in recreating the vector index', error.message); + setMessage({ type: 'danger', content: error.message }); + } + } + setTimeout(() => { + setMessage({ type: 'unknown', content: '' }); + setOpenConnection((prev) => ({ ...prev, openPopUp: false })); + }, 3000); + }, + [userCredentials, userDbVectorIndex] + ); + useEffect(() => { + if (isVectorIndexMatch || chunksExistsWithoutEmbedding) { + setMessage({ + type: 'danger', + content: ( + recreateVectorIndex(chunksExistsWithDifferentEmbedding)} + isVectorIndexAlreadyExists={chunksExistsWithDifferentEmbedding || isVectorIndexMatch} + userVectorIndexDimension={JSON.parse(localStorage.getItem('neo4j.connection') ?? 'null').userDbVectorIndex} + chunksExists={chunksExistsWithoutEmbedding} + /> + ), + }); + } + }, [isVectorIndexMatch, vectorIndexLoading, chunksExistsWithDifferentEmbedding, chunksExistsWithoutEmbedding]); + const parseAndSetURI = (uri: string, urlparams = false) => { const uriParts: string[] = uri.split('://'); let uriHost: string[] | string; @@ -62,7 +124,6 @@ export default function ConnectionModal({ open, setOpenConnection, setConnection // @ts-ignore const hostParts = uriHost.pop()?.split('-'); if (hostParts != undefined) { - console.log(hostParts); if (hostParts.length == 2) { setURI(hostParts.pop() as string); setDatabase(hostParts.pop() as string); @@ -130,27 +191,81 @@ export default function ConnectionModal({ open, setOpenConnection, setConnection const connectionURI = `${protocol}://${URI}${URI.split(':')[1] ? '' : `:${port}`}`; setUserCredentials({ uri: connectionURI, userName: username, password: password, database: database, port: port }); setIsLoading(true); - const response = await connectAPI(connectionURI, username, password, database); - if (response?.data?.status === 'Success') { - localStorage.setItem( - 'neo4j.connection', - JSON.stringify({ uri: connectionURI, user: username, password: password, database: database }) - ); - setConnectionStatus(true); - setMessage({ - type: 'success', - content: response.data.message, - }); - setOpenConnection(false); - } else { - setMessage({ type: 'danger', content: response.data.error }); - setOpenConnection(true); - setPassword(''); - setConnectionStatus(false); + try { + const response = await connectAPI(connectionURI, username, password, database); + setIsLoading(false); + if (response?.data?.status !== 'Success') { + throw new Error(response.data.error); + } else { + setUserDbVectorIndex(response.data.data.db_vector_dimension); + if ( + (response.data.data.application_dimension === response.data.data.db_vector_dimension || + response.data.data.db_vector_dimension == 0) && + !response.data.data.chunks_exists + ) { + setConnectionStatus(true); + setOpenConnection((prev) => ({ ...prev, openPopUp: false })); + setMessage({ + type: 'success', + content: response.data.data.message, + }); + } else if ((response.data.data.chunks_exists ?? true) && response.data.data.db_vector_dimension == 0) { + setMessage({ + type: 'danger', + content: ( + + recreateVectorIndex( + !( + response.data.data.db_vector_dimension > 0 && + response.data.data.db_vector_dimension != response.data.data.application_dimension + ) + ) + } + isVectorIndexAlreadyExists={response.data.data.db_vector_dimension != 0} + chunksExists={true} + /> + ), + }); + } else { + setMessage({ + type: 'danger', + content: ( + recreateVectorIndex(true)} + isVectorIndexAlreadyExists={ + response.data.data.db_vector_dimension != 0 && + response.data.data.db_vector_dimension != response.data.data.application_dimension + } + chunksExists={true} + userVectorIndexDimension={response.data.data.db_vector_dimension} + /> + ), + }); + } + localStorage.setItem( + 'neo4j.connection', + JSON.stringify({ + uri: connectionURI, + user: username, + password: password, + database: database, + userDbVectorIndex, + }) + ); + } + } catch (error) { + setIsLoading(false); + if (error instanceof Error) { + setMessage({ type: 'danger', content: error.message }); + setOpenConnection((prev) => ({ ...prev, openPopUp: true })); + setPassword(''); + setConnectionStatus(false); + } } - setIsLoading(false); setTimeout(() => { - setMessage({ type: 'unknown', content: '' }); setPassword(''); }, 3000); }; @@ -168,9 +283,10 @@ export default function ConnectionModal({ open, setOpenConnection, setConnection open={open} aria-labelledby='form-dialog-title' onClose={() => { - setOpenConnection(false); + setOpenConnection((prev) => ({ ...prev, openPopUp: false })); setMessage({ type: 'unknown', content: '' }); }} + disableCloseButton={vectorIndexLoading} > Connect to Neo4j @@ -179,15 +295,23 @@ export default function ConnectionModal({ open, setOpenConnection, setConnection Don't have a Neo4j instance? Start for free today - {connectionMessage?.type !== 'unknown' && ( - - )} + {connectionMessage?.type !== 'unknown' && + (vectorIndexLoading ? ( + + ) : ( + + ))}
- + + + diff --git a/frontend/src/components/Popups/ConnectionModal/VectorIndexMisMatchAlert.tsx b/frontend/src/components/Popups/ConnectionModal/VectorIndexMisMatchAlert.tsx new file mode 100644 index 000000000..f9e85b63e --- /dev/null +++ b/frontend/src/components/Popups/ConnectionModal/VectorIndexMisMatchAlert.tsx @@ -0,0 +1,55 @@ +import { Box, Flex } from '@neo4j-ndl/react'; +import Markdown from 'react-markdown'; +import ButtonWithToolTip from '../../UI/ButtonWithToolTip'; +import { useCredentials } from '../../../context/UserCredentials'; + +export default function VectorIndexMisMatchAlert({ + vectorIndexLoading, + recreateVectorIndex, + isVectorIndexAlreadyExists, + userVectorIndexDimension, + chunksExists, +}: { + vectorIndexLoading: boolean; + recreateVectorIndex: () => Promise; + isVectorIndexAlreadyExists: boolean; + userVectorIndexDimension?: number; + chunksExists: boolean; +}) { + const { userCredentials } = useCredentials(); + return ( + + + + {isVectorIndexAlreadyExists + ? `**Vector Index Incompatibility** +The existing Neo4j vector index dimension (${userVectorIndexDimension}) is incompatible with the supported dimension (384) for this application. +To proceed, please choose one of the following options: +1.**Recreate Vector Index:** Click "Re-Create Vector Index" to generate a compatible vector index. +2.**Use a Different Instance:** Connect to a Neo4j instance with a compatible vector index configuration ` + : chunksExists + ? `A vector index is essential for performing efficient similarity searches within your data. Without it, some chunks of data will be invisible to queries based on meaning and context. Creating a vector index unlocks the full potential of your data by allowing you to find related information quickly and accurately.` + : ''} + + + + recreateVectorIndex()} + className='!w-full' + color='danger' + disabled={userCredentials === null} + > + {isVectorIndexAlreadyExists ? 'Re-Create Vector Index' : 'Create Vector Index'} + + + + ); +} diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/Deduplication/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/Deduplication/index.tsx new file mode 100644 index 000000000..f5a021e30 --- /dev/null +++ b/frontend/src/components/Popups/GraphEnhancementDialog/Deduplication/index.tsx @@ -0,0 +1,307 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { getDuplicateNodes } from '../../../../services/GetDuplicateNodes'; +import { useCredentials } from '../../../../context/UserCredentials'; +import { dupNodes, selectedDuplicateNodes, UserCredentials } from '../../../../types'; +import { + useReactTable, + getCoreRowModel, + createColumnHelper, + getFilteredRowModel, + getPaginationRowModel, + Table, + Row, + getSortedRowModel, +} from '@tanstack/react-table'; +import { Checkbox, DataGrid, DataGridComponents, Flex, Tag, Typography, useMediaQuery } from '@neo4j-ndl/react'; +import Legend from '../../../UI/Legend'; +import { DocumentIconOutline } from '@neo4j-ndl/react/icons'; +import { calcWordColor } from '@neo4j-devtools/word-color'; +import ButtonWithToolTip from '../../../UI/ButtonWithToolTip'; +import mergeDuplicateNodes from '../../../../services/MergeDuplicateEntities'; +import { tokens } from '@neo4j-ndl/base'; + +export default function DeduplicationTab() { + const { breakpoints } = tokens; + const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); + const isSmallDesktop = useMediaQuery(`(min-width: ${breakpoints.lg})`); + const { userCredentials } = useCredentials(); + const [duplicateNodes, setDuplicateNodes] = useState([]); + const [rowSelection, setRowSelection] = useState>({}); + const [isLoading, setLoading] = useState(false); + const [mergeAPIloading, setmergeAPIloading] = useState(false); + const tableRef = useRef(null); + const fetchDuplicateNodes = useCallback(async () => { + try { + setLoading(true); + const duplicateNodesData = await getDuplicateNodes(userCredentials as UserCredentials); + setLoading(false); + if (duplicateNodesData.data.status === 'Failed') { + throw new Error(duplicateNodesData.data.error); + } + if (duplicateNodesData.data.data.length) { + setDuplicateNodes(duplicateNodesData.data.data); + } else { + setDuplicateNodes([]); + } + } catch (error) { + setLoading(false); + console.log(error); + } + }, [userCredentials]); + useEffect(() => { + (async () => { + await fetchDuplicateNodes(); + })(); + }, [userCredentials]); + + const clickHandler = async () => { + try { + const selectedNodeMap = table.getSelectedRowModel().rows.map( + (r): selectedDuplicateNodes => ({ + firstElementId: r.id, + similarElementIds: r.original.similar.map((s) => s.elementId), + }) + ); + setmergeAPIloading(true); + const response = await mergeDuplicateNodes(userCredentials as UserCredentials, selectedNodeMap); + table.resetRowSelection(); + table.resetPagination(); + setmergeAPIloading(false); + if (response.data.status === 'Failed') { + throw new Error(response.data.error); + } + } catch (error) { + setmergeAPIloading(false); + console.log(error); + } + }; + + const columnHelper = createColumnHelper(); + const onRemove = (nodeid: string, similarNodeId: string) => { + setDuplicateNodes((prev) => { + return prev.map((d) => + (d.e.elementId === nodeid + ? { + ...d, + similar: d.similar.filter((n) => n.elementId != similarNodeId), + } + : d) + ); + }); + }; + const columns = useMemo( + () => [ + { + id: 'Check to Delete All Files', + header: ({ table }: { table: Table }) => { + return ( + + ); + }, + cell: ({ row }: { row: Row }) => { + return ( +
+ +
+ ); + }, + size: 80, + }, + columnHelper.accessor((row) => row.e.id, { + id: 'Id', + cell: (info) => { + return ( +
+ {info.getValue()} +
+ ); + }, + header: () => ID, + footer: (info) => info.column.id, + }), + columnHelper.accessor((row) => row.similar, { + id: 'Similar Nodes', + cell: (info) => { + return ( + + {info.getValue().map((s, index) => ( + { + onRemove(info.row.original.e.elementId, s.elementId); + }} + removeable={true} + type='default' + size={isTablet ? 'small' : 'medium'} + > + {s.id} + + ))} + + ); + }, + size: isTablet || isSmallDesktop ? 250 : 150, + }), + columnHelper.accessor((row) => row.e.labels, { + id: 'Labels', + cell: (info) => { + return ( + + {info.getValue().map((l, index) => ( + + ))} + + ); + }, + header: () => Labels, + footer: (info) => info.column.id, + }), + columnHelper.accessor((row) => row.documents, { + id: 'Connnected Documents', + cell: (info) => { + return ( + + {Array.from(new Set([...info.getValue()])).map((d, index) => ( + + + + + {d} + + ))} + + ); + }, + header: () => Related Documents , + footer: (info) => info.column.id, + }), + columnHelper.accessor((row) => row.chunkConnections, { + id: 'Connected Chunks', + cell: (info) => {info?.getValue()}, + header: () => Connected Chunks, + footer: (info) => info.column.id, + }), + ], + [] + ); + const table = useReactTable({ + data: duplicateNodes, + columns, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + state: { + rowSelection, + }, + onRowSelectionChange: setRowSelection, + enableGlobalFilter: false, + autoResetPageIndex: false, + enableRowSelection: true, + enableMultiRowSelection: true, + getRowId: (row) => row.e.elementId, + enableSorting: true, + getSortedRowModel: getSortedRowModel(), + initialState: { + pagination: { + pageSize: 5, + }, + }, + }); + const selectedFilesCheck = mergeAPIloading + ? 'Merging...' + : table.getSelectedRowModel().rows.length + ? `Merge Duplicate Nodes (${table.getSelectedRowModel().rows.length})` + : 'Select Node(s) to Merge'; + return ( +
+ + + + Refine Your Knowledge Graph: Merge Duplicate Entities: + + + Identify and merge similar entries like "Apple" and "Apple Inc." to eliminate redundancy and improve the + accuracy and clarity of your knowledge graph. + + + {duplicateNodes.length > 0 && ( + + Total Duplicate Nodes: {duplicateNodes.length} + + )} + + , + PaginationNumericButton: ({ isSelected, innerProps, ...restProps }) => { + return ( + + ); + }, + }} + /> + + { + await clickHandler(); + await fetchDuplicateNodes(); + }} + size='large' + loading={mergeAPIloading} + text={ + isLoading + ? 'Fetching Duplicate Nodes' + : !isLoading && !duplicateNodes.length + ? 'No Nodes Found' + : !table.getSelectedRowModel().rows.length + ? 'No Nodes Selected' + : mergeAPIloading + ? 'Merging' + : `Merge Selected Nodes (${table.getSelectedRowModel().rows.length})` + } + label='Merge Duplicate Node Button' + disabled={!table.getSelectedRowModel().rows.length} + placement='top' + > + {selectedFilesCheck} + + +
+ ); +} diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx index bb3f44bb5..f836c9e87 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx @@ -1,4 +1,4 @@ -import { Checkbox, DataGrid, DataGridComponents, Flex, Typography } from '@neo4j-ndl/react'; +import { Checkbox, DataGrid, DataGridComponents, Flex, Typography, useMediaQuery } from '@neo4j-ndl/react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { UserCredentials, orphanNodeProps } from '../../../../types'; import { getOrphanNodes } from '../../../../services/GetOrphanNodes'; @@ -18,6 +18,7 @@ import { getSortedRowModel, } from '@tanstack/react-table'; import DeletePopUp from '../../DeletePopUp/DeletePopUp'; +import { tokens } from '@neo4j-ndl/base'; export default function DeletePopUpForOrphanNodes({ deleteHandler, loading, @@ -25,6 +26,8 @@ export default function DeletePopUpForOrphanNodes({ deleteHandler: (selectedEntities: string[]) => Promise; loading: boolean; }) { + const { breakpoints } = tokens; + const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); const [orphanNodes, setOrphanNodes] = useState([]); const [totalOrphanNodes, setTotalOrphanNodes] = useState(0); const [isLoading, setLoading] = useState(false); @@ -108,7 +111,7 @@ export default function DeletePopUpForOrphanNodes({ return ( {info.getValue().map((l, index) => ( - + ))} ); @@ -200,13 +203,17 @@ export default function DeletePopUpForOrphanNodes({
- Orphan Nodes Deletion (100 nodes per batch) + + Orphan Nodes Deletion (100 nodes per batch) + {totalOrphanNodes > 0 && ( - Total Nodes: {totalOrphanNodes} + + Total Nodes: {totalOrphanNodes} + )} - + This feature helps improve the accuracy of your knowledge graph by identifying and removing entities that are not connected to any other information. These "lonely" entities can be remnants of past analyses or errors in data processing. By removing them, we can create a cleaner and more efficient knowledge graph diff --git a/frontend/src/components/Popups/Settings/EntityExtractionSetting.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/EntityExtractionSetting.tsx similarity index 86% rename from frontend/src/components/Popups/Settings/EntityExtractionSetting.tsx rename to frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/EntityExtractionSetting.tsx index a44ae6ba7..cca1a09a3 100644 --- a/frontend/src/components/Popups/Settings/EntityExtractionSetting.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/EntityExtractionSetting.tsx @@ -1,14 +1,15 @@ import { MouseEventHandler, useCallback, useEffect, useState } from 'react'; -import ButtonWithToolTip from '../../UI/ButtonWithToolTip'; -import { appLabels, buttonCaptions, tooltips } from '../../../utils/Constants'; -import { Dropdown, Flex, Typography } from '@neo4j-ndl/react'; -import { useCredentials } from '../../../context/UserCredentials'; -import { useFileContext } from '../../../context/UsersFiles'; +import ButtonWithToolTip from '../../../UI/ButtonWithToolTip'; +import { appLabels, buttonCaptions, tooltips } from '../../../../utils/Constants'; +import { Dropdown, Flex, Typography, useMediaQuery } from '@neo4j-ndl/react'; +import { useCredentials } from '../../../../context/UserCredentials'; +import { useFileContext } from '../../../../context/UsersFiles'; import { OnChangeValue, ActionMeta } from 'react-select'; -import { OptionType, OptionTypeForExamples, schema, UserCredentials } from '../../../types'; -import { useAlertContext } from '../../../context/Alert'; -import { getNodeLabelsAndRelTypes } from '../../../services/GetNodeLabelsRelTypes'; -import schemaExamples from '../../../assets/schemas.json'; +import { OptionType, OptionTypeForExamples, schema, UserCredentials } from '../../../../types'; +import { useAlertContext } from '../../../../context/Alert'; +import { getNodeLabelsAndRelTypes } from '../../../../services/GetNodeLabelsRelTypes'; +import schemaExamples from '../../../../assets/schemas.json'; +import { tokens } from '@neo4j-ndl/base'; export default function EntityExtractionSetting({ view, @@ -27,6 +28,7 @@ export default function EntityExtractionSetting({ onContinue?: () => void; colseEnhanceGraphSchemaDialog?: () => void; }) { + const { breakpoints } = tokens; const { setSelectedRels, setSelectedNodes, @@ -39,7 +41,7 @@ export default function EntityExtractionSetting({ } = useFileContext(); const { userCredentials } = useCredentials(); const [loading, setLoading] = useState(false); - + const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); const removeNodesAndRels = (nodelabels: string[], relationshipTypes: string[]) => { const labelsToRemoveSet = new Set(nodelabels); const relationshipLabelsToremoveSet = new Set(relationshipTypes); @@ -73,6 +75,10 @@ export default function EntityExtractionSetting({ removeNodesAndRels(removedNodelabels, removedRelations); } setSelectedSchemas(selectedOptions); + localStorage.setItem( + 'selectedSchemas', + JSON.stringify({ db: userCredentials?.uri, selectedOptions: selectedOptions }) + ); const nodesFromSchema = selectedOptions.map((s) => JSON.parse(s.value).nodelabels).flat(); const relationsFromSchema = selectedOptions.map((s) => JSON.parse(s.value).relationshipTypes).flat(); let nodeOptionsFromSchema: OptionType[] = []; @@ -155,7 +161,6 @@ export default function EntityExtractionSetting({ }, []); setdefaultExamples(parsedData); }, []); - useEffect(() => { if (userCredentials) { if (open && view === 'Dialog') { @@ -206,6 +211,7 @@ export default function EntityExtractionSetting({ setSelectedSchemas([]); setSelectedNodes(nodeLabelOptions); setSelectedRels(relationshipTypeOptions); + setIsSchema(true); localStorage.setItem('isSchema', JSON.stringify(true)); }, [nodeLabelOptions, relationshipTypeOptions]); @@ -220,11 +226,22 @@ export default function EntityExtractionSetting({ 'selectedRelationshipLabels', JSON.stringify({ db: userCredentials?.uri, selectedOptions: [] }) ); + localStorage.setItem('selectedSchemas', JSON.stringify({ db: userCredentials?.uri, selectedOptions: [] })); showAlert('info', `Successfully Removed the Schema settings`); if (view === 'Dialog' && onClose != undefined) { onClose(); } }; + + // Load selectedSchemas from local storage on mount + useEffect(() => { + const storedSchemas = localStorage.getItem('selectedSchemas'); + if (storedSchemas) { + const parsedSchemas = JSON.parse(storedSchemas); + setSelectedSchemas(parsedSchemas.selectedOptions); + } + }, []); + return (
@@ -244,7 +261,7 @@ export default function EntityExtractionSetting({ diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/index.tsx new file mode 100644 index 000000000..06e76ff8e --- /dev/null +++ b/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/index.tsx @@ -0,0 +1,49 @@ +import { Checkbox, Flex, Typography, useMediaQuery } from '@neo4j-ndl/react'; +import { POST_PROCESSING_JOBS } from '../../../../utils/Constants'; +import { capitalize } from '../../../../utils/Utils'; +import { useFileContext } from '../../../../context/UsersFiles'; +import { tokens } from '@neo4j-ndl/base'; + +export default function PostProcessingCheckList() { + const { breakpoints } = tokens; + const tablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); + const { postProcessingTasks, setPostProcessingTasks } = useFileContext(); + return ( + +
+ + + + These options allow you to fine-tune your knowledge graph for improved performance and deeper analysis + + + + {POST_PROCESSING_JOBS.map((job, idx) => ( + + + {job.title + .split('_') + .map((s) => capitalize(s)) + .join(' ')} + + } + checked={postProcessingTasks.includes(job.title)} + onChange={(e) => { + if (e.target.checked) { + setPostProcessingTasks((prev) => [...prev, job.title]); + } else { + setPostProcessingTasks((prev) => prev.filter((s) => s !== job.title)); + } + }} + > + {job.description} + + ))} + + +
+
+ ); +} diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx index 8e3b8adbc..5e2aaf713 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx @@ -1,19 +1,22 @@ -import { Dialog, Tabs, Box, Typography, Flex } from '@neo4j-ndl/react'; +import { Dialog, Tabs, Box, Typography, Flex, useMediaQuery } from '@neo4j-ndl/react'; import graphenhancement from '../../../assets/images/graph-enhancements.svg'; import { useEffect, useState } from 'react'; import DeletePopUpForOrphanNodes from './DeleteTabForOrphanNodes'; import deleteOrphanAPI from '../../../services/DeleteOrphanNodes'; import { UserCredentials } from '../../../types'; import { useCredentials } from '../../../context/UserCredentials'; -import EntityExtractionSettings from '../Settings/EntityExtractionSetting'; +import EntityExtractionSettings from './EnitityExtraction/EntityExtractionSetting'; import { AlertColor, AlertPropsColorOverrides } from '@mui/material'; import { OverridableStringUnion } from '@mui/types'; import { useFileContext } from '../../../context/UsersFiles'; +import DeduplicationTab from './Deduplication'; +import { tokens } from '@neo4j-ndl/base'; +import PostProcessingCheckList from './PostProcessingCheckList'; export default function GraphEnhancementDialog({ open, onClose, - closeSettingModal + closeSettingModal, }: { open: boolean; onClose: () => void; @@ -21,11 +24,13 @@ export default function GraphEnhancementDialog({ alertmsg: string, alerttype: OverridableStringUnion | undefined ) => void; - closeSettingModal:()=>void + closeSettingModal: () => void; }) { + const { breakpoints } = tokens; const [orphanDeleteAPIloading, setorphanDeleteAPIloading] = useState(false); const { setShowTextFromSchemaDialog } = useFileContext(); const { userCredentials } = useCredentials(); + const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); const orphanNodesDeleteHandler = async (selectedEntities: string[]) => { try { @@ -34,13 +39,14 @@ export default function GraphEnhancementDialog({ setorphanDeleteAPIloading(false); console.log(response); } catch (error) { + setorphanDeleteAPIloading(false); console.log(error); } }; useEffect(() => { - closeSettingModal() - }, []) - + closeSettingModal(); + }, []); + const [activeTab, setactiveTab] = useState(0); return ( - + - Graph Enhancements - - This set of tools will help you enhance the quality of your Knowledge Graph by removing possible + Graph Enhancements + + {isTablet + ? `This set of tools will help you enhance the quality of your Knowledge Graph` + : `This set of tools will help you enhance the quality of your Knowledge Graph by removing possible duplicated entities, disconnected nodes and set a Graph Schema for improving the quality of the entity - extraction process + extraction process`} - + Entity Extraction Settings Disconnected Nodes + + De-Duplication Of Nodes + + + Post Processing Jobs + @@ -79,7 +102,7 @@ export default function GraphEnhancementDialog({ - +
+ + + + + +
); diff --git a/frontend/src/components/Popups/LargeFilePopUp/ConfirmationDialog.tsx b/frontend/src/components/Popups/LargeFilePopUp/ConfirmationDialog.tsx index b1f59fe2f..50dbf6095 100644 --- a/frontend/src/components/Popups/LargeFilePopUp/ConfirmationDialog.tsx +++ b/frontend/src/components/Popups/LargeFilePopUp/ConfirmationDialog.tsx @@ -10,14 +10,16 @@ export default function ConfirmationDialog({ onClose, loading, extractHandler, + selectedRows, }: { largeFiles: CustomFile[]; open: boolean; onClose: () => void; loading: boolean; - extractHandler: (allowLargeFiles: boolean, selectedFilesFromAllfiles: CustomFile[]) => void; + extractHandler: (selectedFilesFromAllfiles: CustomFile[]) => void; + selectedRows: CustomFile[]; }) { - const { setSelectedRows, filesData, setRowSelection, selectedRows } = useFileContext(); + const { setSelectedRows, filesData, setRowSelection } = useFileContext(); const [checked, setChecked] = useState([...largeFiles.map((f) => f.id)]); const handleToggle = (ischecked: boolean, id: string) => { const newChecked = [...checked]; @@ -25,7 +27,7 @@ export default function ConfirmationDialog({ const file = filesData.find((f) => f.id === id); newChecked.push(id); setSelectedRows((prev) => { - const fileindex = prev.findIndex((f) => JSON.parse(f).id === id); + const fileindex = prev.findIndex((f) => f === id); if (fileindex == -1) { return [...prev, JSON.stringify(file)]; } @@ -53,7 +55,7 @@ export default function ConfirmationDialog({ return copiedobj; }); setSelectedRows((prev) => { - const filteredrows = prev.filter((f) => JSON.parse(f).id != id); + const filteredrows = prev.filter((f) => f != id); return filteredrows; }); } @@ -73,7 +75,6 @@ export default function ConfirmationDialog({ onClose={() => { setChecked([]); onClose(); - extractHandler(false, []); }} > @@ -87,7 +88,7 @@ export default function ConfirmationDialog({ diff --git a/frontend/src/components/UI/CustomCheckBox.tsx b/frontend/src/components/UI/CustomCheckBox.tsx new file mode 100644 index 000000000..f8e7f7780 --- /dev/null +++ b/frontend/src/components/UI/CustomCheckBox.tsx @@ -0,0 +1,20 @@ +import { Checkbox } from '@neo4j-ndl/react'; +import React, { HTMLProps } from 'react'; + +export default function IndeterminateCheckbox({ + indeterminate, + className = '', + ...rest +}: { indeterminate?: boolean } & HTMLProps) { + const ref = React.useRef(null!); + + React.useEffect(() => { + if (typeof indeterminate === 'boolean') { + ref.current.indeterminate = !rest.checked && indeterminate; + } + }, [ref, indeterminate]); + + return ( + + ); +} diff --git a/frontend/src/components/UI/FallBackDialog.tsx b/frontend/src/components/UI/FallBackDialog.tsx new file mode 100644 index 000000000..8bd608146 --- /dev/null +++ b/frontend/src/components/UI/FallBackDialog.tsx @@ -0,0 +1,11 @@ +import { Dialog, LoadingSpinner } from '@neo4j-ndl/react'; + +export default function FallBackDialog() { + return ( + + + ; + + + ); +} diff --git a/frontend/src/components/UI/Legend.tsx b/frontend/src/components/UI/Legend.tsx index 5c7110a06..a3c806669 100644 --- a/frontend/src/components/UI/Legend.tsx +++ b/frontend/src/components/UI/Legend.tsx @@ -1,16 +1,22 @@ +import { GraphLabel } from '@neo4j-ndl/react'; + export default function Legend({ bgColor, title, - chunkCount, + count, + type, + onClick, }: { bgColor: string; title: string; - chunkCount?: number; + count?: number; + type: 'node' | 'relationship' | 'propertyKey'; + tabIndex?: number; + onClick?: (e: React.MouseEvent) => void; }) { return ( -
- {title} - {chunkCount && `(${chunkCount})`} -
+ + {title} {count !== undefined && `(${count})`} + ); } diff --git a/frontend/src/components/UI/ShowAll.tsx b/frontend/src/components/UI/ShowAll.tsx new file mode 100644 index 000000000..726146723 --- /dev/null +++ b/frontend/src/components/UI/ShowAll.tsx @@ -0,0 +1,38 @@ +import { Button } from '@neo4j-ndl/react'; +import type { ReactNode } from 'react'; +import { useState } from 'react'; + +// import { ButtonGroup } from '../button-group/button-group'; + +type ShowAllProps = { + initiallyShown: number; + /* pass thunk to enable rendering only shown components */ + children: ((() => ReactNode) | ReactNode)[]; + ariaLabel?: string; +}; +const isThunkComponent = (t: (() => ReactNode) | ReactNode): t is () => ReactNode => typeof t === 'function'; + +export function ShowAll({ initiallyShown, children }: ShowAllProps) { + const [expanded, setExpanded] = useState(false); + const toggleExpanded = () => setExpanded((e) => !e); + const itemCount = children.length; + const controlsNeeded = itemCount > initiallyShown; + const shown = expanded ? itemCount : initiallyShown; + const leftToShow = itemCount - shown; + + if (itemCount === 0) { + return null; + } + + const currentChildren = children.slice(0, shown).map((c) => (isThunkComponent(c) ? c() : c)); + return ( + <> +
{currentChildren}
+ {controlsNeeded && ( + + )} + + ); +} diff --git a/frontend/src/components/WebSources/GenericSourceButton.tsx b/frontend/src/components/WebSources/GenericSourceButton.tsx index fcde2c15e..65c8b0046 100644 --- a/frontend/src/components/WebSources/GenericSourceButton.tsx +++ b/frontend/src/components/WebSources/GenericSourceButton.tsx @@ -1,25 +1,14 @@ -import CustomButton from '../UI/CustomButton'; -import internet from '../../assets/images/web-search-svgrepo-com.svg'; -import internetdarkmode from '../../assets/images/web-search-darkmode-final2.svg'; import { DataComponentProps } from '../../types'; import { Flex, Typography } from '@neo4j-ndl/react'; import IconButtonWithToolTip from '../UI/IconButtonToolTip'; import { InformationCircleIconOutline } from '@neo4j-ndl/react/icons'; import { APP_SOURCES } from '../../utils/Constants'; -import { useContext } from 'react'; -import { ThemeWrapperContext } from '../../context/ThemeWrapper'; +import WebButton from '../DataSources/Web/WebButton'; export default function GenericButton({ openModal }: DataComponentProps) { - const themeUtils = useContext(ThemeWrapperContext); - return ( - + Web Sources diff --git a/frontend/src/components/WebSources/GenericSourceModal.tsx b/frontend/src/components/WebSources/GenericSourceModal.tsx index 5bd218772..5a312e178 100644 --- a/frontend/src/components/WebSources/GenericSourceModal.tsx +++ b/frontend/src/components/WebSources/GenericSourceModal.tsx @@ -31,7 +31,13 @@ export default function GenericModal({ const [isLoading, setIsLoading] = useState(false); return ( - + { + setIsLoading(false); + closeHandler(); + }} + > @@ -67,17 +73,17 @@ export default function GenericModal({ {APP_SOURCES != undefined && APP_SOURCES.includes('youtube') && ( - + )} {APP_SOURCES != undefined && APP_SOURCES.includes('wiki') && ( - + )} {APP_SOURCES != undefined && APP_SOURCES.includes('web') && ( - + )} diff --git a/frontend/src/components/WebSources/Web/WebInput.tsx b/frontend/src/components/WebSources/Web/WebInput.tsx index 994949840..a4d6764bd 100644 --- a/frontend/src/components/WebSources/Web/WebInput.tsx +++ b/frontend/src/components/WebSources/Web/WebInput.tsx @@ -2,7 +2,13 @@ import { webLinkValidation } from '../../../utils/Utils'; import useSourceInput from '../../../hooks/useSourceInput'; import CustomSourceInput from '../CustomSourceInput'; -export default function WebInput({ setIsLoading }: { setIsLoading: React.Dispatch> }) { +export default function WebInput({ + setIsLoading, + loading, +}: { + setIsLoading: React.Dispatch>; + loading: boolean; +}) { const { inputVal, onChangeHandler, @@ -21,7 +27,7 @@ export default function WebInput({ setIsLoading }: { setIsLoading: React.Dispatc onCloseHandler={onClose} isFocused={isFocused} isValid={isValid} - disabledCheck={false} + disabledCheck={Boolean(loading)} label='Website Link' placeHolder='https://neo4j.com/' value={inputVal} diff --git a/frontend/src/components/WebSources/WikiPedia/WikipediaInput.tsx b/frontend/src/components/WebSources/WikiPedia/WikipediaInput.tsx index cf90d8349..34a1e22e3 100644 --- a/frontend/src/components/WebSources/WikiPedia/WikipediaInput.tsx +++ b/frontend/src/components/WebSources/WikiPedia/WikipediaInput.tsx @@ -3,9 +3,11 @@ import useSourceInput from '../../../hooks/useSourceInput'; import CustomSourceInput from '../CustomSourceInput'; export default function WikipediaInput({ + loading, setIsLoading, }: { setIsLoading: React.Dispatch>; + loading: boolean; }) { const { inputVal, @@ -25,7 +27,7 @@ export default function WikipediaInput({ onCloseHandler={onClose} isFocused={isFocused} isValid={isValid} - disabledCheck={false} + disabledCheck={Boolean(loading)} label='Wikipedia Link' placeHolder='https://en.wikipedia.org/wiki/Albert_Einstein' value={inputVal} diff --git a/frontend/src/components/WebSources/Youtube/YoutubeInput.tsx b/frontend/src/components/WebSources/Youtube/YoutubeInput.tsx index 8652d7f92..bb8479013 100644 --- a/frontend/src/components/WebSources/Youtube/YoutubeInput.tsx +++ b/frontend/src/components/WebSources/Youtube/YoutubeInput.tsx @@ -3,8 +3,10 @@ import useSourceInput from '../../../hooks/useSourceInput'; import { youtubeLinkValidation } from '../../../utils/Utils'; export default function YoutubeInput({ + loading, setIsLoading, }: { + loading: boolean; setIsLoading: React.Dispatch>; }) { const { @@ -25,7 +27,7 @@ export default function YoutubeInput({ onCloseHandler={onClose} isFocused={isFocused} isValid={isValid} - disabledCheck={false} + disabledCheck={Boolean(loading)} label='Youtube Link' placeHolder='https://www.youtube.com/watch?v=2W9HM1xBibo' value={inputVal} diff --git a/frontend/src/context/UserCredentials.tsx b/frontend/src/context/UserCredentials.tsx index 1559b0459..20ed75ae5 100644 --- a/frontend/src/context/UserCredentials.tsx +++ b/frontend/src/context/UserCredentials.tsx @@ -1,14 +1,10 @@ import { createContext, useState, useContext, FunctionComponent, ReactNode } from 'react'; -import { UserCredentials } from '../types'; +import { ContextProps, UserCredentials } from '../types'; type Props = { children: ReactNode; }; -interface ContextProps { - userCredentials: UserCredentials | null; - setUserCredentials: (UserCredentials: UserCredentials) => void; -} export const UserConnection = createContext({ userCredentials: null, setUserCredentials: () => null, diff --git a/frontend/src/context/UserMessages.tsx b/frontend/src/context/UserMessages.tsx index 700eed843..4f74a7642 100644 --- a/frontend/src/context/UserMessages.tsx +++ b/frontend/src/context/UserMessages.tsx @@ -1,13 +1,8 @@ -import { createContext, useState, useContext, Dispatch, SetStateAction, FC } from 'react'; -import { MessagesContextProviderProps, Messages } from '../types'; +import { createContext, useState, useContext, FC } from 'react'; +import { MessagesContextProviderProps, Messages, MessageContextType } from '../types'; import chatbotmessages from '../assets/ChatbotMessages.json'; import { getDateTime } from '../utils/Utils'; -interface MessageContextType { - messages: Messages[] | []; - setMessages: Dispatch>; -} - const MessageContext = createContext(undefined); const MessageContextWrapper: FC = ({ children }) => { diff --git a/frontend/src/context/UsersFiles.tsx b/frontend/src/context/UsersFiles.tsx index 26daf6ddd..fd3531403 100644 --- a/frontend/src/context/UsersFiles.tsx +++ b/frontend/src/context/UsersFiles.tsx @@ -2,6 +2,7 @@ import { createContext, useContext, useState, Dispatch, SetStateAction, FC, useE import { CustomFile, FileContextProviderProps, OptionType } from '../types'; import { defaultLLM } from '../utils/Constants'; import { useCredentials } from './UserCredentials'; +import Queue from '../utils/Queue'; interface showTextFromSchemaDialogType { triggeredFrom: string; show: boolean; @@ -31,15 +32,25 @@ interface FileContextType { setIsSchema: React.Dispatch>; showTextFromSchemaDialog: showTextFromSchemaDialogType; setShowTextFromSchemaDialog: React.Dispatch>; + postProcessingTasks: string[]; + setPostProcessingTasks: React.Dispatch>; + queue: Queue; + setQueue: Dispatch>; + processedCount: number; + setProcessedCount: Dispatch>; } const FileContext = createContext(undefined); const FileContextProvider: FC = ({ children }) => { const selectedNodeLabelstr = localStorage.getItem('selectedNodeLabels'); const selectedNodeRelsstr = localStorage.getItem('selectedRelationshipLabels'); - + const persistedQueue = localStorage.getItem('waitingQueue'); + const { userCredentials } = useCredentials(); const [files, setFiles] = useState<(File | null)[] | []>([]); const [filesData, setFilesData] = useState([]); + const [queue, setQueue] = useState( + new Queue(JSON.parse(persistedQueue ?? JSON.stringify({ queue: [] })).queue) + ); const [model, setModel] = useState(defaultLLM); const [graphType, setGraphType] = useState('Knowledge Graph Entities'); const [selectedNodes, setSelectedNodes] = useState([]); @@ -47,14 +58,18 @@ const FileContextProvider: FC = ({ children }) => { const [selectedSchemas, setSelectedSchemas] = useState([]); const [rowSelection, setRowSelection] = useState>({}); const [selectedRows, setSelectedRows] = useState([]); - const [chatMode, setchatMode] = useState('graph+vector'); - const { userCredentials } = useCredentials(); + const [chatMode, setchatMode] = useState('graph+vector+fulltext'); const [isSchema, setIsSchema] = useState(false); const [showTextFromSchemaDialog, setShowTextFromSchemaDialog] = useState({ triggeredFrom: '', show: false, }); - + const [postProcessingTasks, setPostProcessingTasks] = useState([ + 'materialize_text_chunk_similarities', + 'enable_hybrid_search_and_fulltext_search_in_bloom', + 'materialize_entity_similarities', + ]); + const [processedCount, setProcessedCount] = useState(0); useEffect(() => { if (selectedNodeLabelstr != null) { const selectedNodeLabel = JSON.parse(selectedNodeLabelstr); @@ -95,6 +110,12 @@ const FileContextProvider: FC = ({ children }) => { setIsSchema, setShowTextFromSchemaDialog, showTextFromSchemaDialog, + postProcessingTasks, + setPostProcessingTasks, + queue, + setQueue, + processedCount, + setProcessedCount, }; return {children}; }; diff --git a/frontend/src/hooks/useSourceInput.tsx b/frontend/src/hooks/useSourceInput.tsx index 63a8deb5c..602acb947 100644 --- a/frontend/src/hooks/useSourceInput.tsx +++ b/frontend/src/hooks/useSourceInput.tsx @@ -141,6 +141,7 @@ export default function useSourceInput( setIsValid(false); setIsFocused(false); } catch (error) { + setIsLoading(false); setStatus('danger'); setStatusMessage('Some Error Occurred or Please Check your Instance Connection'); } diff --git a/frontend/src/hooks/useSse.tsx b/frontend/src/hooks/useSse.tsx index a1f54633e..36be24c7b 100644 --- a/frontend/src/hooks/useSse.tsx +++ b/frontend/src/hooks/useSse.tsx @@ -1,12 +1,13 @@ import { useFileContext } from '../context/UsersFiles'; import { eventResponsetypes } from '../types'; +import { batchSize } from '../utils/Constants'; import { calculateProcessingTime } from '../utils/Utils'; export default function useServerSideEvent( alertHandler: (inMinutes: boolean, minutes: number, filename: string) => void, errorHandler: (filename: string) => void ) { - const { setFilesData } = useFileContext(); + const { setFilesData, setProcessedCount, queue } = useFileContext(); function updateStatusForLargeFiles(eventSourceRes: eventResponsetypes) { const { fileName, @@ -44,7 +45,7 @@ export default function useServerSideEvent( }); }); } - } else if (status === 'Completed' || status === 'Cancelled') { + } else if (status === 'Completed') { setFilesData((prevfiles) => { return prevfiles.map((curfile) => { if (curfile.name == fileName) { @@ -60,6 +61,13 @@ export default function useServerSideEvent( return curfile; }); }); + setProcessedCount((prev) => { + if (prev == batchSize) { + return batchSize - 1; + } + return prev + 1; + }); + queue.remove(fileName); } else if (eventSourceRes.status === 'Failed') { setFilesData((prevfiles) => { return prevfiles.map((curfile) => { diff --git a/frontend/src/services/CancelAPI.ts b/frontend/src/services/CancelAPI.ts index a162bed83..de3ea9ba0 100644 --- a/frontend/src/services/CancelAPI.ts +++ b/frontend/src/services/CancelAPI.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { UserCredentials, commonserverresponse } from '../types'; +import api from '../API/Index'; const cancelAPI = async (filenames: string[], source_types: string[]) => { try { @@ -14,7 +13,7 @@ const cancelAPI = async (filenames: string[], source_types: string[]) => { } formData.append('filenames', JSON.stringify(filenames)); formData.append('source_types', JSON.stringify(source_types)); - const response = await axios.post(`${url()}/cancelled_job`, formData); + const response = await api.post(`/cancelled_job`, formData); return response; } catch (error) { console.log('Error Posting the Question:', error); diff --git a/frontend/src/services/ChunkEntitiesInfo.ts b/frontend/src/services/ChunkEntitiesInfo.ts index 3b4323197..aa133c815 100644 --- a/frontend/src/services/ChunkEntitiesInfo.ts +++ b/frontend/src/services/ChunkEntitiesInfo.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { ChatInfo_APIResponse, UserCredentials } from '../types'; +import api from '../API/Index'; const chunkEntitiesAPI = async (userCredentials: UserCredentials, chunk_ids: string) => { try { @@ -10,7 +9,7 @@ const chunkEntitiesAPI = async (userCredentials: UserCredentials, chunk_ids: str formData.append('password', userCredentials?.password ?? ''); formData.append('chunk_ids', chunk_ids); - const response: ChatInfo_APIResponse = await axios.post(`${url()}/chunk_entities`, formData, { + const response: ChatInfo_APIResponse = await api.post(`/chunk_entities`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/frontend/src/services/CommonAPI.ts b/frontend/src/services/CommonAPI.ts index 78d324248..bbae83032 100644 --- a/frontend/src/services/CommonAPI.ts +++ b/frontend/src/services/CommonAPI.ts @@ -1,5 +1,6 @@ -import axios, { AxiosResponse, Method } from 'axios'; +import { AxiosResponse, Method } from 'axios'; import { UserCredentials, FormDataParams } from '../types'; +import api from '../API/Index'; // API Call const apiCall = async ( @@ -16,7 +17,7 @@ const apiCall = async ( for (const key in additionalParams) { formData.append(key, additionalParams[key]); } - const response: AxiosResponse = await axios({ + const response: AxiosResponse = await api({ method: method, url: url, data: formData, diff --git a/frontend/src/services/ConnectAPI.ts b/frontend/src/services/ConnectAPI.ts index 73d997b1e..026c41c44 100644 --- a/frontend/src/services/ConnectAPI.ts +++ b/frontend/src/services/ConnectAPI.ts @@ -1,5 +1,4 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; +import api from '../API/Index'; const connectAPI = async (connectionURI: string, username: string, password: string, database: string) => { try { @@ -8,7 +7,7 @@ const connectAPI = async (connectionURI: string, username: string, password: str formData.append('database', database ?? ''); formData.append('userName', username ?? ''); formData.append('password', password ?? ''); - const response = await axios.post(`${url()}/connect`, formData, { + const response = await api.post(`/connect`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/frontend/src/services/DeleteFiles.ts b/frontend/src/services/DeleteFiles.ts index 36ae28cbd..a86ef187e 100644 --- a/frontend/src/services/DeleteFiles.ts +++ b/frontend/src/services/DeleteFiles.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { CustomFile, UserCredentials } from '../types'; +import api from '../API/Index'; const deleteAPI = async (userCredentials: UserCredentials, selectedFiles: CustomFile[], deleteEntities: boolean) => { try { @@ -14,7 +13,7 @@ const deleteAPI = async (userCredentials: UserCredentials, selectedFiles: Custom formData.append('deleteEntities', JSON.stringify(deleteEntities)); formData.append('filenames', JSON.stringify(filenames)); formData.append('source_types', JSON.stringify(source_types)); - const response = await axios.post(`${url()}/delete_document_and_entities`, formData); + const response = await api.post(`/delete_document_and_entities`, formData); return response; } catch (error) { console.log('Error Posting the Question:', error); diff --git a/frontend/src/services/DeleteOrphanNodes.ts b/frontend/src/services/DeleteOrphanNodes.ts index 135dfcdeb..2cebc572c 100644 --- a/frontend/src/services/DeleteOrphanNodes.ts +++ b/frontend/src/services/DeleteOrphanNodes.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { UserCredentials } from '../types'; +import api from '../API/Index'; const deleteOrphanAPI = async (userCredentials: UserCredentials, selectedNodes: string[]) => { try { @@ -10,7 +9,7 @@ const deleteOrphanAPI = async (userCredentials: UserCredentials, selectedNodes: formData.append('userName', userCredentials?.userName ?? ''); formData.append('password', userCredentials?.password ?? ''); formData.append('unconnected_entities_list', JSON.stringify(selectedNodes)); - const response = await axios.post(`${url()}/delete_unconnected_nodes`, formData); + const response = await api.post(`/delete_unconnected_nodes`, formData); return response; } catch (error) { console.log('Error Posting the Question:', error); diff --git a/frontend/src/services/GetDuplicateNodes.ts b/frontend/src/services/GetDuplicateNodes.ts new file mode 100644 index 000000000..b7ea0c426 --- /dev/null +++ b/frontend/src/services/GetDuplicateNodes.ts @@ -0,0 +1,17 @@ +import { duplicateNodesData, UserCredentials } from '../types'; +import api from '../API/Index'; + +export const getDuplicateNodes = async (userCredentials: UserCredentials) => { + const formData = new FormData(); + formData.append('uri', userCredentials?.uri ?? ''); + formData.append('database', userCredentials?.database ?? ''); + formData.append('userName', userCredentials?.userName ?? ''); + formData.append('password', userCredentials?.password ?? ''); + try { + const response = await api.post(`/get_duplicate_nodes`, formData); + return response; + } catch (error) { + console.log(error); + throw error; + } +}; diff --git a/frontend/src/services/GetFiles.ts b/frontend/src/services/GetFiles.ts index fd8d60fba..056a9cc05 100644 --- a/frontend/src/services/GetFiles.ts +++ b/frontend/src/services/GetFiles.ts @@ -1,14 +1,11 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { SourceListServerData, UserCredentials } from '../types'; +import api from '../API/Index'; export const getSourceNodes = async (userCredentials: UserCredentials) => { try { const encodedstr = btoa(userCredentials.password); - const response = await axios.get( - `${url()}/sources_list?uri=${userCredentials.uri}&database=${userCredentials.database}&userName=${ - userCredentials.userName - }&password=${encodedstr}` + const response = await api.get( + `/sources_list?uri=${userCredentials.uri}&database=${userCredentials.database}&userName=${userCredentials.userName}&password=${encodedstr}` ); return response; } catch (error) { diff --git a/frontend/src/services/GetNodeLabelsRelTypes.ts b/frontend/src/services/GetNodeLabelsRelTypes.ts index acc05f267..8c7345c2a 100644 --- a/frontend/src/services/GetNodeLabelsRelTypes.ts +++ b/frontend/src/services/GetNodeLabelsRelTypes.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { ServerData, UserCredentials } from '../types'; +import api from '../API/Index'; export const getNodeLabelsAndRelTypes = async (userCredentials: UserCredentials) => { const formData = new FormData(); @@ -9,7 +8,7 @@ export const getNodeLabelsAndRelTypes = async (userCredentials: UserCredentials) formData.append('userName', userCredentials?.userName ?? ''); formData.append('password', userCredentials?.password ?? ''); try { - const response = await axios.post(`${url()}/schema`, formData); + const response = await api.post(`/schema`, formData); return response; } catch (error) { console.log(error); diff --git a/frontend/src/services/GetOrphanNodes.ts b/frontend/src/services/GetOrphanNodes.ts index 70cfe8cda..3578214d1 100644 --- a/frontend/src/services/GetOrphanNodes.ts +++ b/frontend/src/services/GetOrphanNodes.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { OrphanNodeResponse, UserCredentials } from '../types'; +import api from '../API/Index'; export const getOrphanNodes = async (userCredentials: UserCredentials) => { const formData = new FormData(); @@ -9,7 +8,7 @@ export const getOrphanNodes = async (userCredentials: UserCredentials) => { formData.append('userName', userCredentials?.userName ?? ''); formData.append('password', userCredentials?.password ?? ''); try { - const response = await axios.post(`${url()}/get_unconnected_nodes_list`, formData); + const response = await api.post(`/get_unconnected_nodes_list`, formData); return response; } catch (error) { console.log(error); diff --git a/frontend/src/services/GraphQuery.ts b/frontend/src/services/GraphQuery.ts index 55d22a5ee..f792f4aec 100644 --- a/frontend/src/services/GraphQuery.ts +++ b/frontend/src/services/GraphQuery.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { UserCredentials } from '../types'; +import api from '../API/Index'; const graphQueryAPI = async ( userCredentials: UserCredentials, @@ -16,7 +15,7 @@ const graphQueryAPI = async ( formData.append('query_type', query_type ?? 'entities'); formData.append('document_names', JSON.stringify(document_names)); - const response = await axios.post(`${url()}/graph_query`, formData, { + const response = await api.post(`/graph_query`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/frontend/src/services/HealthStatus.ts b/frontend/src/services/HealthStatus.ts index 69a77f0fe..a59badbe0 100644 --- a/frontend/src/services/HealthStatus.ts +++ b/frontend/src/services/HealthStatus.ts @@ -1,9 +1,8 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; +import api from '../API/Index'; const healthStatus = async () => { try { - const healthUrl = `${url()}/health`; - const response = await axios.get(healthUrl); + const healthUrl = `/health`; + const response = await api.get(healthUrl); return response; } catch (error) { console.log('API status error', error); diff --git a/frontend/src/services/MergeDuplicateEntities.ts b/frontend/src/services/MergeDuplicateEntities.ts new file mode 100644 index 000000000..1fdb9b387 --- /dev/null +++ b/frontend/src/services/MergeDuplicateEntities.ts @@ -0,0 +1,19 @@ +import { commonserverresponse, selectedDuplicateNodes, UserCredentials } from '../types'; +import api from '../API/Index'; + +const mergeDuplicateNodes = async (userCredentials: UserCredentials, selectedNodes: selectedDuplicateNodes[]) => { + try { + const formData = new FormData(); + formData.append('uri', userCredentials?.uri ?? ''); + formData.append('database', userCredentials?.database ?? ''); + formData.append('userName', userCredentials?.userName ?? ''); + formData.append('password', userCredentials?.password ?? ''); + formData.append('duplicate_nodes_list', JSON.stringify(selectedNodes)); + const response = await api.post(`/merge_duplicate_nodes`, formData); + return response; + } catch (error) { + console.log('Error Merging the duplicate nodes:', error); + throw error; + } +}; +export default mergeDuplicateNodes; diff --git a/frontend/src/services/PollingAPI.ts b/frontend/src/services/PollingAPI.ts index 08e6a11bb..f14ed0580 100644 --- a/frontend/src/services/PollingAPI.ts +++ b/frontend/src/services/PollingAPI.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { PollingAPI_Response, statusupdate } from '../types'; +import api from '../API/Index'; export default async function subscribe( fileName: string, @@ -15,12 +14,12 @@ export default async function subscribe( const MAX_POLLING_ATTEMPTS = 10; let pollingAttempts = 0; - let delay = 2000; + let delay = 1000; while (pollingAttempts < MAX_POLLING_ATTEMPTS) { let currentdelay = delay; - let response: PollingAPI_Response = await axios.get( - `${url()}/document_status/${fileName}?url=${uri}&userName=${username}&password=${encodedstr}&database=${database}` + let response: PollingAPI_Response = await api.get( + `/document_status/${fileName}?url=${uri}&userName=${username}&password=${encodedstr}&database=${database}` ); if (response.data?.file_name?.status === 'Processing') { diff --git a/frontend/src/services/PostProcessing.ts b/frontend/src/services/PostProcessing.ts index 9f45cc6bd..98c94c238 100644 --- a/frontend/src/services/PostProcessing.ts +++ b/frontend/src/services/PostProcessing.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { UserCredentials } from '../types'; +import api from '../API/Index'; const postProcessing = async (userCredentials: UserCredentials, taskParam: string[]) => { try { @@ -10,7 +9,7 @@ const postProcessing = async (userCredentials: UserCredentials, taskParam: strin formData.append('userName', userCredentials?.userName ?? ''); formData.append('password', userCredentials?.password ?? ''); formData.append('tasks', JSON.stringify(taskParam)); - const response = await axios.post(`${url()}/post_processing`, formData, { + const response = await api.post(`/post_processing`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/frontend/src/services/QnaAPI.ts b/frontend/src/services/QnaAPI.ts index 931618839..78fa240ba 100644 --- a/frontend/src/services/QnaAPI.ts +++ b/frontend/src/services/QnaAPI.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { UserCredentials } from '../types'; +import api from '../API/Index'; export const chatBotAPI = async ( userCredentials: UserCredentials, @@ -22,7 +21,7 @@ export const chatBotAPI = async ( formData.append('mode', mode); formData.append('document_names', JSON.stringify(document_names)); const startTime = Date.now(); - const response = await axios.post(`${url()}/chat_bot`, formData, { + const response = await api.post(`/chat_bot`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, @@ -44,7 +43,7 @@ export const clearChatAPI = async (userCredentials: UserCredentials, session_id: formData.append('userName', userCredentials?.userName ?? ''); formData.append('password', userCredentials?.password ?? ''); formData.append('session_id', session_id); - const response = await axios.post(`${url()}/clear_chat_bot`, formData, { + const response = await api.post(`/clear_chat_bot`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/frontend/src/services/SchemaFromTextAPI.ts b/frontend/src/services/SchemaFromTextAPI.ts index 785fb0d68..3d1984ccf 100644 --- a/frontend/src/services/SchemaFromTextAPI.ts +++ b/frontend/src/services/SchemaFromTextAPI.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { ScehmaFromText } from '../types'; +import api from '../API/Index'; export const getNodeLabelsAndRelTypesFromText = async (model: string, inputText: string, isSchemaText: boolean) => { const formData = new FormData(); @@ -9,7 +8,7 @@ export const getNodeLabelsAndRelTypesFromText = async (model: string, inputText: formData.append('is_schema_description_checked', JSON.stringify(isSchemaText)); try { - const response = await axios.post(`${url()}/populate_graph_schema`, formData); + const response = await api.post(`/populate_graph_schema`, formData); return response; } catch (error) { console.log(error); diff --git a/frontend/src/services/URLScan.ts b/frontend/src/services/URLScan.ts index 58ad37dfb..444022934 100644 --- a/frontend/src/services/URLScan.ts +++ b/frontend/src/services/URLScan.ts @@ -1,6 +1,5 @@ -import axios from 'axios'; -import { url } from '../utils/Utils'; import { ScanProps, ServerResponse } from '../types'; +import api from '../API/Index'; const urlScanAPI = async (props: ScanProps) => { try { @@ -46,7 +45,7 @@ const urlScanAPI = async (props: ScanProps) => { formData.append('access_token', props.access_token); } - const response: ServerResponse = await axios.post(`${url()}/url/scan`, formData, { + const response: ServerResponse = await api.post(`/url/scan`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/frontend/src/services/vectorIndexCreation.ts b/frontend/src/services/vectorIndexCreation.ts new file mode 100644 index 000000000..e89767551 --- /dev/null +++ b/frontend/src/services/vectorIndexCreation.ts @@ -0,0 +1,18 @@ +import { commonserverresponse, UserCredentials } from '../types'; +import api from '../API/Index'; + +export const createVectorIndex = async (userCredentials: UserCredentials, isVectorIndexExists: boolean) => { + const formData = new FormData(); + formData.append('uri', userCredentials?.uri ?? ''); + formData.append('database', userCredentials?.database ?? ''); + formData.append('userName', userCredentials?.userName ?? ''); + formData.append('password', userCredentials?.password ?? ''); + formData.append('isVectorIndexExist', JSON.stringify(isVectorIndexExists)); + try { + const response = await api.post(`/drop_create_vector_index`, formData); + return response; + } catch (error) { + console.log(error); + throw error; + } +}; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index dc0cadf1a..5bf076d10 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -89,6 +89,7 @@ export interface CustomAlertProps { } export interface DataComponentProps { openModal: () => void; + isLargeDesktop?: boolean; } export interface S3ModalProps { @@ -100,11 +101,6 @@ export interface GCSModalProps { open: boolean; openGCSModal: () => void; } -export interface ConnectionModalProps { - open: boolean; - setOpenConnection: Dispatch>; - setConnectionStatus: Dispatch>; -} export interface SourceNode { fileName: string; @@ -140,10 +136,20 @@ export interface SideNavProps { setIsRightExpanded?: Dispatch>; messages?: Messages[]; clearHistoryData?: boolean; + toggles3Modal: () => void; + toggleGCSModal: () => void; + toggleGenericModal: () => void; + setIsleftExpanded?: Dispatch>; } export interface DrawerProps { isExpanded: boolean; + shows3Modal: boolean; + showGCSModal: boolean; + showGenericModal: boolean; + toggleS3Modal: () => void; + toggleGCSModal: () => void; + toggleGenericModal: () => void; } export interface ContentProps { @@ -155,8 +161,8 @@ export interface ContentProps { isSchema?: boolean; setIsSchema: Dispatch>; showEnhancementDialog: boolean; - setshowEnhancementDialog: Dispatch>; - closeSettingModal:()=>void + toggleEnhancementDialog: () => void; + closeSettingModal: () => void; } export interface FileTableProps { @@ -164,6 +170,7 @@ export interface FileTableProps { connectionStatus: boolean; setConnectionStatus: Dispatch>; onInspect: (id: string) => void; + handleGenerateGraph: () => void; } export interface CustomModalProps { @@ -231,6 +238,7 @@ export interface Messages { mode?: string; cypher_query?: string; graphonly_entities?: []; + error?: string; } export type ChatbotProps = { @@ -250,12 +258,12 @@ export interface GraphViewModalProps { inspectedName?: string; setGraphViewOpen: Dispatch>; viewPoint: string; - nodeValues?: Node[]; - relationshipValues?: Relationship[]; + nodeValues?: ExtendedNode[]; + relationshipValues?: ExtendedRelationship[]; selectedRows?: CustomFile[] | undefined; } -export type GraphType = 'Document' | 'Entities' | 'Chunk'; +export type GraphType = 'Entities' | 'DocumentChunk'; export type PartialLabelNode = Partial & { labels: string; @@ -336,13 +344,13 @@ export type alertStateType = { export type Scheme = Record; export type LabelCount = Record; -interface NodeType extends Partial { - labels?: string[]; -} + export interface LegendChipProps { scheme: Scheme; - title: string; - nodes: NodeType[]; + label: string; + type: 'node' | 'relationship' | 'propertyKey'; + count: number; + onClick: (e: React.MouseEvent) => void; } export interface FileContextProviderProps { children: ReactNode; @@ -352,13 +360,14 @@ export interface orphanNode { elementId: string; description: string; labels: string[]; - embedding: null | string; + embedding?: null | string; } export interface orphanNodeProps { documents: string[]; chunkConnections: number; e: orphanNode; checked?: boolean; + similar?: orphanNode[]; } export interface labelsAndTypes { labels: string[]; @@ -372,15 +381,34 @@ export interface commonserverresponse { error?: string; message?: string | orphanTotalNodes; file_name?: string; - data?: labelsAndTypes | labelsAndTypes[] | uploadData | orphanNodeProps[]; + data?: labelsAndTypes | labelsAndTypes[] | uploadData | orphanNodeProps[] | dupNodes[]; +} +export interface dupNodeProps { + id: string; + elementId: string; + labels: string[]; + embedding?: null | string; +} +export interface dupNodes { + e: dupNodeProps; + similar: dupNodeProps[]; + documents: string[]; + chunkConnections: number; +} +export interface selectedDuplicateNodes { + firstElementId: string; + similarElementIds: string[]; } - export interface ScehmaFromText extends Partial { data: labelsAndTypes; } + export interface ServerData extends Partial { data: labelsAndTypes[]; } +export interface duplicateNodesData extends Partial { + data: dupNodes[]; +} export interface OrphanNodeResponse extends Partial { data: orphanNodeProps[]; } @@ -405,6 +433,7 @@ export interface chatInfoMessage extends Partial { mode: string; cypher_query?: string; graphonly_entities: []; + error: string; } export interface eventResponsetypes { @@ -438,7 +467,7 @@ export interface CHATINFO_RESPONSE { status: string; message: string; error?: string; - node: Node[]; + node: ExtendedNode[]; relationships: Relationship[]; data?: any; } @@ -549,25 +578,73 @@ export type GraphStatsLabels = Record< } >; -type NodeStyling = { - backgroundColor: string; - borderColor: string; - textColor: string; - caption: string; - diameter: string; -}; +export interface ExtendedNode extends Node { + labels: string[]; + properties: { + fileName?: string; + [key: string]: any; + }; +} -type RelationStyling = { - fontSize: string; - lineColor: string; - textColorExternal: string; - textColorInternal: string; - caption: string; - padding: string; - width: string; -}; +export interface ExtendedRelationship extends Relationship { + count: number; +} +export interface connectionState { + openPopUp: boolean; + chunksExists: boolean; + vectorIndexMisMatch: boolean; + chunksExistsWithDifferentDimension: boolean; +} +export interface Message { + type: 'success' | 'info' | 'warning' | 'danger' | 'unknown'; + content: string | React.ReactNode; +} -export type GraphStyling = { - node: Record>; - relationship: Record>; -}; +export interface ConnectionModalProps { + open: boolean; + setOpenConnection: Dispatch>; + setConnectionStatus: Dispatch>; + isVectorIndexMatch: boolean; + chunksExistsWithoutEmbedding: boolean; + chunksExistsWithDifferentEmbedding: boolean; +} +export interface ReusableDropdownProps extends DropdownProps { + options: string[] | OptionType[]; + placeholder?: string; + defaultValue?: string; + children?: React.ReactNode; + view?: 'ContentView' | 'GraphView'; + isDisabled: boolean; + value?: OptionType; +} +export interface ChildRef { + getSelectedRows: () => CustomFile[]; +} +export interface IconProps { + closeChatBot: () => void; + deleteOnClick?: () => void; + messages: Messages[]; +} +export interface S3File { + fileName: string; + fileSize: number; + url: string; +} +export interface GraphViewButtonProps { + nodeValues?: ExtendedNode[]; + relationshipValues?: ExtendedRelationship[]; +} +export interface DrawerChatbotProps { + isExpanded: boolean; + clearHistoryData: boolean; + messages: Messages[]; +} + +export interface ContextProps { + userCredentials: UserCredentials | null; + setUserCredentials: (UserCredentials: UserCredentials) => void; +} +export interface MessageContextType { + messages: Messages[] | []; + setMessages: Dispatch>; +} diff --git a/frontend/src/utils/Constants.ts b/frontend/src/utils/Constants.ts index 1006b041f..47f1e119f 100644 --- a/frontend/src/utils/Constants.ts +++ b/frontend/src/utils/Constants.ts @@ -29,16 +29,17 @@ export const docChunkEntities = `+[chunks] //chunks with entities + collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }`; export const APP_SOURCES = - process.env.REACT_APP_SOURCES !== '' - ? process.env.REACT_APP_SOURCES?.split(',') + process.env.VITE_REACT_APP_SOURCES !== '' + ? (process.env.VITE_REACT_APP_SOURCES?.split(',') as string[]) : ['gcs', 's3', 'local', 'wiki', 'youtube', 'web']; export const llms = - process.env?.LLM_MODELS?.trim() != '' - ? process.env.LLM_MODELS?.split(',') + process.env?.VITE_LLM_MODELS?.trim() != '' + ? (process.env.VITE_LLM_MODELS?.split(',') as string[]) : [ 'diffbot', 'openai-gpt-3.5', 'openai-gpt-4o', + 'openai-gpt-4o-mini', 'gemini-1.0-pro', 'gemini-1.5-pro', 'azure_ai_gpt_35', @@ -46,21 +47,25 @@ export const llms = 'ollama_llama3', 'groq_llama3_70b', 'anthropic_claude_3_5_sonnet', - 'fireworks_llama_v3_70b', + 'fireworks_v3p1_405b', 'bedrock_claude_3_5_sonnet', ]; -export const defaultLLM = llms?.includes('openai-gpt-3.5') - ? 'openai-gpt-3.5' +export const defaultLLM = llms?.includes('openai-gpt-4o') + ? 'openai-gpt-4o' : llms?.includes('gemini-1.0-pro') ? 'gemini-1.0-pro' : 'diffbot'; export const chatModes = - process.env?.CHAT_MODES?.trim() != '' ? process.env.CHAT_MODES?.split(',') : ['vector', 'graph', 'graph+vector']; -export const chunkSize = process.env.CHUNK_SIZE ? parseInt(process.env.CHUNK_SIZE) : 1 * 1024 * 1024; -export const timeperpage = process.env.TIME_PER_PAGE ? parseInt(process.env.TIME_PER_PAGE) : 50; + process.env?.VITE_CHAT_MODES?.trim() != '' + ? process.env.VITE_CHAT_MODES?.split(',') + : ['vector', 'graph', 'graph+vector', 'fulltext', 'graph+vector+fulltext']; +export const chunkSize = process.env.VITE_CHUNK_SIZE ? parseInt(process.env.VITE_CHUNK_SIZE) : 1 * 1024 * 1024; +export const timeperpage = process.env.VITE_TIME_PER_PAGE ? parseInt(process.env.VITE_TIME_PER_PAGE) : 50; export const timePerByte = 0.2; -export const largeFileSize = process.env.LARGE_FILE_SIZE ? parseInt(process.env.LARGE_FILE_SIZE) : 5 * 1024 * 1024; +export const largeFileSize = process.env.VITE_LARGE_FILE_SIZE + ? parseInt(process.env.VITE_LARGE_FILE_SIZE) + : 5 * 1024 * 1024; export const NODES_OPTIONS = [ { label: 'Person', @@ -131,7 +136,7 @@ export const tooltips = { useExistingSchema: 'Use the already existing schema from DB', clearChat: 'Clear Chat History', continue: 'Continue', - clearGraphSettings: 'Allow User to remove Settings', + clearGraphSettings: 'Clear configured Graph Schema', }; export const buttonCaptions = { @@ -153,11 +158,35 @@ export const buttonCaptions = { cancel: 'Cancel', details: 'Details', continueSettings: 'Continue', - clearSettings: 'Clear Settings', + clearSettings: 'Clear Schema', ask: 'Ask', }; -export const taskParam: string[] = ['update_similarity_graph', 'create_fulltext_index', 'create_entity_embedding']; +export const POST_PROCESSING_JOBS: { title: string; description: string }[] = [ + { + title: 'materialize_text_chunk_similarities', + description: `This option refines the connections between different pieces of information (chunks) within your + knowledge graph. By leveraging a k-nearest neighbor algorithm with a similarity threshold (KNN_MIN_SCORE + of 0.8), this process identifies and links chunks with high semantic similarity. This results in a more + interconnected and insightful knowledge representation, enabling more accurate and relevant search + results.`, + }, + { + title: 'enable_hybrid_search_and_fulltext_search_in_bloom', + description: `This option optimizes search capabilities within your knowledge graph. It rebuilds the full-text index + on database labels, ensuring faster and more efficient retrieval of information. This is particularly + beneficial for large knowledge graphs, as it significantly speeds up keyword-based searches and improves + overall query performance.`, + }, + { + title: 'materialize_entity_similarities', + description: `Enhances entity analysis by generating numerical representations (embeddings) that capture their + semantic meaning. This facilitates tasks like clustering similar entities, identifying duplicates, and + performing similarity-based searches.`, + }, +]; + +export const batchSize: number = parseInt(process.env.VITE_BATCH_SIZE ?? '2'); export const nvlOptions: NvlOptions = { allowDynamicMinZoom: true, @@ -176,18 +205,34 @@ export const mouseEventCallbacks = { onDrag: true, }; -export const graphQuery: string = queryMap.DocChunkEntities; +// export const graphQuery: string = queryMap.DocChunkEntities; export const graphView: OptionType[] = [ { label: 'Lexical Graph', value: queryMap.DocChunks }, { label: 'Entity Graph', value: queryMap.Entities }, { label: 'Knowledge Graph', value: queryMap.DocChunkEntities }, ]; -export const intitalGraphType: GraphType[] = ['Document', 'Entities', 'Chunk']; -export const knowledgeGraph = 'Knowledge Graph'; -export const lexicalGraph = 'Lexical Graph'; -export const entityGraph = 'Entity Graph'; +export const intitalGraphType: GraphType[] = ['DocumentChunk', 'Entities']; export const appLabels = { ownSchema: 'Or Define your own Schema', predefinedSchema: 'Select a Pre-defined Schema', }; + +export const graphLabels = { + showGraphView: 'showGraphView', + chatInfoView: 'chatInfoView', + generateGraph: 'Generated Graph', + inspectGeneratedGraphFrom: 'Inspect Generated Graph from', + document: 'Document', + chunk: 'Chunk', + documentChunk: 'DocumentChunk', + entities: 'Entities', + resultOverview: 'Result Overview', + totalNodes: 'Total Nodes', + noEntities: 'No Entities Found', + selectCheckbox: 'Select atleast one checkbox for graph view', + totalRelationships: 'Total Relationships', + nodeSize: 30, +}; + +export const RESULT_STEP_SIZE = 25; diff --git a/frontend/src/utils/Queue.ts b/frontend/src/utils/Queue.ts new file mode 100644 index 000000000..725918623 --- /dev/null +++ b/frontend/src/utils/Queue.ts @@ -0,0 +1,42 @@ +import { CustomFile } from '../types'; +class Queue { + items: CustomFile[] = []; + + constructor(items: CustomFile[]) { + this.items = items; + } + + enqueue(item: CustomFile) { + this.items.push(item); + } + + dequeue() { + if (!this.isEmpty()) { + return this.items.shift(); + } + } + + peek() { + if (this.isEmpty()) { + return -1; + } + return this.items[0]; + } + + size() { + return this.items.length; + } + + isEmpty() { + return this.items.length === 0; + } + + remove(name: string) { + this.items = [...this.items.filter((f) => f.name != name)]; + } + + clear() { + this.items = []; + } +} +export default Queue; diff --git a/frontend/src/utils/Utils.ts b/frontend/src/utils/Utils.ts index 9230c0875..0b8b05841 100644 --- a/frontend/src/utils/Utils.ts +++ b/frontend/src/utils/Utils.ts @@ -1,12 +1,12 @@ import { calcWordColor } from '@neo4j-devtools/word-color'; -import type { Node, Relationship } from '@neo4j-nvl/base'; -import { GraphType, Messages, Scheme } from '../types'; +import type { Relationship } from '@neo4j-nvl/base'; +import { Entity, ExtendedNode, ExtendedRelationship, GraphType, Messages, Scheme } from '../types'; // Get the Url export const url = () => { let url = window.location.href.replace('5173', '8000'); - if (process.env.BACKEND_API_URL) { - url = process.env.BACKEND_API_URL; + if (process.env.VITE_BACKEND_API_URL) { + url = process.env.VITE_BACKEND_API_URL; } return !url || !url.match('/$') ? url : url.substring(0, url.length - 1); }; @@ -121,7 +121,7 @@ export const getIcon = (node: any) => { }; export function extractPdfFileName(url: string): string { const splitUrl = url.split('/'); - const encodedFileName = splitUrl[splitUrl.length - 1].split('?')[0]; + const [encodedFileName] = splitUrl[splitUrl.length - 1].split('?'); const decodedFileName = decodeURIComponent(encodedFileName); if (decodedFileName.includes('/')) { const splitedstr = decodedFileName.split('/'); @@ -130,7 +130,7 @@ export function extractPdfFileName(url: string): string { return decodedFileName; } -export const processGraphData = (neoNodes: Node[], neoRels: Relationship[]) => { +export const processGraphData = (neoNodes: ExtendedNode[], neoRels: ExtendedRelationship[]) => { const schemeVal: Scheme = {}; let iterator = 0; const labels: string[] = neoNodes.map((f: any) => f.labels); @@ -140,7 +140,7 @@ export const processGraphData = (neoNodes: Node[], neoRels: Relationship[]) => { iterator += 1; } }); - const newNodes: Node[] = neoNodes.map((g: any) => { + const newNodes: ExtendedNode[] = neoNodes.map((g: any) => { return { id: g.element_id, size: getSize(g), @@ -150,6 +150,7 @@ export const processGraphData = (neoNodes: Node[], neoRels: Relationship[]) => { color: schemeVal[g.labels[0]], icon: getIcon(g), labels: g.labels, + properties: g.properties, }; }); const finalNodes = newNodes.flat(); @@ -167,63 +168,40 @@ export const processGraphData = (neoNodes: Node[], neoRels: Relationship[]) => { export const filterData = ( graphType: GraphType[], - allNodes: Node[], + allNodes: ExtendedNode[], allRelationships: Relationship[], scheme: Scheme ) => { - let filteredNodes: Node[] = []; + let filteredNodes: ExtendedNode[] = []; let filteredRelations: Relationship[] = []; let filteredScheme: Scheme = {}; const entityTypes = Object.keys(scheme).filter((type) => type !== 'Document' && type !== 'Chunk'); - if (graphType.includes('Document') && !graphType.includes('Entities') && !graphType.includes('Chunk')) { - // Document only - // @ts-ignore - filteredNodes = allNodes.filter((node) => node.labels.includes('Document')); - filteredScheme = { Document: scheme.Document }; - } else if (!graphType.includes('Document') && graphType.includes('Entities') && !graphType.includes('Chunk')) { - // Only Entity - // @ts-ignore - const entityNode = allNodes.filter((node) => !node.labels.includes('Document') && !node.labels.includes('Chunk')); - filteredNodes = entityNode ? entityNode : []; - // @ts-ignore - filteredRelations = allRelationships.filter( - (rel) => !['PART_OF', 'FIRST_CHUNK', 'HAS_ENTITY', 'SIMILAR', 'NEXT_CHUNK'].includes(rel.caption) - ); - filteredScheme = Object.fromEntries(entityTypes.map((key) => [key, scheme[key]])) as Scheme; - } else if (!graphType.includes('Document') && !graphType.includes('Entities') && graphType.includes('Chunk')) { - // Only Chunk - // @ts-ignore - filteredNodes = allNodes.filter((node) => node.labels.includes('Chunk')); - // @ts-ignore - filteredRelations = allRelationships.filter((rel) => ['SIMILAR', 'NEXT_CHUNK'].includes(rel.caption)); - filteredScheme = { Chunk: scheme.Chunk }; - } else if (graphType.includes('Document') && graphType.includes('Entities') && !graphType.includes('Chunk')) { - // Document + Entity - // @ts-ignore + if (graphType.includes('DocumentChunk') && !graphType.includes('Entities')) { + // Document + Chunk filteredNodes = allNodes.filter( - (node) => - node.labels.includes('Document') || (!node.labels.includes('Document') && !node.labels.includes('Chunk')) + (node) => (node.labels.includes('Document') && node.properties.fileName) || node.labels.includes('Chunk') ); - // @ts-ignore + const nodeIds = new Set(filteredNodes.map((node) => node.id)); filteredRelations = allRelationships.filter( - (rel) => !['PART_OF', 'FIRST_CHUNK', 'HAS_ENTITY', 'SIMILAR', 'NEXT_CHUNK'].includes(rel.caption) - ); - } else if (graphType.includes('Document') && !graphType.includes('Entities') && graphType.includes('Chunk')) { - // Document + Chunk - // @ts-ignore - filteredNodes = allNodes.filter((node) => node.labels.includes('Document') || node.labels.includes('Chunk')); - // @ts-ignore - filteredRelations = allRelationships.filter((rel) => - ['PART_OF', 'FIRST_CHUNK', 'SIMILAR', 'NEXT_CHUNK'].includes(rel.caption) + (rel) => + ['PART_OF', 'FIRST_CHUNK', 'SIMILAR', 'NEXT_CHUNK'].includes(rel.caption ?? '') && + nodeIds.has(rel.from) && + nodeIds.has(rel.to) ); filteredScheme = { Document: scheme.Document, Chunk: scheme.Chunk }; - } else if (!graphType.includes('Document') && graphType.includes('Entities') && graphType.includes('Chunk')) { - // Chunk + Entity - // @ts-ignore - filteredNodes = allNodes.filter((node) => !node.labels.includes('Document')); - // @ts-ignore - filteredRelations = allRelationships.filter((rel) => !['PART_OF', 'FIRST_CHUNK'].includes(rel.caption)); - } else if (graphType.includes('Document') && graphType.includes('Entities') && graphType.includes('Chunk')) { + } else if (graphType.includes('Entities') && !graphType.includes('DocumentChunk')) { + // Only Entity + const entityNodes = allNodes.filter((node) => !node.labels.includes('Document') && !node.labels.includes('Chunk')); + filteredNodes = entityNodes ? entityNodes : []; + const nodeIds = new Set(filteredNodes.map((node) => node.id)); + filteredRelations = allRelationships.filter( + (rel) => + !['PART_OF', 'FIRST_CHUNK', 'HAS_ENTITY', 'SIMILAR', 'NEXT_CHUNK'].includes(rel.caption ?? '') && + nodeIds.has(rel.from) && + nodeIds.has(rel.to) + ); + filteredScheme = Object.fromEntries(entityTypes.map((key) => [key, scheme[key]])) as Scheme; + } else if (graphType.includes('DocumentChunk') && graphType.includes('Entities')) { // Document + Chunk + Entity filteredNodes = allNodes; filteredRelations = allRelationships; @@ -251,3 +229,19 @@ export const calculateProcessingTime = (fileSizeBytes: number, processingTimePer export const capitalize = (word: string): string => { return `${word[0].toUpperCase()}${word.slice(1)}`; }; +export const parseEntity = (entity: Entity) => { + const { labels, properties } = entity; + const [label] = labels; + const text = properties.id; + return { label, text }; +}; + +export const titleCheck = (title: string) => { + return title === 'Chunk' || title === 'Document'; +}; + +export const sortAlphabetically = (a: Relationship, b: Relationship) => { + const captionOne = a.caption?.toLowerCase() || ''; + const captionTwo = b.caption?.toLowerCase() || ''; + return captionOne.localeCompare(captionTwo); +}; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index bd3e40e45..34487e0a4 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -4,11 +4,12 @@ import react from '@vitejs/plugin-react'; // see https://stackoverflow.com/questions/73834404/react-uncaught-referenceerror-process-is-not-defined // otherwise use import.meta.env.VITE_BACKEND_API_URL and expose it as such with the VITE_ prefix export default defineConfig(({ mode }) => { - const env = loadEnv(mode, process.cwd(), ''); + const env = loadEnv(mode, process.cwd(), 'VITE_'); return { define: { 'process.env': env, }, plugins: [react()], + optimizeDeps: { esbuildOptions: { target: 'es2020' } }, }; }); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9a0bfb935..3915845ef 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -23,75 +23,53 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" -"@babel/compat-data@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" - integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== +"@babel/compat-data@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5" + integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ== "@babel/core@^7.24.5": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" - integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helpers" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" - integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== +"@babel/generator@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" + integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== dependencies: - "@babel/types" "^7.24.7" + "@babel/types" "^7.25.0" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" - integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== dependencies: - "@babel/compat-data" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - browserslist "^4.22.2" + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-environment-visitor@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" - integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== - dependencies: - "@babel/types" "^7.24.7" - -"@babel/helper-function-name@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" - integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== - dependencies: - "@babel/template" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-hoist-variables@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" - integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== - dependencies: - "@babel/types" "^7.24.7" - "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" @@ -100,21 +78,20 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-module-transforms@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" - integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== +"@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-module-imports" "^7.24.7" "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" "@babel/helper-plugin-utils@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" - integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== "@babel/helper-simple-access@^7.24.7": version "7.24.7" @@ -124,35 +101,28 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-split-export-declaration@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" - integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== - dependencies: - "@babel/types" "^7.24.7" - -"@babel/helper-string-parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" - integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== -"@babel/helper-validator-option@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" - integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== -"@babel/helpers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" - integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== +"@babel/helpers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" + integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== dependencies: - "@babel/template" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" "@babel/highlight@^7.24.7": version "7.24.7" @@ -164,10 +134,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" - integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065" + integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw== + dependencies: + "@babel/types" "^7.25.2" "@babel/plugin-transform-react-jsx-self@^7.24.5": version "7.24.7" @@ -183,51 +155,48 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" - integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" - integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== +"@babel/template@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" -"@babel/traverse@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" - integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== +"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490" + integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-hoist-variables" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.2" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" - integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" + integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== dependencies: - "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" "@codemirror/autocomplete@^6.4.1": - version "6.17.0" - resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.17.0.tgz#24ff5fc37fd91f6439df6f4ff9c8e910cde1b053" - integrity sha512-fdfj6e6ZxZf8yrkMHUSJJir7OJkHkZKaOZGzLWIYp2PZ3jd+d+UjG8zVPqJF6d3bKxkhvXTPan/UZ1t7Bqm0gA== + version "6.18.0" + resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.18.0.tgz#5f39b05daca04c95e990b70024144df47b2aa635" + integrity sha512-5DbOvBbY4qW5l57cjDsmmpDh3/TeK1vXfTHa+BUMrRzdWdcxKZ4U4V7vQaTtOpApNU4kLS4FQ6cINtLg245LXA== dependencies: "@codemirror/language" "^6.0.0" "@codemirror/state" "^6.0.0" @@ -280,9 +249,9 @@ integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A== "@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.9.0": - version "6.28.4" - resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.28.4.tgz#6fb91182ad9cfa1d300578342d3327348f3ed912" - integrity sha512-QScv95fiviSQ/CaVGflxAvvvDy/9wi0RFyDl4LkHHWiMr/UPebyuTspmYSeN5Nx6eujcPYwsQzA6ZIZucKZVHQ== + version "6.30.0" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.30.0.tgz#4daceb2b3951477b99283b59b98ed0c01ce016b1" + integrity sha512-96Nmn8OeLh6aONQprIeYk8hGVnEuYpWuxKSkdsODOx9hWPxyuyZGvmvxV/JmLsp+CubMO1PsLaN5TNNgrl0UrQ== dependencies: "@codemirror/state" "^6.4.0" style-mod "^4.1.0" @@ -319,16 +288,16 @@ dependencies: tslib "^2.0.0" -"@emotion/babel-plugin@^11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" - integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== +"@emotion/babel-plugin@^11.12.0": + version "11.12.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2" + integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw== dependencies: "@babel/helper-module-imports" "^7.16.7" "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/serialize" "^1.1.2" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.2.0" babel-plugin-macros "^3.1.0" convert-source-map "^1.5.0" escape-string-regexp "^4.0.0" @@ -336,95 +305,95 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" - integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== +"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": + version "11.13.1" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" + integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== dependencies: - "@emotion/memoize" "^0.8.1" - "@emotion/sheet" "^1.2.2" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" stylis "4.2.0" -"@emotion/hash@^0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" - integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== -"@emotion/is-prop-valid@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" - integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== +"@emotion/is-prop-valid@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz#bd84ba972195e8a2d42462387581560ef780e4e2" + integrity sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ== dependencies: - "@emotion/memoize" "^0.8.1" + "@emotion/memoize" "^0.9.0" -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== "@emotion/react@^11.8.1": - version "11.11.4" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" - integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw== + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.0.tgz#a9ebf827b98220255e5760dac89fa2d38ca7b43d" + integrity sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.3" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" + "@emotion/babel-plugin" "^11.12.0" + "@emotion/cache" "^11.13.0" + "@emotion/serialize" "^1.3.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3", "@emotion/serialize@^1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.4.tgz#fc8f6d80c492cfa08801d544a05331d1cc7cd451" - integrity sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ== +"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.0.tgz#e07cadfc967a4e7816e0c3ffaff4c6ce05cb598d" + integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA== dependencies: - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/unitless" "^0.8.1" - "@emotion/utils" "^1.2.1" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.9.0" + "@emotion/utils" "^1.4.0" csstype "^3.0.2" -"@emotion/sheet@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" - integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== "@emotion/styled@^11.11.0": - version "11.11.5" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.5.tgz#0c5c8febef9d86e8a926e663b2e5488705545dfb" - integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ== + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.13.0.tgz#633fd700db701472c7a5dbef54d6f9834e9fb190" + integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/is-prop-valid" "^1.2.2" - "@emotion/serialize" "^1.1.4" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - -"@emotion/unitless@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" - integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== - -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + "@emotion/babel-plugin" "^11.12.0" + "@emotion/is-prop-valid" "^1.3.0" + "@emotion/serialize" "^1.3.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" + +"@emotion/unitless@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.9.0.tgz#8e5548f072bd67b8271877e51c0f95c76a66cbe2" + integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" + integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== -"@emotion/utils@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" - integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== +"@emotion/utils@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd" + integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== "@esbuild/android-arm64@0.18.20": version "0.18.20" @@ -569,19 +538,19 @@ integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== "@floating-ui/core@^1.6.0": - version "1.6.4" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.4.tgz#0140cf5091c8dee602bff9da5ab330840ff91df6" - integrity sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA== + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12" + integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g== dependencies: - "@floating-ui/utils" "^0.2.4" + "@floating-ui/utils" "^0.2.7" "@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.3.0": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.7.tgz#85d22f731fcc5b209db504478fb1df5116a83015" - integrity sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng== + version "1.6.10" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f" + integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A== dependencies: "@floating-ui/core" "^1.6.0" - "@floating-ui/utils" "^0.2.4" + "@floating-ui/utils" "^0.2.7" "@floating-ui/react-dom@2.0.1": version "2.0.1" @@ -590,7 +559,7 @@ dependencies: "@floating-ui/dom" "^1.3.0" -"@floating-ui/react-dom@^2.0.1", "@floating-ui/react-dom@^2.0.8": +"@floating-ui/react-dom@^2.0.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0" integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg== @@ -611,10 +580,10 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== -"@floating-ui/utils@^0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c" - integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA== +"@floating-ui/utils@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e" + integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== "@formatjs/ecma402-abstract@2.0.0": version "2.0.0" @@ -679,10 +648,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@internationalized/date@^3.5.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.4.tgz#49ba11634fd4350b7a9308e297032267b4063c44" - integrity sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw== +"@internationalized/date@^3.5.5": + version "3.5.5" + resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.5.tgz#7d34cb9da35127f98dd669fc926bb37e771e177f" + integrity sha512-H+CfYvOZ0LTJeeLOqm19E3uj/4YjrmOFtBufDHPfvtI80hFAMqtrp7oCACpe4Cil5l8S0Qu/9dYfZc/5lY8WQQ== dependencies: "@swc/helpers" "^0.5.0" @@ -740,9 +709,9 @@ integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" @@ -765,95 +734,96 @@ "@lezer/common" "^1.0.0" "@lezer/lr@^1.0.0": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.1.tgz#fe25f051880a754e820b28148d90aa2a96b8bdd2" - integrity sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw== + version "1.4.2" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.2.tgz#931ea3dea8e9de84e90781001dae30dea9ff1727" + integrity sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA== dependencies: "@lezer/common" "^1.0.0" -"@mui/base@5.0.0-beta.40": - version "5.0.0-beta.40" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" - integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== +"@lukeed/csprng@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + +"@lukeed/uuid@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@lukeed/uuid/-/uuid-2.0.1.tgz#4f6c34259ee0982a455e1797d56ac27bb040fd74" + integrity sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w== dependencies: - "@babel/runtime" "^7.23.9" - "@floating-ui/react-dom" "^2.0.8" - "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.14" - "@popperjs/core" "^2.11.8" - clsx "^2.1.0" - prop-types "^15.8.1" + "@lukeed/csprng" "^1.1.0" -"@mui/core-downloads-tracker@^5.15.21": - version "5.15.21" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.21.tgz#15ffc56cef7009479229b55126176f988afba96b" - integrity sha512-dp9lXBaJZzJYeJfQY3Ow4Rb49QaCEdkl2KKYscdQHQm6bMJ+l4XPY3Cd9PCeeJTsHPIDJ60lzXbeRgs6sx/rpw== +"@mui/core-downloads-tracker@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.6.tgz#f029e12ffda8eb79838cc85897f03a628010037c" + integrity sha512-kytg6LheUG42V8H/o/Ptz3olSO5kUXW9zF0ox18VnblX6bO2yif1FPItgc3ey1t5ansb1+gbe7SatntqusQupg== "@mui/material@^5.15.10": - version "5.15.21" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.21.tgz#b2c8d756af570a61cb4975acf0e71dafb110b001" - integrity sha512-nTyCcgduKwHqiuQ/B03EQUa+utSMzn2sQp0QAibsnYe4tvc3zkMbO0amKpl48vhABIY3IvT6w9615BFIgMt0YA== + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.6.tgz#c7d695f4a9a473052dc086e64471d0435b7e4a52" + integrity sha512-0LUIKBOIjiFfzzFNxXZBRAyr9UQfmTAFzbt6ziOU2FDXhorNN2o3N9/32mNJbCA8zJo2FqFU6d3dtoqUDyIEfA== dependencies: "@babel/runtime" "^7.23.9" - "@mui/base" "5.0.0-beta.40" - "@mui/core-downloads-tracker" "^5.15.21" - "@mui/system" "^5.15.20" - "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.20" + "@mui/core-downloads-tracker" "^5.16.6" + "@mui/system" "^5.16.6" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.6" + "@popperjs/core" "^2.11.8" "@types/react-transition-group" "^4.4.10" clsx "^2.1.0" csstype "^3.1.3" prop-types "^15.8.1" - react-is "^18.2.0" + react-is "^18.3.1" react-transition-group "^4.4.5" -"@mui/private-theming@^5.15.20": - version "5.15.20" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.20.tgz#028c4e3c717a13691ac2c8c98e29aa819d89001a" - integrity sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g== +"@mui/private-theming@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.6.tgz#547671e7ae3f86b68d1289a0b90af04dfcc1c8c9" + integrity sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw== dependencies: "@babel/runtime" "^7.23.9" - "@mui/utils" "^5.15.20" + "@mui/utils" "^5.16.6" prop-types "^15.8.1" -"@mui/styled-engine@^5.15.14", "@mui/styled-engine@^5.15.9": - version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" - integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw== +"@mui/styled-engine@^5.15.9", "@mui/styled-engine@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.6.tgz#60110c106dd482dfdb7e2aa94fd6490a0a3f8852" + integrity sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g== dependencies: "@babel/runtime" "^7.23.9" "@emotion/cache" "^11.11.0" csstype "^3.1.3" prop-types "^15.8.1" -"@mui/system@^5.15.20": - version "5.15.20" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.20.tgz#f1933aabc4c10f8580c7a951ca3b88542ef0f76b" - integrity sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA== +"@mui/system@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.6.tgz#2dabe63d2e45816ce611c40d6e3f79b9c2ccbcd7" + integrity sha512-5xgyJjBIMPw8HIaZpfbGAaFYPwImQn7Nyh+wwKWhvkoIeDosQ1ZMVrbTclefi7G8hNmqhip04duYwYpbBFnBgw== dependencies: "@babel/runtime" "^7.23.9" - "@mui/private-theming" "^5.15.20" - "@mui/styled-engine" "^5.15.14" - "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.20" + "@mui/private-theming" "^5.16.6" + "@mui/styled-engine" "^5.16.6" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.6" clsx "^2.1.0" csstype "^3.1.3" prop-types "^15.8.1" -"@mui/types@^7.2.14": - version "7.2.14" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" - integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== +"@mui/types@^7.2.15": + version "7.2.15" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53" + integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q== -"@mui/utils@^5.15.14", "@mui/utils@^5.15.20": - version "5.15.20" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.20.tgz#92778d749ce5ded1598639b4e684aaedb1146e08" - integrity sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A== +"@mui/utils@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.6.tgz#905875bbc58d3dcc24531c3314a6807aba22a711" + integrity sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA== dependencies: "@babel/runtime" "^7.23.9" - "@types/prop-types" "^15.7.11" + "@mui/types" "^7.2.15" + "@types/prop-types" "^15.7.12" + clsx "^2.1.1" prop-types "^15.8.1" - react-is "^18.2.0" + react-is "^18.3.1" "@neo4j-bloom/dagre@^0.8.14": version "0.8.14" @@ -922,15 +892,15 @@ "@types/chroma-js" "2.1.4" chroma-js "2.4.2" -"@neo4j-ndl/base@^2.11.6": - version "2.11.6" - resolved "https://registry.yarnpkg.com/@neo4j-ndl/base/-/base-2.11.6.tgz#3eacf4e41a86d84e0663f905a769ef95d3fa9cc2" - integrity sha512-HA4YWgN3GcGFzDcAj1maYjYgfD8mwHcFkwh/BaY1964/A2T9BCffHsxtQaPOW24AIhdO5tMMH72qwcvZB2PvlQ== +"@neo4j-ndl/base@^2.12.10", "@neo4j-ndl/base@^2.12.7": + version "2.12.10" + resolved "https://registry.yarnpkg.com/@neo4j-ndl/base/-/base-2.12.10.tgz#50c96e34593e2b3ee157f89a1dbcdceceae5dd89" + integrity sha512-RPwHeUki7JBUTfORA/ssrqOZb1LcMbfZyWrKPYKi8QTrEzypEUONwuX8hef6sg4E+vp+4WI/dAEsjnTezCpB8A== -"@neo4j-ndl/react@^2.15.10": - version "2.15.10" - resolved "https://registry.yarnpkg.com/@neo4j-ndl/react/-/react-2.15.10.tgz#c109633adc3fd08fb510b3b57e70a1ed5216bf4f" - integrity sha512-U3pFJS23riItlklCgVZs40xxVAypVAM3u+rbIItOJYDEl1/yxtoYtjuFSyyegke6Hw6EBw/gJ73aBMel7hEWoQ== +"@neo4j-ndl/react@^2.16.9": + version "2.16.14" + resolved "https://registry.yarnpkg.com/@neo4j-ndl/react/-/react-2.16.14.tgz#384ba28e2fe80b02201c6be4bf8b5090ed8392fa" + integrity sha512-Whhj8kN0l0ppoz2EJRMUQE8OaJUK5/SsMpRLCETD907Ikwdeo8W97Jm2m5pNsWw0XiRfCVa/t4kX7oHQkZI56w== dependencies: "@dnd-kit/core" "6.1.0" "@dnd-kit/sortable" "8.0.0" @@ -938,7 +908,7 @@ "@floating-ui/react-dom" "2.0.1" "@heroicons/react" "2.0.18" "@neo4j-cypher/react-codemirror" "^1.0.1" - "@neo4j-ndl/base" "^2.11.6" + "@neo4j-ndl/base" "^2.12.10" "@table-nav/core" "0.0.7" "@table-nav/react" "0.0.7" "@tanstack/react-table" "8.11.8" @@ -959,14 +929,14 @@ tinycolor2 "^1.4.2" usehooks-ts "2.9.1" -"@neo4j-nvl/base@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@neo4j-nvl/base/-/base-0.3.1.tgz#d38f3fa0925828194463975310a7cac2717d7e22" - integrity sha512-bI9a0nY4ACrvSXS9tpuqf7oQ1U+/3Ooen/2FefcnXYsHd0Fmk71wDj0Ia1BMPQ6JetDkc/Vj8GrexLxuk0UzUA== +"@neo4j-nvl/base@0.3.3", "@neo4j-nvl/base@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@neo4j-nvl/base/-/base-0.3.3.tgz#6c96c8e76c97f0ce431ea18417ba18e9236bcbdb" + integrity sha512-+WYcgeuud0YSqc1kiFqHabPkSKgBIG3aLkMFvKoag2NwqIPhLGEzxYkqxqXz6kkKSAxDXGOs5R8SzxLWm2G4iw== dependencies: - "@neo4j-nvl/layout-workers" "^0.3.1" + "@neo4j-nvl/layout-workers" "0.3.3" + "@segment/analytics-next" "^1.70.0" color-string "^1.9.1" - contrast "1.0.1" d3-force "^3.0.0" gl-matrix "^3.3.0" glsl-inject-defines "^1.0.3" @@ -975,20 +945,22 @@ mobx "^3.2.2" node-pid-controller "^1.0.1" resizelistener "^1.1.0" + tinycolor2 "1.6.0" + uuid "^8.3.2" -"@neo4j-nvl/interaction-handlers@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@neo4j-nvl/interaction-handlers/-/interaction-handlers-0.3.1.tgz#067a4ba504a8f55d68936ed945838006beee71c6" - integrity sha512-JHuwyNgfzc2Ua3GSUOI1pmai5r2QNiVR8qu1fYo8Nn9ppAHJQ2NwAG5x3BERxJc+3Z+WhePnx+c89bvnoBDphw== +"@neo4j-nvl/interaction-handlers@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@neo4j-nvl/interaction-handlers/-/interaction-handlers-0.3.3.tgz#bb555322a8d60668429d2efdab50d5f42c6d3216" + integrity sha512-BQMF8E/M9RT8nYoPwAH4j/C42iUEy8gzPAAeb0q39VFGDuHq0Dwu316feHPN1wB5z8I9UpQiJF6/c7OSWmQyWQ== dependencies: - "@neo4j-nvl/base" "^0.3.1" + "@neo4j-nvl/base" "0.3.3" concaveman "^1.2.1" lodash "4.17.21" -"@neo4j-nvl/layout-workers@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@neo4j-nvl/layout-workers/-/layout-workers-0.3.1.tgz#b8c7fd22a823f6274c1e4250244ab5b072663635" - integrity sha512-tVOU6V15vKMi8mHVzwcsAXzzkVl4Jh6+Id4JZPlGZRuw0U6AwrbmyfXe5tFEjPlO4CXG8dPd47fbYKIPVboeMg== +"@neo4j-nvl/layout-workers@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@neo4j-nvl/layout-workers/-/layout-workers-0.3.3.tgz#22be7cd3a7b898682638e23efc3738b9fff59fa6" + integrity sha512-IWalR7B9NnXFw5rLny583VkwmiVadNOraTzKpzDex6VjLakRDkFwhOZHm857qaitgekoxkWQ1iQX+l//VcblHg== dependencies: "@neo4j-bloom/dagre" "^0.8.14" bin-pack "^1.0.2" @@ -996,13 +968,13 @@ cytoscape-cose-bilkent "^4.1.0" graphlib "^2.1.8" -"@neo4j-nvl/react@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@neo4j-nvl/react/-/react-0.3.1.tgz#bb29ec2f3400d6188206b281d28d5902ad785999" - integrity sha512-OYkar+MAcfyfuGvNN+66ubIyVMpquxbgMAdgnnlMUxGeYjfL3WQ7/nshooK+wHJXLicPfOL77ZeLTSLERI9f3Q== +"@neo4j-nvl/react@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@neo4j-nvl/react/-/react-0.3.3.tgz#96f806a05478b9b520a3c88c2a784c6bc38cef08" + integrity sha512-oCX+CRTWts29d7UKQX9jwUpT9oYUXCfZvOv7RHNSa12P/wOBkDfkZeHals9vVSi7IuuqAnyHyBTq/rdZcERmSw== dependencies: - "@neo4j-nvl/base" "^0.3.1" - "@neo4j-nvl/interaction-handlers" "^0.3.1" + "@neo4j-nvl/base" "0.3.3" + "@neo4j-nvl/interaction-handlers" "0.3.3" lodash "4.17.21" react "^18.2.0" react-dom "^18.2.0" @@ -1039,253 +1011,252 @@ integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== "@react-aria/breadcrumbs@^3.5.9": - version "3.5.13" - resolved "https://registry.yarnpkg.com/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz#2686f7f460f20d67fe5cdfe185e32e3e78186962" - integrity sha512-G1Gqf/P6kVdfs94ovwP18fTWuIxadIQgHsXS08JEVcFVYMjb9YjqnEBaohUxD1tq2WldMbYw53ahQblT4NTG+g== - dependencies: - "@react-aria/i18n" "^3.11.1" - "@react-aria/link" "^3.7.1" - "@react-aria/utils" "^3.24.1" - "@react-types/breadcrumbs" "^3.7.5" - "@react-types/shared" "^3.23.1" + version "3.5.15" + resolved "https://registry.yarnpkg.com/@react-aria/breadcrumbs/-/breadcrumbs-3.5.15.tgz#c8ba91b36ec07b4c81bc6124461054a91d258217" + integrity sha512-KJ7678hwKbacz6dyY4aOJlgtV91PtuSnlWGR+AsK88WwHhpjjTjLLTSRepjbQ35GuQuoYokM4mmfaS/I0nblhw== + dependencies: + "@react-aria/i18n" "^3.12.1" + "@react-aria/link" "^3.7.3" + "@react-aria/utils" "^3.25.1" + "@react-types/breadcrumbs" "^3.7.7" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/button@^3.9.1": - version "3.9.5" - resolved "https://registry.yarnpkg.com/@react-aria/button/-/button-3.9.5.tgz#f0082f58394394f3d16fdf45de57b382748f3345" - integrity sha512-dgcYR6j8WDOMLKuVrtxzx4jIC05cVKDzc+HnPO8lNkBAOfjcuN5tkGRtIjLtqjMvpZHhQT5aDbgFpIaZzxgFIg== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-stately/toggle" "^3.7.4" - "@react-types/button" "^3.9.4" - "@react-types/shared" "^3.23.1" + version "3.9.7" + resolved "https://registry.yarnpkg.com/@react-aria/button/-/button-3.9.7.tgz#f839cb54d318d03cc3ec8bdcd31cca9a4046cd21" + integrity sha512-xwE6uatbbn3KbNSc0dyDnOo539HJM2cqCPfjiQGt8O9cFbpQSmx76Fj4WotU3BwT7ZVbcAC8D206CgF1C2cDcQ== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/toggle" "^3.7.6" + "@react-types/button" "^3.9.6" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/calendar@^3.5.4": - version "3.5.8" - resolved "https://registry.yarnpkg.com/@react-aria/calendar/-/calendar-3.5.8.tgz#fd0858b34c8961b76957e9ac13b514f485c329a3" - integrity sha512-Whlp4CeAA5/ZkzrAHUv73kgIRYjw088eYGSc+cvSOCxfrc/2XkBm9rNrnSBv0DvhJ8AG0Fjz3vYakTmF3BgZBw== + version "3.5.10" + resolved "https://registry.yarnpkg.com/@react-aria/calendar/-/calendar-3.5.10.tgz#749ac8baf44f7b4639b898dc9f4e80b5e905f4e4" + integrity sha512-5PokdIHAH+CAd6vMHFW9mg77I5tC0FQglYsCEI9ikhCnL5xlt3FmJjLtOs3UJQaWgrd4cdVd0oINpPafJ9ydhA== dependencies: - "@internationalized/date" "^3.5.4" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" + "@internationalized/date" "^3.5.5" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" "@react-aria/live-announcer" "^3.3.4" - "@react-aria/utils" "^3.24.1" - "@react-stately/calendar" "^3.5.1" - "@react-types/button" "^3.9.4" - "@react-types/calendar" "^3.4.6" - "@react-types/shared" "^3.23.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/calendar" "^3.5.3" + "@react-types/button" "^3.9.6" + "@react-types/calendar" "^3.4.8" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/checkbox@^3.13.0": - version "3.14.3" - resolved "https://registry.yarnpkg.com/@react-aria/checkbox/-/checkbox-3.14.3.tgz#6e2579681008e460d2c764a03f1f1b54e0815868" - integrity sha512-EtBJL6iu0gvrw3A4R7UeVLR6diaVk/mh4kFBc7c8hQjpEJweRr4hmJT3hrNg3MBcTWLxFiMEXPGgWEwXDBygtA== - dependencies: - "@react-aria/form" "^3.0.5" - "@react-aria/interactions" "^3.21.3" - "@react-aria/label" "^3.7.8" - "@react-aria/toggle" "^3.10.4" - "@react-aria/utils" "^3.24.1" - "@react-stately/checkbox" "^3.6.5" - "@react-stately/form" "^3.0.3" - "@react-stately/toggle" "^3.7.4" - "@react-types/checkbox" "^3.8.1" - "@react-types/shared" "^3.23.1" + version "3.14.5" + resolved "https://registry.yarnpkg.com/@react-aria/checkbox/-/checkbox-3.14.5.tgz#8e25a19a2cf6c28de603aebd77ec14f0f504333b" + integrity sha512-On8m66CNi1LvbDeDo355au0K66ayIjo0nDe4oe85aNsR/owyzz8hXNPAFuh98owQVMsKt4596FZICAVSMzzhJg== + dependencies: + "@react-aria/form" "^3.0.7" + "@react-aria/interactions" "^3.22.1" + "@react-aria/label" "^3.7.10" + "@react-aria/toggle" "^3.10.6" + "@react-aria/utils" "^3.25.1" + "@react-stately/checkbox" "^3.6.7" + "@react-stately/form" "^3.0.5" + "@react-stately/toggle" "^3.7.6" + "@react-types/checkbox" "^3.8.3" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/combobox@^3.8.1": - version "3.9.1" - resolved "https://registry.yarnpkg.com/@react-aria/combobox/-/combobox-3.9.1.tgz#ab12b698b76fd063f386aa5516129b2c72f5bf60" - integrity sha512-SpK92dCmT8qn8aEcUAihRQrBb5LZUhwIbDExFII8PvUvEFy/PoQHXIo3j1V29WkutDBDpMvBv/6XRCHGXPqrhQ== + version "3.10.1" + resolved "https://registry.yarnpkg.com/@react-aria/combobox/-/combobox-3.10.1.tgz#8e964815471e09f794d314945b7f8fabf250858a" + integrity sha512-B0zjX66HEqjPFnunYR0quAqwVJ6U0ez1eqBp25/611Dtzh3JHUovQmTE0xGGTjRe6N6qJg0VHVr2eRO/D0A+Lw== dependencies: - "@react-aria/i18n" "^3.11.1" - "@react-aria/listbox" "^3.12.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/listbox" "^3.13.1" "@react-aria/live-announcer" "^3.3.4" - "@react-aria/menu" "^3.14.1" - "@react-aria/overlays" "^3.22.1" - "@react-aria/selection" "^3.18.1" - "@react-aria/textfield" "^3.14.5" - "@react-aria/utils" "^3.24.1" - "@react-stately/collections" "^3.10.7" - "@react-stately/combobox" "^3.8.4" - "@react-stately/form" "^3.0.3" - "@react-types/button" "^3.9.4" - "@react-types/combobox" "^3.11.1" - "@react-types/shared" "^3.23.1" + "@react-aria/menu" "^3.15.1" + "@react-aria/overlays" "^3.23.1" + "@react-aria/selection" "^3.19.1" + "@react-aria/textfield" "^3.14.7" + "@react-aria/utils" "^3.25.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/combobox" "^3.9.1" + "@react-stately/form" "^3.0.5" + "@react-types/button" "^3.9.6" + "@react-types/combobox" "^3.12.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/datepicker@^3.9.1": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@react-aria/datepicker/-/datepicker-3.10.1.tgz#513a9d18e118d4c3d078fdbfc45dca76b7eeb37f" - integrity sha512-4HZL593nrNMa1GjBmWEN/OTvNS6d3/16G1YJWlqiUlv11ADulSbqBIjMmkgwrJVFcjrgqtXFy+yyrTA/oq94Zw== + version "3.11.1" + resolved "https://registry.yarnpkg.com/@react-aria/datepicker/-/datepicker-3.11.1.tgz#0162ac24f5e46c798422e5cdbb23a1c2d41f1de3" + integrity sha512-yEEuDt/ynt7bTfd/9RD1EiLPysWhbgSYSpn5PHVz7I2XORvNPpyamyAgz3+oFiLFLC/zy0qrG7e6V1rvI1NBzw== dependencies: - "@internationalized/date" "^3.5.4" + "@internationalized/date" "^3.5.5" "@internationalized/number" "^3.5.3" "@internationalized/string" "^3.2.3" - "@react-aria/focus" "^3.17.1" - "@react-aria/form" "^3.0.5" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/label" "^3.7.8" - "@react-aria/spinbutton" "^3.6.5" - "@react-aria/utils" "^3.24.1" - "@react-stately/datepicker" "^3.9.4" - "@react-stately/form" "^3.0.3" - "@react-types/button" "^3.9.4" - "@react-types/calendar" "^3.4.6" - "@react-types/datepicker" "^3.7.4" - "@react-types/dialog" "^3.5.10" - "@react-types/shared" "^3.23.1" + "@react-aria/focus" "^3.18.1" + "@react-aria/form" "^3.0.7" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/label" "^3.7.10" + "@react-aria/spinbutton" "^3.6.7" + "@react-aria/utils" "^3.25.1" + "@react-stately/datepicker" "^3.10.1" + "@react-stately/form" "^3.0.5" + "@react-types/button" "^3.9.6" + "@react-types/calendar" "^3.4.8" + "@react-types/datepicker" "^3.8.1" + "@react-types/dialog" "^3.5.12" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/dialog@^3.5.9": - version "3.5.14" - resolved "https://registry.yarnpkg.com/@react-aria/dialog/-/dialog-3.5.14.tgz#d4b078410c00b7cc7e6f25f67dfe53fa755be769" - integrity sha512-oqDCjQ8hxe3GStf48XWBf2CliEnxlR9GgSYPHJPUc69WBj68D9rVcCW3kogJnLAnwIyf3FnzbX4wSjvUa88sAQ== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/overlays" "^3.22.1" - "@react-aria/utils" "^3.24.1" - "@react-types/dialog" "^3.5.10" - "@react-types/shared" "^3.23.1" + version "3.5.16" + resolved "https://registry.yarnpkg.com/@react-aria/dialog/-/dialog-3.5.16.tgz#f21d69886ce9d87c07903cc51551888445dadb8e" + integrity sha512-2clBSQQaoqCjAUkHnMA/noZ1ZnFbEVU67fL9M1QfokezAyLAlyCyD9XSed6+Td/Ncj80N3/Lax65XAlvWCyOlg== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/overlays" "^3.23.1" + "@react-aria/utils" "^3.25.1" + "@react-types/dialog" "^3.5.12" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/dnd@^3.5.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@react-aria/dnd/-/dnd-3.6.1.tgz#c119ac8fa84b68a3533d9719cc32ab145481d381" - integrity sha512-6WnujUTD+cIYZVF/B+uXdHyJ+WSpbYa8jH282epvY4FUAq1qLmen12/HHcoj/5dswKQe8X6EM3OhkQM89d9vFw== + version "3.7.1" + resolved "https://registry.yarnpkg.com/@react-aria/dnd/-/dnd-3.7.1.tgz#091d42094fc8f47816075704b5b16a34da8b3a57" + integrity sha512-p3/pc8p2fGd4s+Qj4SfRPJjZFStuuXqRNyDQxd9AAFYUWcCQxwDOqtiTZmfvs7Hvl0PUuysHW6Q5v7ABRjVr7w== dependencies: "@internationalized/string" "^3.2.3" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" "@react-aria/live-announcer" "^3.3.4" - "@react-aria/overlays" "^3.22.1" - "@react-aria/utils" "^3.24.1" - "@react-stately/dnd" "^3.3.1" - "@react-types/button" "^3.9.4" - "@react-types/shared" "^3.23.1" + "@react-aria/overlays" "^3.23.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/dnd" "^3.4.1" + "@react-types/button" "^3.9.6" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/focus@^3.16.0", "@react-aria/focus@^3.17.1": - version "3.17.1" - resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.17.1.tgz#c796a188120421e2fedf438cadacdf463c77ad29" - integrity sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ== +"@react-aria/focus@^3.16.0", "@react-aria/focus@^3.18.1": + version "3.18.1" + resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.18.1.tgz#b54b88e78662549ddae917e3143723c8dd7a4e90" + integrity sha512-N0Cy61WCIv+57mbqC7hiZAsB+3rF5n4JKabxUmg/2RTJL6lq7hJ5N4gx75ymKxkN8GnVDwt4pKZah48Wopa5jw== dependencies: - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-types/shared" "^3.23.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" clsx "^2.0.0" -"@react-aria/form@^3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@react-aria/form/-/form-3.0.5.tgz#abaf6ac005dc3f98760ac74fdb6524ad189399d6" - integrity sha512-n290jRwrrRXO3fS82MyWR+OKN7yznVesy5Q10IclSTVYHHI3VI53xtAPr/WzNjJR1um8aLhOcDNFKwnNIUUCsQ== +"@react-aria/form@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@react-aria/form/-/form-3.0.7.tgz#767a6474a20d4206dca0fd5f5946656452ff59a9" + integrity sha512-VIsKP/KytJPOLRQl0NxWWS1bQELPBuW3vRjmmhBrtgPFmp0uCLhjPBkP6A4uIVj1E/JtAocyHN3DNq4+IJGQCg== dependencies: - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-stately/form" "^3.0.3" - "@react-types/shared" "^3.23.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/form" "^3.0.5" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/grid@^3.9.1": - version "3.9.1" - resolved "https://registry.yarnpkg.com/@react-aria/grid/-/grid-3.9.1.tgz#7fcf7a8352ece79406caf3cd149947fb9f000009" - integrity sha512-fGEZqAEaS8mqzV/II3N4ndoNWegIcbh+L3PmKbXdpKKUP8VgMs/WY5rYl5WAF0f5RoFwXqx3ibDLeR9tKj/bOg== +"@react-aria/grid@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@react-aria/grid/-/grid-3.10.1.tgz#5eb81575c9f8d872b00e2c43e5167f5d9451f131" + integrity sha512-7dSgiYVQapBtPV4SIit+9fJ1qoEjtp+PXffJkWAPtGbg/jJ4b0jcVzykH7ARD4w/6jAJN/oVSfrKZqFPoLAd9w== dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" + "@react-aria/focus" "^3.18.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" "@react-aria/live-announcer" "^3.3.4" - "@react-aria/selection" "^3.18.1" - "@react-aria/utils" "^3.24.1" - "@react-stately/collections" "^3.10.7" - "@react-stately/grid" "^3.8.7" - "@react-stately/selection" "^3.15.1" - "@react-stately/virtualizer" "^3.7.1" - "@react-types/checkbox" "^3.8.1" - "@react-types/grid" "^3.2.6" - "@react-types/shared" "^3.23.1" + "@react-aria/selection" "^3.19.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/grid" "^3.9.1" + "@react-stately/selection" "^3.16.1" + "@react-types/checkbox" "^3.8.3" + "@react-types/grid" "^3.2.8" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/gridlist@^3.7.3", "@react-aria/gridlist@^3.8.1": - version "3.8.1" - resolved "https://registry.yarnpkg.com/@react-aria/gridlist/-/gridlist-3.8.1.tgz#4a8e805687f02f96347494343e265fd6eac01b56" - integrity sha512-vVPkkA+Ct0NDcpnNm/tnYaBumg0fP9pXxsPLqL1rxvsTyj1PaIpFTZ4corabPTbTDExZwUSTS3LG1n+o1OvBtQ== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/grid" "^3.9.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/selection" "^3.18.1" - "@react-aria/utils" "^3.24.1" - "@react-stately/collections" "^3.10.7" - "@react-stately/list" "^3.10.5" - "@react-stately/tree" "^3.8.1" - "@react-types/shared" "^3.23.1" +"@react-aria/gridlist@^3.7.3", "@react-aria/gridlist@^3.9.1": + version "3.9.1" + resolved "https://registry.yarnpkg.com/@react-aria/gridlist/-/gridlist-3.9.1.tgz#02672b42f8b1c8df26521ef24b5722aee252da09" + integrity sha512-cue2KCI4WyVmL3j9tZx7xG7gUJ7UyRbawzRTcocJukOmpeoyRaw/robrIYK2Pd//GhRbIMAoo4iOyZk5j7vEww== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/grid" "^3.10.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/selection" "^3.19.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/list" "^3.10.7" + "@react-stately/tree" "^3.8.3" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/i18n@^3.10.0", "@react-aria/i18n@^3.11.1": - version "3.11.1" - resolved "https://registry.yarnpkg.com/@react-aria/i18n/-/i18n-3.11.1.tgz#2d238d2be30d8c691b5fa3161f5fb48066fc8e4b" - integrity sha512-vuiBHw1kZruNMYeKkTGGnmPyMnM5T+gT8bz97H1FqIq1hQ6OPzmtBZ6W6l6OIMjeHI5oJo4utTwfZl495GALFQ== +"@react-aria/i18n@^3.10.0", "@react-aria/i18n@^3.12.1": + version "3.12.1" + resolved "https://registry.yarnpkg.com/@react-aria/i18n/-/i18n-3.12.1.tgz#2b170953867f14688c4dd17b2ab55a8ca8eba07d" + integrity sha512-0q3gyogF9Ekah+9LOo6tcfshxsk2Ope+KdbtFHJVhznedMxn6RpHGcVur5ImbQ1dYafA5CmjBUGJW70b56+BGA== dependencies: - "@internationalized/date" "^3.5.4" + "@internationalized/date" "^3.5.5" "@internationalized/message" "^3.1.4" "@internationalized/number" "^3.5.3" "@internationalized/string" "^3.2.3" - "@react-aria/ssr" "^3.9.4" - "@react-aria/utils" "^3.24.1" - "@react-types/shared" "^3.23.1" + "@react-aria/ssr" "^3.9.5" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/interactions@^3.20.1", "@react-aria/interactions@^3.21.3": - version "3.21.3" - resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.21.3.tgz#a2a3e354a8b894bed7a46e1143453f397f2538d7" - integrity sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA== +"@react-aria/interactions@^3.20.1", "@react-aria/interactions@^3.22.1": + version "3.22.1" + resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.22.1.tgz#f2219a100c886cee383da7be9ae05e9dd940d39a" + integrity sha512-5TLzQaDAQQ5C70yG8GInbO4wIylKY67RfTIIwQPGR/4n5OIjbUD8BOj3NuSsuZ/frUPaBXo1VEBBmSO23fxkjw== dependencies: - "@react-aria/ssr" "^3.9.4" - "@react-aria/utils" "^3.24.1" - "@react-types/shared" "^3.23.1" + "@react-aria/ssr" "^3.9.5" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/label@^3.7.4", "@react-aria/label@^3.7.8": - version "3.7.8" - resolved "https://registry.yarnpkg.com/@react-aria/label/-/label-3.7.8.tgz#69f1c184836b04445fcedce78db9fd939a0570ea" - integrity sha512-MzgTm5+suPA3KX7Ug6ZBK2NX9cin/RFLsv1BdafJ6CZpmUSpWnGE/yQfYUB7csN7j31OsZrD3/P56eShYWAQfg== +"@react-aria/label@^3.7.10", "@react-aria/label@^3.7.4": + version "3.7.10" + resolved "https://registry.yarnpkg.com/@react-aria/label/-/label-3.7.10.tgz#5343c95da514bc448ad779b2fbec9f8dd550e0d1" + integrity sha512-e5XVHA+OUK0aIwr4nHcnIj0z1kUryGaJWYYD2OGkkIltyUCKmwpRqdx8LQYbO4HGsJhvC3hJgidFdGcQwHHPYw== dependencies: - "@react-aria/utils" "^3.24.1" - "@react-types/shared" "^3.23.1" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/link@^3.6.3", "@react-aria/link@^3.7.1": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@react-aria/link/-/link-3.7.1.tgz#ae75feebc5c6f40e1031abf57f3125d45882e976" - integrity sha512-a4IaV50P3fXc7DQvEIPYkJJv26JknFbRzFT5MJOMgtzuhyJoQdILEUK6XHYjcSSNCA7uLgzpojArVk5Hz3lCpw== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-types/link" "^3.5.5" - "@react-types/shared" "^3.23.1" +"@react-aria/link@^3.6.3", "@react-aria/link@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@react-aria/link/-/link-3.7.3.tgz#a6170ed04c67e55dedf0eec1b699cfa168e9eb28" + integrity sha512-dOwzxzo7LF4djBfRC8GcIhuTpDkNUIMT6ykQRV1a3749kgrr10YLascsO/l66k60i2k0T2oClkzfefYEK6WZeA== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-types/link" "^3.5.7" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/listbox@^3.11.3", "@react-aria/listbox@^3.12.1": - version "3.12.1" - resolved "https://registry.yarnpkg.com/@react-aria/listbox/-/listbox-3.12.1.tgz#cc4f0d23630f496273ca5c31b4dfacf6d6f37df1" - integrity sha512-7JiUp0NGykbv/HgSpmTY1wqhuf/RmjFxs1HZcNaTv8A+DlzgJYc7yQqFjP3ZA/z5RvJFuuIxggIYmgIFjaRYdA== - dependencies: - "@react-aria/interactions" "^3.21.3" - "@react-aria/label" "^3.7.8" - "@react-aria/selection" "^3.18.1" - "@react-aria/utils" "^3.24.1" - "@react-stately/collections" "^3.10.7" - "@react-stately/list" "^3.10.5" - "@react-types/listbox" "^3.4.9" - "@react-types/shared" "^3.23.1" +"@react-aria/listbox@^3.11.3", "@react-aria/listbox@^3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@react-aria/listbox/-/listbox-3.13.1.tgz#3a59b2973baebb96f10e784f14c6ddf1312c2196" + integrity sha512-b5Nu+5d5shJbxpy4s6OXvMlMzm+PVbs3L6CtoHlsKe8cAlSWD340vPHCOGYLwZApIBewepOBvRWgeAF8IDI04w== + dependencies: + "@react-aria/interactions" "^3.22.1" + "@react-aria/label" "^3.7.10" + "@react-aria/selection" "^3.19.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/list" "^3.10.7" + "@react-types/listbox" "^3.5.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/live-announcer@^3.3.4": @@ -1295,308 +1266,309 @@ dependencies: "@swc/helpers" "^0.5.0" -"@react-aria/menu@^3.12.0", "@react-aria/menu@^3.14.1": - version "3.14.1" - resolved "https://registry.yarnpkg.com/@react-aria/menu/-/menu-3.14.1.tgz#c9ec25bc374ee9bb02dc3d92d8260df702349133" - integrity sha512-BYliRb38uAzq05UOFcD5XkjA5foQoXRbcH3ZufBsc4kvh79BcP1PMW6KsXKGJ7dC/PJWUwCui6QL1kUg8PqMHA== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/overlays" "^3.22.1" - "@react-aria/selection" "^3.18.1" - "@react-aria/utils" "^3.24.1" - "@react-stately/collections" "^3.10.7" - "@react-stately/menu" "^3.7.1" - "@react-stately/tree" "^3.8.1" - "@react-types/button" "^3.9.4" - "@react-types/menu" "^3.9.9" - "@react-types/shared" "^3.23.1" +"@react-aria/menu@^3.12.0", "@react-aria/menu@^3.15.1": + version "3.15.1" + resolved "https://registry.yarnpkg.com/@react-aria/menu/-/menu-3.15.1.tgz#d44196d268f731c7a1b6b4fb907419a7c3632060" + integrity sha512-ZBTMZiJ17j6t7epcsjd0joAzsMKO31KLJHPtWAEfk1JkBxrMoirISPN8O1CeK/uBX++VaWSrDZfFe1EjrOwKuA== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/overlays" "^3.23.1" + "@react-aria/selection" "^3.19.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/menu" "^3.8.1" + "@react-stately/tree" "^3.8.3" + "@react-types/button" "^3.9.6" + "@react-types/menu" "^3.9.11" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/meter@^3.4.9": - version "3.4.13" - resolved "https://registry.yarnpkg.com/@react-aria/meter/-/meter-3.4.13.tgz#2ccd93d0b8e7d44e371fa39cb3fd2a1ea25f0ab3" - integrity sha512-oG6KvHQM3ri93XkYQkgEaMKSMO9KNDVpcW1MUqFfqyUXHFBRZRrJB4BTXMZ4nyjheFVQjVboU51fRwoLjOzThg== + version "3.4.15" + resolved "https://registry.yarnpkg.com/@react-aria/meter/-/meter-3.4.15.tgz#7a277636f8f03660134296dc067b614b59d52b98" + integrity sha512-OUAzgmfiyEvBF+h9NlG7s8jvrGNTqj/zAWyUWEh5FMEjKFrDfni6awwFoRs164QqmUvRBNC0/eKv3Ghd2GIkRA== dependencies: - "@react-aria/progress" "^3.4.13" - "@react-types/meter" "^3.4.1" - "@react-types/shared" "^3.23.1" + "@react-aria/progress" "^3.4.15" + "@react-types/meter" "^3.4.3" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/numberfield@^3.10.1": - version "3.11.3" - resolved "https://registry.yarnpkg.com/@react-aria/numberfield/-/numberfield-3.11.3.tgz#23b7f2d64131fd8422a8592732bb34cbae5cb26f" - integrity sha512-QQ9ZTzBbRI8d9ksaBWm6YVXbgv+5zzUsdxVxwzJVXLznvivoORB8rpdFJzUEWVCo25lzoBxluCEPYtLOxP1B0w== - dependencies: - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/spinbutton" "^3.6.5" - "@react-aria/textfield" "^3.14.5" - "@react-aria/utils" "^3.24.1" - "@react-stately/form" "^3.0.3" - "@react-stately/numberfield" "^3.9.3" - "@react-types/button" "^3.9.4" - "@react-types/numberfield" "^3.8.3" - "@react-types/shared" "^3.23.1" + version "3.11.5" + resolved "https://registry.yarnpkg.com/@react-aria/numberfield/-/numberfield-3.11.5.tgz#09582fadda67a908c83175949c0f5ebabbbdc1df" + integrity sha512-cfJzU7SWsksKiLjfubSj5lR18ebQ7IbYaMQZbxdpZSPOANHIiktaxjPK4Nz7cqZ+HZ/6tQEirpY0iqpLx35CSw== + dependencies: + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/spinbutton" "^3.6.7" + "@react-aria/textfield" "^3.14.7" + "@react-aria/utils" "^3.25.1" + "@react-stately/form" "^3.0.5" + "@react-stately/numberfield" "^3.9.5" + "@react-types/button" "^3.9.6" + "@react-types/numberfield" "^3.8.5" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/overlays@^3.20.0", "@react-aria/overlays@^3.22.1": - version "3.22.1" - resolved "https://registry.yarnpkg.com/@react-aria/overlays/-/overlays-3.22.1.tgz#7a01673317fa6517bb91b0b7504e303facdc9ccb" - integrity sha512-GHiFMWO4EQ6+j6b5QCnNoOYiyx1Gk8ZiwLzzglCI4q1NY5AG2EAmfU4Z1+Gtrf2S5Y0zHbumC7rs9GnPoGLUYg== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/ssr" "^3.9.4" - "@react-aria/utils" "^3.24.1" - "@react-aria/visually-hidden" "^3.8.12" - "@react-stately/overlays" "^3.6.7" - "@react-types/button" "^3.9.4" - "@react-types/overlays" "^3.8.7" - "@react-types/shared" "^3.23.1" +"@react-aria/overlays@^3.20.0", "@react-aria/overlays@^3.23.1": + version "3.23.1" + resolved "https://registry.yarnpkg.com/@react-aria/overlays/-/overlays-3.23.1.tgz#64214e91251a0c005f93d3c769547cf8f3c8c5dd" + integrity sha512-qNV3pGThvRXjhdHCfqN9Eg4uD+nFm2DoK6d5e9LFd1+xCkKbT88afDBIcLmeG7fgfmukb1sNmzCJQJt8Svk54g== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/ssr" "^3.9.5" + "@react-aria/utils" "^3.25.1" + "@react-aria/visually-hidden" "^3.8.14" + "@react-stately/overlays" "^3.6.9" + "@react-types/button" "^3.9.6" + "@react-types/overlays" "^3.8.9" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/progress@^3.4.13", "@react-aria/progress@^3.4.9": - version "3.4.13" - resolved "https://registry.yarnpkg.com/@react-aria/progress/-/progress-3.4.13.tgz#dc86c98ed0f9a164cf62140e13865235c1991548" - integrity sha512-YBV9bOO5JzKvG8QCI0IAA00o6FczMgIDiK8Q9p5gKorFMatFUdRayxlbIPoYHMi+PguLil0jHgC7eOyaUcrZ0g== +"@react-aria/progress@^3.4.15", "@react-aria/progress@^3.4.9": + version "3.4.15" + resolved "https://registry.yarnpkg.com/@react-aria/progress/-/progress-3.4.15.tgz#20a1d339a34cfc408c40ad74738a4fa978c8e9f5" + integrity sha512-wlx8pgEet3mlq5Skjy7yV1DfQiEg79tZtojpb5YGN2dIAH8sxClrKOSJRVce0fy9IXVCKrQxjQNXPNUIojK5Rg== dependencies: - "@react-aria/i18n" "^3.11.1" - "@react-aria/label" "^3.7.8" - "@react-aria/utils" "^3.24.1" - "@react-types/progress" "^3.5.4" - "@react-types/shared" "^3.23.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/label" "^3.7.10" + "@react-aria/utils" "^3.25.1" + "@react-types/progress" "^3.5.6" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/radio@^3.10.0": - version "3.10.4" - resolved "https://registry.yarnpkg.com/@react-aria/radio/-/radio-3.10.4.tgz#e1b54fa7a9ee3912a5fe170fc752000eef836c06" - integrity sha512-3fmoMcQtCpgjTwJReFjnvIE/C7zOZeCeWUn4JKDqz9s1ILYsC3Rk5zZ4q66tFn6v+IQnecrKT52wH6+hlVLwTA== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/form" "^3.0.5" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/label" "^3.7.8" - "@react-aria/utils" "^3.24.1" - "@react-stately/radio" "^3.10.4" - "@react-types/radio" "^3.8.1" - "@react-types/shared" "^3.23.1" + version "3.10.6" + resolved "https://registry.yarnpkg.com/@react-aria/radio/-/radio-3.10.6.tgz#e5ded29e604866bde49b536c1e146b2812b3cc38" + integrity sha512-Cr7kiTUWw+HOEdFHztqrFlSXvwuzOCTMbwNkziTyc9fualIX6UDilykND2ctfBgkM4qH7SgQt+SxAIwTdevsKg== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/form" "^3.0.7" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/label" "^3.7.10" + "@react-aria/utils" "^3.25.1" + "@react-stately/radio" "^3.10.6" + "@react-types/radio" "^3.8.3" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/searchfield@^3.7.0": - version "3.7.5" - resolved "https://registry.yarnpkg.com/@react-aria/searchfield/-/searchfield-3.7.5.tgz#777a0608988a0f781bd10139104dbb70b4f6049d" - integrity sha512-h1sMUOWjhevaKKUHab/luHbM6yiyeN57L4RxZU0IIc9Ww0h5Rp2GUuKZA3pcdPiExHje0aijcImL3wBHEbKAzw== - dependencies: - "@react-aria/i18n" "^3.11.1" - "@react-aria/textfield" "^3.14.5" - "@react-aria/utils" "^3.24.1" - "@react-stately/searchfield" "^3.5.3" - "@react-types/button" "^3.9.4" - "@react-types/searchfield" "^3.5.5" - "@react-types/shared" "^3.23.1" + version "3.7.7" + resolved "https://registry.yarnpkg.com/@react-aria/searchfield/-/searchfield-3.7.7.tgz#4cfb5497f3192ea4a84653466dd862e1a23e40a7" + integrity sha512-2f087PCR8X5LYyLnvjCIOV27xjjTCkDFPnQaC7XSPCfzDYGM8utCR56JfZMqHnjcMnVNoiEg7EjSBBrh7I2bnQ== + dependencies: + "@react-aria/i18n" "^3.12.1" + "@react-aria/textfield" "^3.14.7" + "@react-aria/utils" "^3.25.1" + "@react-stately/searchfield" "^3.5.5" + "@react-types/button" "^3.9.6" + "@react-types/searchfield" "^3.5.7" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/select@^3.14.1": - version "3.14.5" - resolved "https://registry.yarnpkg.com/@react-aria/select/-/select-3.14.5.tgz#edbea4fa012654d6feef285e27d7d67fb03b017f" - integrity sha512-s8jixBuTUNdKWRHe2tIJqp55ORHeUObGMw1s7PQRRVrrHPdNSYseAOI9B2W7qpl3hKhvjJg40UW+45mcb1WKbw== - dependencies: - "@react-aria/form" "^3.0.5" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/label" "^3.7.8" - "@react-aria/listbox" "^3.12.1" - "@react-aria/menu" "^3.14.1" - "@react-aria/selection" "^3.18.1" - "@react-aria/utils" "^3.24.1" - "@react-aria/visually-hidden" "^3.8.12" - "@react-stately/select" "^3.6.4" - "@react-types/button" "^3.9.4" - "@react-types/select" "^3.9.4" - "@react-types/shared" "^3.23.1" + version "3.14.7" + resolved "https://registry.yarnpkg.com/@react-aria/select/-/select-3.14.7.tgz#8019c2b0b81360b17001798cf7db6d8b2c0001e7" + integrity sha512-qZy5oX6P8SGrdv4bHb8iVMIVv+vLuo7UwOJtsQ1FUORIsZmBEz0RyfgYdzlueMcZNoQ9JgLYtrK2e0h6AmJOlg== + dependencies: + "@react-aria/form" "^3.0.7" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/label" "^3.7.10" + "@react-aria/listbox" "^3.13.1" + "@react-aria/menu" "^3.15.1" + "@react-aria/selection" "^3.19.1" + "@react-aria/utils" "^3.25.1" + "@react-aria/visually-hidden" "^3.8.14" + "@react-stately/select" "^3.6.6" + "@react-types/button" "^3.9.6" + "@react-types/select" "^3.9.6" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/selection@^3.17.3", "@react-aria/selection@^3.18.1": - version "3.18.1" - resolved "https://registry.yarnpkg.com/@react-aria/selection/-/selection-3.18.1.tgz#fd6a10a86be187ac2a591cbbc1f41c3aa0c09f7f" - integrity sha512-GSqN2jX6lh7v+ldqhVjAXDcrWS3N4IsKXxO6L6Ygsye86Q9q9Mq9twWDWWu5IjHD6LoVZLUBCMO+ENGbOkyqeQ== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-stately/selection" "^3.15.1" - "@react-types/shared" "^3.23.1" +"@react-aria/selection@^3.17.3", "@react-aria/selection@^3.19.1": + version "3.19.1" + resolved "https://registry.yarnpkg.com/@react-aria/selection/-/selection-3.19.1.tgz#3fbbac4d3f8dc1711cb6523bef0d9e0c47a514cb" + integrity sha512-mbExvq2Omi60sTWFGjwcNz1ja2P8VDsxWAqSypHRTyqXhtgqbv8V/v8Gp+7BmVPH1YHcbhztl6rvUZTDOSszzw== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/selection" "^3.16.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/separator@^3.3.9": - version "3.3.13" - resolved "https://registry.yarnpkg.com/@react-aria/separator/-/separator-3.3.13.tgz#12c8127bb672e53246f63c50f3b7a36960c8bb63" - integrity sha512-hofA6JCPnAOqSE9vxnq7Dkazr7Kb2A0I5sR16fOG7ddjYRc/YEY5Nv7MWfKUGU0kNFHkgNjsDAILERtLechzeA== + version "3.4.1" + resolved "https://registry.yarnpkg.com/@react-aria/separator/-/separator-3.4.1.tgz#c8c643f9506b569ba2af7185761588a87ca73949" + integrity sha512-bZ+GQ936Y+WXAtsQjJdEMgYeqmqjhU90+wOlRGjmGdwf+/ht2yzBpeRuHEYUbE6F0iis/YoVc+b8ppAtPna/kA== dependencies: - "@react-aria/utils" "^3.24.1" - "@react-types/shared" "^3.23.1" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/slider@^3.7.4": - version "3.7.8" - resolved "https://registry.yarnpkg.com/@react-aria/slider/-/slider-3.7.8.tgz#6f2109527e0ebfaa1aaf46fce2460549d5550e1b" - integrity sha512-MYvPcM0K8jxEJJicUK2+WxUkBIM/mquBxOTOSSIL3CszA80nXIGVnLlCUnQV3LOUzpWtabbWaZokSPtGgOgQOw== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/label" "^3.7.8" - "@react-aria/utils" "^3.24.1" - "@react-stately/slider" "^3.5.4" - "@react-types/shared" "^3.23.1" - "@react-types/slider" "^3.7.3" + version "3.7.10" + resolved "https://registry.yarnpkg.com/@react-aria/slider/-/slider-3.7.10.tgz#09474eb9f6d041bf1651dc663695843b34652061" + integrity sha512-QmBn87sDkncS/uhcrH0MxUN7bcEo8cHYcWk+gk7mibdIpyxyVDPKh7v7ZsosmAJLzjS0yb2ec1/Q5Oldfg1k/A== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/label" "^3.7.10" + "@react-aria/utils" "^3.25.1" + "@react-stately/slider" "^3.5.6" + "@react-types/shared" "^3.24.1" + "@react-types/slider" "^3.7.5" "@swc/helpers" "^0.5.0" -"@react-aria/spinbutton@^3.6.5": - version "3.6.5" - resolved "https://registry.yarnpkg.com/@react-aria/spinbutton/-/spinbutton-3.6.5.tgz#77a69c26dfc74381bc1f112835eb59c572d659dd" - integrity sha512-0aACBarF/Xr/7ixzjVBTQ0NBwwwsoGkf5v6AVFVMTC0uYMXHTALvRs+ULHjHMa5e/cX/aPlEvaVT7jfSs+Xy9Q== +"@react-aria/spinbutton@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@react-aria/spinbutton/-/spinbutton-3.6.7.tgz#4c549039542e1215f68c791d5bb2d8be61b10979" + integrity sha512-OCimp4yXoFIgh6WAMOls5DDDRDRO75ZFic3YA6wLWTRNHxo1Lj8S90i1A6pakY6bi4hdBCKmj4DnFSNKAw1iWg== dependencies: - "@react-aria/i18n" "^3.11.1" + "@react-aria/i18n" "^3.12.1" "@react-aria/live-announcer" "^3.3.4" - "@react-aria/utils" "^3.24.1" - "@react-types/button" "^3.9.4" - "@react-types/shared" "^3.23.1" + "@react-aria/utils" "^3.25.1" + "@react-types/button" "^3.9.6" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/ssr@^3.9.1", "@react-aria/ssr@^3.9.4": - version "3.9.4" - resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.4.tgz#9da8b10342c156e816dbfa4c9e713b21f274d7ab" - integrity sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ== +"@react-aria/ssr@^3.9.1", "@react-aria/ssr@^3.9.5": + version "3.9.5" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.5.tgz#775d84f51f90934ff51ae74eeba3728daac1a381" + integrity sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ== dependencies: "@swc/helpers" "^0.5.0" "@react-aria/switch@^3.6.0": - version "3.6.4" - resolved "https://registry.yarnpkg.com/@react-aria/switch/-/switch-3.6.4.tgz#6dba901414785de23ee2f4873ea5e23973fdf58d" - integrity sha512-2nVqz4ZuJyof47IpGSt3oZRmp+EdS8wzeDYgf42WHQXrx4uEOk1mdLJ20+NnsYhj/2NHZsvXVrjBeKMjlMs+0w== + version "3.6.6" + resolved "https://registry.yarnpkg.com/@react-aria/switch/-/switch-3.6.6.tgz#da1ec971ad877ea9551f15a7d23d990075c62e34" + integrity sha512-+dZOX1utODlx5dC90DtwnXd9nvln9HxMffBj/gmMT1/cD/RmXfjvymfjTsTMwvHhqCew9yfpvod0ZWwj3BkLGw== dependencies: - "@react-aria/toggle" "^3.10.4" - "@react-stately/toggle" "^3.7.4" - "@react-types/switch" "^3.5.3" + "@react-aria/toggle" "^3.10.6" + "@react-stately/toggle" "^3.7.6" + "@react-types/shared" "^3.24.1" + "@react-types/switch" "^3.5.5" "@swc/helpers" "^0.5.0" "@react-aria/table@^3.13.3": - version "3.14.1" - resolved "https://registry.yarnpkg.com/@react-aria/table/-/table-3.14.1.tgz#6316349e17fe6adfe9132aab75ce72c4a44c028f" - integrity sha512-WaPgQe4zQF5OaluO5rm+Y2nEoFR63vsLd4BT4yjK1uaFhKhDY2Zk+1SCVQvBLLKS4WK9dhP05nrNzT0vp/ZPOw== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/grid" "^3.9.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" + version "3.15.1" + resolved "https://registry.yarnpkg.com/@react-aria/table/-/table-3.15.1.tgz#908175bf2ad064fd0c7862ff49297b3c036dc920" + integrity sha512-jVDLxp6Y/9M6y45c1I6u6msJ9dBg2I7Cu/FlSaK6HthTpN23UXuGw1oWuAjbfqi31nVXHWBwjCZkGKTdMjLf5A== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/grid" "^3.10.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" "@react-aria/live-announcer" "^3.3.4" - "@react-aria/utils" "^3.24.1" - "@react-aria/visually-hidden" "^3.8.12" - "@react-stately/collections" "^3.10.7" + "@react-aria/utils" "^3.25.1" + "@react-aria/visually-hidden" "^3.8.14" + "@react-stately/collections" "^3.10.9" "@react-stately/flags" "^3.0.3" - "@react-stately/table" "^3.11.8" - "@react-stately/virtualizer" "^3.7.1" - "@react-types/checkbox" "^3.8.1" - "@react-types/grid" "^3.2.6" - "@react-types/shared" "^3.23.1" - "@react-types/table" "^3.9.5" + "@react-stately/table" "^3.12.1" + "@react-types/checkbox" "^3.8.3" + "@react-types/grid" "^3.2.8" + "@react-types/shared" "^3.24.1" + "@react-types/table" "^3.10.1" "@swc/helpers" "^0.5.0" "@react-aria/tabs@^3.8.3": - version "3.9.1" - resolved "https://registry.yarnpkg.com/@react-aria/tabs/-/tabs-3.9.1.tgz#3cfb44648de1f896499d210b80deb1ead8ec4295" - integrity sha512-S5v/0sRcOaSXaJYZuuy1ZVzYc7JD4sDyseG1133GjyuNjJOFHgoWMb+b4uxNIJbZxnLgynn/ZDBZSO+qU+fIxw== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/selection" "^3.18.1" - "@react-aria/utils" "^3.24.1" - "@react-stately/tabs" "^3.6.6" - "@react-types/shared" "^3.23.1" - "@react-types/tabs" "^3.3.7" + version "3.9.3" + resolved "https://registry.yarnpkg.com/@react-aria/tabs/-/tabs-3.9.3.tgz#77e9216ba93dbd5f1af17dca00a2229de0285766" + integrity sha512-J1KOCdx4eSyMMeNCvO8BIz8E8xez12B+cYbM4BbJzWlcfMboGYUnM0lvI8QSpFPa/H9LkAhp7BJnl9IZeIBzoA== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/selection" "^3.19.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/tabs" "^3.6.8" + "@react-types/shared" "^3.24.1" + "@react-types/tabs" "^3.3.9" "@swc/helpers" "^0.5.0" "@react-aria/tag@^3.3.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@react-aria/tag/-/tag-3.4.1.tgz#28206a37db7f4e5cb19469b7851368bb5128fd0c" - integrity sha512-gcIGPYZ2OBwMT4IHnlczEezKlxr0KRPL/mSfm2Q91GE027ZGOJnqusH9az6DX1qxrQx8x3vRdqYT2KmuefkrBQ== - dependencies: - "@react-aria/gridlist" "^3.8.1" - "@react-aria/i18n" "^3.11.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/label" "^3.7.8" - "@react-aria/selection" "^3.18.1" - "@react-aria/utils" "^3.24.1" - "@react-stately/list" "^3.10.5" - "@react-types/button" "^3.9.4" - "@react-types/shared" "^3.23.1" + version "3.4.3" + resolved "https://registry.yarnpkg.com/@react-aria/tag/-/tag-3.4.3.tgz#7d59f4a863ca7fe75bd6f3e21bb328f4c684627d" + integrity sha512-BqXKazX9YHvt6+qzGTu770V0FqGVefzz03hmnV2IVb+zYchXBv3WYbWVy46s/D5zTePOAXdpitQHxqy5rh+hgw== + dependencies: + "@react-aria/gridlist" "^3.9.1" + "@react-aria/i18n" "^3.12.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/label" "^3.7.10" + "@react-aria/selection" "^3.19.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/list" "^3.10.7" + "@react-types/button" "^3.9.6" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-aria/textfield@^3.14.0", "@react-aria/textfield@^3.14.5": - version "3.14.5" - resolved "https://registry.yarnpkg.com/@react-aria/textfield/-/textfield-3.14.5.tgz#afb46b4af019dc88fc7f77396cea5ec0c9701f01" - integrity sha512-hj7H+66BjB1iTKKaFXwSZBZg88YT+wZboEXZ0DNdQB2ytzoz/g045wBItUuNi4ZjXI3P+0AOZznVMYadWBAmiA== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/form" "^3.0.5" - "@react-aria/label" "^3.7.8" - "@react-aria/utils" "^3.24.1" - "@react-stately/form" "^3.0.3" - "@react-stately/utils" "^3.10.1" - "@react-types/shared" "^3.23.1" - "@react-types/textfield" "^3.9.3" +"@react-aria/textfield@^3.14.0", "@react-aria/textfield@^3.14.7": + version "3.14.7" + resolved "https://registry.yarnpkg.com/@react-aria/textfield/-/textfield-3.14.7.tgz#368c2da2e0596a1ebadc6d21df5da41f2c619feb" + integrity sha512-1cWCG6vkjlwJuRTXKbKl9P0Q/0Li5pnMafZqDDWfDOlkS5dFGxYG6QFfoaYp7N6XMoNkXiculnCssfrQ+8hWgA== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/form" "^3.0.7" + "@react-aria/label" "^3.7.10" + "@react-aria/utils" "^3.25.1" + "@react-stately/form" "^3.0.5" + "@react-stately/utils" "^3.10.2" + "@react-types/shared" "^3.24.1" + "@react-types/textfield" "^3.9.5" "@swc/helpers" "^0.5.0" -"@react-aria/toggle@^3.10.4": - version "3.10.4" - resolved "https://registry.yarnpkg.com/@react-aria/toggle/-/toggle-3.10.4.tgz#a3673ead72c389381c6217b5bed7269300351a8e" - integrity sha512-bRk+CdB8QzrSyGNjENXiTWxfzYKRw753iwQXsEAU7agPCUdB8cZJyrhbaUoD0rwczzTp2zDbZ9rRbUPdsBE2YQ== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-stately/toggle" "^3.7.4" - "@react-types/checkbox" "^3.8.1" +"@react-aria/toggle@^3.10.6": + version "3.10.6" + resolved "https://registry.yarnpkg.com/@react-aria/toggle/-/toggle-3.10.6.tgz#c0afba7bdf5263869b7169d97651b1ba746f84f9" + integrity sha512-AGlbtB1b8grrtjbiW5Au0LKYzxR83RHbHhaUkFwajyYRGyuEzr3Y03OiveoPB+DayA8Gz3H1ZVmW++8JZQOWHw== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/toggle" "^3.7.6" + "@react-types/checkbox" "^3.8.3" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-aria/tooltip@^3.7.0": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@react-aria/tooltip/-/tooltip-3.7.4.tgz#0efe8b4cc543a39395e99861ad6f0c64cd746026" - integrity sha512-+XRx4HlLYqWY3fB8Z60bQi/rbWDIGlFUtXYbtoa1J+EyRWfhpvsYImP8qeeNO/vgjUtDy1j9oKa8p6App9mBMQ== - dependencies: - "@react-aria/focus" "^3.17.1" - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-stately/tooltip" "^3.4.9" - "@react-types/shared" "^3.23.1" - "@react-types/tooltip" "^3.4.9" + version "3.7.6" + resolved "https://registry.yarnpkg.com/@react-aria/tooltip/-/tooltip-3.7.6.tgz#1e3a715a2d0a977bd4481d4ae6cc69de16e4f5a3" + integrity sha512-JvRAMTcMju/KBOtISjVKKtIDzG3J1r6xK+mZTvu6ArM7DdeMBM5A8Lwk0bJ8dhr+YybiM9rR3hoZv3/E7IIYVw== + dependencies: + "@react-aria/focus" "^3.18.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-stately/tooltip" "^3.4.11" + "@react-types/shared" "^3.24.1" + "@react-types/tooltip" "^3.4.11" "@swc/helpers" "^0.5.0" -"@react-aria/utils@^3.23.0", "@react-aria/utils@^3.24.1": - version "3.24.1" - resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.24.1.tgz#9d16023f07c23c41793c9030a9bd203a9c8cf0a7" - integrity sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q== +"@react-aria/utils@^3.23.0", "@react-aria/utils@^3.25.1": + version "3.25.1" + resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.25.1.tgz#f6530ce47aa28617924cc6868b4cf1c113a909c5" + integrity sha512-5Uj864e7T5+yj78ZfLnfHqmypLiqW2mN+nsdslog2z5ssunTqjolVeM15ootXskjISlZ7MojLpq97kIC4nlnAw== dependencies: - "@react-aria/ssr" "^3.9.4" - "@react-stately/utils" "^3.10.1" - "@react-types/shared" "^3.23.1" + "@react-aria/ssr" "^3.9.5" + "@react-stately/utils" "^3.10.2" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" clsx "^2.0.0" -"@react-aria/visually-hidden@^3.8.12", "@react-aria/visually-hidden@^3.8.8": - version "3.8.12" - resolved "https://registry.yarnpkg.com/@react-aria/visually-hidden/-/visually-hidden-3.8.12.tgz#89388b4773b8fbea4b5f9682e807510c14218c93" - integrity sha512-Bawm+2Cmw3Xrlr7ARzl2RLtKh0lNUdJ0eNqzWcyx4c0VHUAWtThmH5l+HRqFUGzzutFZVo89SAy40BAbd0gjVw== +"@react-aria/visually-hidden@^3.8.14", "@react-aria/visually-hidden@^3.8.8": + version "3.8.14" + resolved "https://registry.yarnpkg.com/@react-aria/visually-hidden/-/visually-hidden-3.8.14.tgz#ccfff3c5220b833ef811574b70751bac303b29c5" + integrity sha512-DV3yagbAgO4ywQTq6D/AxcIaTC8c77r/SxlIMhQBMQ6vScJWTCh6zFG55wmLe3NKqvRrowv1OstlmYfZQ4v/XA== dependencies: - "@react-aria/interactions" "^3.21.3" - "@react-aria/utils" "^3.24.1" - "@react-types/shared" "^3.23.1" + "@react-aria/interactions" "^3.22.1" + "@react-aria/utils" "^3.25.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-oauth/google@^0.12.1": @@ -1604,80 +1576,80 @@ resolved "https://registry.yarnpkg.com/@react-oauth/google/-/google-0.12.1.tgz#b76432c3a525e9afe076f787d2ded003fcc1bee9" integrity sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg== -"@react-stately/calendar@^3.4.3", "@react-stately/calendar@^3.5.1": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@react-stately/calendar/-/calendar-3.5.1.tgz#3e865d69675ba78f56e7abfadff0ef667f438a69" - integrity sha512-7l7QhqGUJ5AzWHfvZzbTe3J4t72Ht5BmhW4hlVI7flQXtfrmYkVtl3ZdytEZkkHmWGYZRW9b4IQTQGZxhtlElA== +"@react-stately/calendar@^3.4.3", "@react-stately/calendar@^3.5.3": + version "3.5.3" + resolved "https://registry.yarnpkg.com/@react-stately/calendar/-/calendar-3.5.3.tgz#7bfbeceac85a273ee71f80312c6c98939623148f" + integrity sha512-SRwsgszyc9FNcvkjqBe81e/tnjKpRqH+yTYpG0uI9NR1HfyddmhR3Y7QilWPcqQkq4SQb7pL68SkTPH2dX2dng== dependencies: - "@internationalized/date" "^3.5.4" - "@react-stately/utils" "^3.10.1" - "@react-types/calendar" "^3.4.6" - "@react-types/shared" "^3.23.1" + "@internationalized/date" "^3.5.5" + "@react-stately/utils" "^3.10.2" + "@react-types/calendar" "^3.4.8" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/checkbox@^3.6.1", "@react-stately/checkbox@^3.6.5": - version "3.6.5" - resolved "https://registry.yarnpkg.com/@react-stately/checkbox/-/checkbox-3.6.5.tgz#0566eae3ba3a84af6f29526b3feaf124d3c3a66b" - integrity sha512-IXV3f9k+LtmfQLE+DKIN41Q5QB/YBLDCB1YVx5PEdRp52S9+EACD5683rjVm8NVRDwjMi2SP6RnFRk7fVb5Azg== +"@react-stately/checkbox@^3.6.1", "@react-stately/checkbox@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@react-stately/checkbox/-/checkbox-3.6.7.tgz#d622c3cac9c1ad5e09746d354fe82fc37259659f" + integrity sha512-ZOaBNXXazpwkuKj5hk6FtGbXO7HoKEGXvf3p7FcHcIHyiEJ65GBvC7e7HwMc3jYxlBwtbebSpEcf3oFqI5dl3A== dependencies: - "@react-stately/form" "^3.0.3" - "@react-stately/utils" "^3.10.1" - "@react-types/checkbox" "^3.8.1" - "@react-types/shared" "^3.23.1" + "@react-stately/form" "^3.0.5" + "@react-stately/utils" "^3.10.2" + "@react-types/checkbox" "^3.8.3" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/collections@^3.10.4", "@react-stately/collections@^3.10.7": - version "3.10.7" - resolved "https://registry.yarnpkg.com/@react-stately/collections/-/collections-3.10.7.tgz#b1add46cb8e2f2a0d33938ef1b232fb2d0fd11eb" - integrity sha512-KRo5O2MWVL8n3aiqb+XR3vP6akmHLhLWYZEmPKjIv0ghQaEebBTrN3wiEjtd6dzllv0QqcWvDLM1LntNfJ2TsA== +"@react-stately/collections@^3.10.4", "@react-stately/collections@^3.10.9": + version "3.10.9" + resolved "https://registry.yarnpkg.com/@react-stately/collections/-/collections-3.10.9.tgz#cdf23d46de30741e2f836b96d439cf095acf4d84" + integrity sha512-plyrng6hOQMG8LrjArMA6ts/DgWyXln3g90/hFNbqe/hdVYF53sDVsj8Jb+5LtoYTpiAlV6eOvy1XR0vPZUf8w== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/combobox@^3.8.1", "@react-stately/combobox@^3.8.4": - version "3.8.4" - resolved "https://registry.yarnpkg.com/@react-stately/combobox/-/combobox-3.8.4.tgz#6540ec4d53af210e6f3a769ba3f2615a55380984" - integrity sha512-iLVGvKRRz0TeJXZhZyK783hveHpYA6xovOSdzSD+WGYpiPXo1QrcrNoH3AE0Z2sHtorU+8nc0j58vh5PB+m2AA== - dependencies: - "@react-stately/collections" "^3.10.7" - "@react-stately/form" "^3.0.3" - "@react-stately/list" "^3.10.5" - "@react-stately/overlays" "^3.6.7" - "@react-stately/select" "^3.6.4" - "@react-stately/utils" "^3.10.1" - "@react-types/combobox" "^3.11.1" - "@react-types/shared" "^3.23.1" +"@react-stately/combobox@^3.8.1", "@react-stately/combobox@^3.9.1": + version "3.9.1" + resolved "https://registry.yarnpkg.com/@react-stately/combobox/-/combobox-3.9.1.tgz#2593c050b65c6c90b806fe17d5526b54a21f53bc" + integrity sha512-jmeKUKs0jK18NwDAlpu79ATufgxrc6Sn3ZMmI8KPVQ5sdPTjNlnDx6gTFyOOIa87axf/c6WYU7v3jxmcp+RDdg== + dependencies: + "@react-stately/collections" "^3.10.9" + "@react-stately/form" "^3.0.5" + "@react-stately/list" "^3.10.7" + "@react-stately/overlays" "^3.6.9" + "@react-stately/select" "^3.6.6" + "@react-stately/utils" "^3.10.2" + "@react-types/combobox" "^3.12.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-stately/data@^3.11.0": - version "3.11.4" - resolved "https://registry.yarnpkg.com/@react-stately/data/-/data-3.11.4.tgz#a6168c292830af0e8d1dff154724d7ea253c7407" - integrity sha512-PbnUQxeE6AznSuEWYnRmrYQ9t5z1Asx98Jtrl96EeA6Iapt9kOjTN9ySqCxtPxMKleb1NIqG3+uHU3veIqmLsg== + version "3.11.6" + resolved "https://registry.yarnpkg.com/@react-stately/data/-/data-3.11.6.tgz#bf4e5216cac3f1e302924b1e5369519a27b76146" + integrity sha512-S8q1Ejuhijl8SnyVOdDNFrMrWWnLk/Oh1ZT3KHSbTdpfMRtvhi5HukoiP06jlzz75phnpSPQL40npDtUB/kk3Q== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/datepicker@^3.9.1", "@react-stately/datepicker@^3.9.4": - version "3.9.4" - resolved "https://registry.yarnpkg.com/@react-stately/datepicker/-/datepicker-3.9.4.tgz#c9862cdc09da72760ed3005169223c7743b44b2d" - integrity sha512-yBdX01jn6gq4NIVvHIqdjBUPo+WN8Bujc4OnPw+ZnfA4jI0eIgq04pfZ84cp1LVXW0IB0VaCu1AlQ/kvtZjfGA== +"@react-stately/datepicker@^3.10.1", "@react-stately/datepicker@^3.9.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@react-stately/datepicker/-/datepicker-3.10.1.tgz#f4926f9b881fdb925220b9d78f6a64646f8aa4b5" + integrity sha512-KXr5cxLOLUYBf3wlDSKhvshsKOWpdV2flhS075V6dgC/EPBh7igBZGUXJ9AZzndT7Hx1w8v/ul6CIffxEJz1Nw== dependencies: - "@internationalized/date" "^3.5.4" + "@internationalized/date" "^3.5.5" "@internationalized/string" "^3.2.3" - "@react-stately/form" "^3.0.3" - "@react-stately/overlays" "^3.6.7" - "@react-stately/utils" "^3.10.1" - "@react-types/datepicker" "^3.7.4" - "@react-types/shared" "^3.23.1" + "@react-stately/form" "^3.0.5" + "@react-stately/overlays" "^3.6.9" + "@react-stately/utils" "^3.10.2" + "@react-types/datepicker" "^3.8.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/dnd@^3.2.7", "@react-stately/dnd@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@react-stately/dnd/-/dnd-3.3.1.tgz#e4f9d5c58dd2ed4869c8d458c8fdc23e269bf5d3" - integrity sha512-I/Ci5xB8hSgAXzoWYWScfMM9UK1MX/eTlARBhiSlfudewweOtNJAI+cXJgU7uiUnGjh4B4v3qDBtlAH1dWDCsw== +"@react-stately/dnd@^3.2.7", "@react-stately/dnd@^3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@react-stately/dnd/-/dnd-3.4.1.tgz#a91be9a01d8788970d4be1313a2a556bbb12c96e" + integrity sha512-EXPW1vKx3vNpMaXOpPKTOU1T4S+jqjllGFDyWD659Ql0lL9SQ5Y4IU/KmIK3T3yKkjps9xrMmCjLAkb75PH5zg== dependencies: - "@react-stately/selection" "^3.15.1" - "@react-types/shared" "^3.23.1" + "@react-stately/selection" "^3.16.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" "@react-stately/flags@^3.0.3": @@ -1687,380 +1659,433 @@ dependencies: "@swc/helpers" "^0.5.0" -"@react-stately/form@^3.0.0", "@react-stately/form@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@react-stately/form/-/form-3.0.3.tgz#9894f9b219cc4cfbbde814d43d3f897bc43b25b3" - integrity sha512-92YYBvlHEWUGUpXgIaQ48J50jU9XrxfjYIN8BTvvhBHdD63oWgm8DzQnyT/NIAMzdLnhkg7vP+fjG8LjHeyIAg== +"@react-stately/form@^3.0.0", "@react-stately/form@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@react-stately/form/-/form-3.0.5.tgz#653f603ddd8b74a8a126b426ebc17abd112b672b" + integrity sha512-J3plwJ63HQz109OdmaTqTA8Qhvl3gcYYK7DtgKyNP6mc/Me2Q4tl2avkWoA+22NRuv5m+J8TpBk4AVHUEOwqeQ== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/grid@^3.8.7": - version "3.8.7" - resolved "https://registry.yarnpkg.com/@react-stately/grid/-/grid-3.8.7.tgz#5c8aa22c83c0cb1146edad716c218739768e72ca" - integrity sha512-he3TXCLAhF5C5z1/G4ySzcwyt7PEiWcVIupxebJQqRyFrNWemSuv+7tolnStmG8maMVIyV3P/3j4eRBbdSlOIg== +"@react-stately/grid@^3.9.1": + version "3.9.1" + resolved "https://registry.yarnpkg.com/@react-stately/grid/-/grid-3.9.1.tgz#27b4a35e344891e3cf69ef7a3d7dc56979f117ec" + integrity sha512-LSVIcXO/cqwG0IgDSk2juDbpARBS1IzGnsTp/8vSOejMxq5MXrwxL5hUcqNczL8Ss6aLpELm42tCS0kPm3cMKw== dependencies: - "@react-stately/collections" "^3.10.7" - "@react-stately/selection" "^3.15.1" - "@react-types/grid" "^3.2.6" - "@react-types/shared" "^3.23.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/selection" "^3.16.1" + "@react-types/grid" "^3.2.8" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/list@^3.10.2", "@react-stately/list@^3.10.5": - version "3.10.5" - resolved "https://registry.yarnpkg.com/@react-stately/list/-/list-3.10.5.tgz#b68ebd595b5f4a51d6719cdcabd34f0780e95b85" - integrity sha512-fV9plO+6QDHiewsYIhboxcDhF17GO95xepC5ki0bKXo44gr14g/LSo/BMmsaMnV+1BuGdBunB05bO4QOIaigXA== +"@react-stately/list@^3.10.2", "@react-stately/list@^3.10.7": + version "3.10.7" + resolved "https://registry.yarnpkg.com/@react-stately/list/-/list-3.10.7.tgz#cff58ca38103cdb770d3d9809273a77e6606759c" + integrity sha512-W5PG7uG5GQV2Q59vXJE7QLKHZIoUNEx+JmHrBUCMKUgyngSpKIIEDR/R/C1b6ZJ9jMqqZA68Zlnd5iK1/mBi1A== dependencies: - "@react-stately/collections" "^3.10.7" - "@react-stately/selection" "^3.15.1" - "@react-stately/utils" "^3.10.1" - "@react-types/shared" "^3.23.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/selection" "^3.16.1" + "@react-stately/utils" "^3.10.2" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/menu@^3.6.0", "@react-stately/menu@^3.7.1": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@react-stately/menu/-/menu-3.7.1.tgz#af3c259c519de036d9e80d7d8370278c7b042c6a" - integrity sha512-mX1w9HHzt+xal1WIT2xGrTQsoLvDwuB2R1Er1MBABs//MsJzccycatcgV/J/28m6tO5M9iuFQQvLV+i1dCtodg== +"@react-stately/menu@^3.6.0", "@react-stately/menu@^3.8.1": + version "3.8.1" + resolved "https://registry.yarnpkg.com/@react-stately/menu/-/menu-3.8.1.tgz#f7c91aae721eb2678673ecb5161e5803fb55ce3a" + integrity sha512-HzAANHg+QUpyRok0CBIL/5qb+4TARteP0q9av2tKnQWPG91iJw84phJDJrmmY55uFFax4fxBgDM9dy1t12iKgQ== dependencies: - "@react-stately/overlays" "^3.6.7" - "@react-types/menu" "^3.9.9" - "@react-types/shared" "^3.23.1" + "@react-stately/overlays" "^3.6.9" + "@react-types/menu" "^3.9.11" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/numberfield@^3.8.0", "@react-stately/numberfield@^3.9.3": - version "3.9.3" - resolved "https://registry.yarnpkg.com/@react-stately/numberfield/-/numberfield-3.9.3.tgz#b61429835b949aa6ad5e2fb6e6699ee78ce7bcd5" - integrity sha512-UlPTLSabhLEuHtgzM0PgfhtEaHy3yttbzcRb8yHNvGo4KbCHeHpTHd3QghKfTFm024Mug7+mVlWCmMtW0f5ttg== +"@react-stately/numberfield@^3.8.0", "@react-stately/numberfield@^3.9.5": + version "3.9.5" + resolved "https://registry.yarnpkg.com/@react-stately/numberfield/-/numberfield-3.9.5.tgz#0a6869ead08c387cc6becefa06fc68353c5dc9b7" + integrity sha512-aWilyzrZOvkgntcXd6Kl+t1QiCbnajUCN8yll6/saByKpfuOf1k6AGYNQBJ0CO/5HyffPPdbFs+45sj4e3cdjA== dependencies: "@internationalized/number" "^3.5.3" - "@react-stately/form" "^3.0.3" - "@react-stately/utils" "^3.10.1" - "@react-types/numberfield" "^3.8.3" + "@react-stately/form" "^3.0.5" + "@react-stately/utils" "^3.10.2" + "@react-types/numberfield" "^3.8.5" "@swc/helpers" "^0.5.0" -"@react-stately/overlays@^3.6.4", "@react-stately/overlays@^3.6.7": - version "3.6.7" - resolved "https://registry.yarnpkg.com/@react-stately/overlays/-/overlays-3.6.7.tgz#d4aa1b709e6e72306c33308bb031466730dd0480" - integrity sha512-6zp8v/iNUm6YQap0loaFx6PlvN8C0DgWHNlrlzMtMmNuvjhjR0wYXVaTfNoUZBWj25tlDM81ukXOjpRXg9rLrw== +"@react-stately/overlays@^3.6.4", "@react-stately/overlays@^3.6.9": + version "3.6.9" + resolved "https://registry.yarnpkg.com/@react-stately/overlays/-/overlays-3.6.9.tgz#3eaea249e35f424c4354fbee75c7c6767776a877" + integrity sha512-4chfyzKw7P2UEainm0yzjUgYwG1ovBejN88eTrn+O62x5huuMCwe0cbMxmYh4y7IhRFSee3jIJd0SP0u/+i39w== dependencies: - "@react-stately/utils" "^3.10.1" - "@react-types/overlays" "^3.8.7" + "@react-stately/utils" "^3.10.2" + "@react-types/overlays" "^3.8.9" "@swc/helpers" "^0.5.0" -"@react-stately/radio@^3.10.1", "@react-stately/radio@^3.10.4": - version "3.10.4" - resolved "https://registry.yarnpkg.com/@react-stately/radio/-/radio-3.10.4.tgz#499ef1e781a47b5ac89b3af571fc61054327f55b" - integrity sha512-kCIc7tAl4L7Hu4Wt9l2jaa+MzYmAJm0qmC8G8yPMbExpWbLRu6J8Un80GZu+JxvzgDlqDyrVvyv9zFifwH/NkQ== +"@react-stately/radio@^3.10.1", "@react-stately/radio@^3.10.6": + version "3.10.6" + resolved "https://registry.yarnpkg.com/@react-stately/radio/-/radio-3.10.6.tgz#2513757b5aa49aed5f0b95d3e3a4460dde2635c5" + integrity sha512-wiJuUUQ6LuEv0J1DQtkC0+Sed7tO6y3sIPeB+5uIxIIsUpxvNlDcqr+JOkrQm7gZmkmvcfotb5Gv5PqaIl1zKA== dependencies: - "@react-stately/form" "^3.0.3" - "@react-stately/utils" "^3.10.1" - "@react-types/radio" "^3.8.1" - "@react-types/shared" "^3.23.1" + "@react-stately/form" "^3.0.5" + "@react-stately/utils" "^3.10.2" + "@react-types/radio" "^3.8.3" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/searchfield@^3.5.0", "@react-stately/searchfield@^3.5.3": - version "3.5.3" - resolved "https://registry.yarnpkg.com/@react-stately/searchfield/-/searchfield-3.5.3.tgz#423056e1260dd0332c1d2454a91c67e338964c40" - integrity sha512-H0OvlgwPIFdc471ypw79MDjz3WXaVq9+THaY6JM4DIohEJNN5Dwei7O9g6r6m/GqPXJIn5TT3b74kJ2Osc00YQ== +"@react-stately/searchfield@^3.5.0", "@react-stately/searchfield@^3.5.5": + version "3.5.5" + resolved "https://registry.yarnpkg.com/@react-stately/searchfield/-/searchfield-3.5.5.tgz#bcd6da5fd077baaa8ba35b5257f370f4ba9168fa" + integrity sha512-rKWIVNbxft5eGGxQ4CtcTKGXm2B1AuYSg6kLRQLq+VYspPNq3wfeMtVBeIdy4LNjWXsTmzs2b3o+zkFYdPqPPw== dependencies: - "@react-stately/utils" "^3.10.1" - "@react-types/searchfield" "^3.5.5" + "@react-stately/utils" "^3.10.2" + "@react-types/searchfield" "^3.5.7" "@swc/helpers" "^0.5.0" -"@react-stately/select@^3.6.1", "@react-stately/select@^3.6.4": - version "3.6.4" - resolved "https://registry.yarnpkg.com/@react-stately/select/-/select-3.6.4.tgz#efd512c94545309e2373ea2f17cd97c8a1803321" - integrity sha512-whZgF1N53D0/dS8tOFdrswB0alsk5Q5620HC3z+5f2Hpi8gwgAZ8TYa+2IcmMYRiT+bxVuvEc/NirU9yPmqGbA== - dependencies: - "@react-stately/form" "^3.0.3" - "@react-stately/list" "^3.10.5" - "@react-stately/overlays" "^3.6.7" - "@react-types/select" "^3.9.4" - "@react-types/shared" "^3.23.1" +"@react-stately/select@^3.6.1", "@react-stately/select@^3.6.6": + version "3.6.6" + resolved "https://registry.yarnpkg.com/@react-stately/select/-/select-3.6.6.tgz#fd01bc8cd47aebcddf07488b52d8250437ebbe1f" + integrity sha512-JEpBosWNSXRexE/iReATei1EiVdTIwOWlLcCGw6K7oC/5/f+OHMsh2Kkt/c/RzM/to3vgR+Wbbqwrb712AWgYQ== + dependencies: + "@react-stately/form" "^3.0.5" + "@react-stately/list" "^3.10.7" + "@react-stately/overlays" "^3.6.9" + "@react-types/select" "^3.9.6" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/selection@^3.14.2", "@react-stately/selection@^3.15.1": - version "3.15.1" - resolved "https://registry.yarnpkg.com/@react-stately/selection/-/selection-3.15.1.tgz#853af4958e7eb02d75487c878460338bbec3f548" - integrity sha512-6TQnN9L0UY9w19B7xzb1P6mbUVBtW840Cw1SjgNXCB3NPaCf59SwqClYzoj8O2ZFzMe8F/nUJtfU1NS65/OLlw== +"@react-stately/selection@^3.14.2", "@react-stately/selection@^3.16.1": + version "3.16.1" + resolved "https://registry.yarnpkg.com/@react-stately/selection/-/selection-3.16.1.tgz#00fd65d2abef48067cbb67ad1a7aee8e89bf25c2" + integrity sha512-qmnmYaXY7IhhzmIiInec1a/yPxlPSBHka6vrWddvt0S6zN7FU5cv6sm69ONUwYwLKSoaNHgOGvZhmsTzyV0O2A== dependencies: - "@react-stately/collections" "^3.10.7" - "@react-stately/utils" "^3.10.1" - "@react-types/shared" "^3.23.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/utils" "^3.10.2" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/slider@^3.5.0", "@react-stately/slider@^3.5.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@react-stately/slider/-/slider-3.5.4.tgz#f8c1b5133769380348fa1e8a7a513ebbd88a8355" - integrity sha512-Jsf7K17dr93lkNKL9ij8HUcoM1sPbq8TvmibD6DhrK9If2lje+OOL8y4n4qreUnfMT56HCAeS9wCO3fg3eMyrw== +"@react-stately/slider@^3.5.0", "@react-stately/slider@^3.5.6": + version "3.5.6" + resolved "https://registry.yarnpkg.com/@react-stately/slider/-/slider-3.5.6.tgz#691a14e5aace3208c3de14d3ad6e54d32f26519d" + integrity sha512-a7DZgpOVjQyGzMLPiVRCVHISPJX8E3bT+qbZpcRQN+F7T7wReOwUt2I8gQMosnnCGWgU6kdYk8snn0obXe70Fg== dependencies: - "@react-stately/utils" "^3.10.1" - "@react-types/shared" "^3.23.1" - "@react-types/slider" "^3.7.3" + "@react-stately/utils" "^3.10.2" + "@react-types/shared" "^3.24.1" + "@react-types/slider" "^3.7.5" "@swc/helpers" "^0.5.0" -"@react-stately/table@^3.11.4", "@react-stately/table@^3.11.8": - version "3.11.8" - resolved "https://registry.yarnpkg.com/@react-stately/table/-/table-3.11.8.tgz#b5323b095be8937761b9c5598f38623089047cf8" - integrity sha512-EdyRW3lT1/kAVDp5FkEIi1BQ7tvmD2YgniGdLuW/l9LADo0T+oxZqruv60qpUS6sQap+59Riaxl91ClDxrJnpg== +"@react-stately/table@^3.11.4", "@react-stately/table@^3.12.1": + version "3.12.1" + resolved "https://registry.yarnpkg.com/@react-stately/table/-/table-3.12.1.tgz#fd12c2c43cf9225e0afd8f57583c3a7ff60d79b4" + integrity sha512-Cg3lXrWJNrYkD1gqRclMxq0GGiR+ygxdeAqk2jbbsmHU8RSQuzoO/RtUCw6WAKfQjAq4gE0E60TlAsGgCUdJGA== dependencies: - "@react-stately/collections" "^3.10.7" + "@react-stately/collections" "^3.10.9" "@react-stately/flags" "^3.0.3" - "@react-stately/grid" "^3.8.7" - "@react-stately/selection" "^3.15.1" - "@react-stately/utils" "^3.10.1" - "@react-types/grid" "^3.2.6" - "@react-types/shared" "^3.23.1" - "@react-types/table" "^3.9.5" + "@react-stately/grid" "^3.9.1" + "@react-stately/selection" "^3.16.1" + "@react-stately/utils" "^3.10.2" + "@react-types/grid" "^3.2.8" + "@react-types/shared" "^3.24.1" + "@react-types/table" "^3.10.1" "@swc/helpers" "^0.5.0" -"@react-stately/tabs@^3.6.3", "@react-stately/tabs@^3.6.6": - version "3.6.6" - resolved "https://registry.yarnpkg.com/@react-stately/tabs/-/tabs-3.6.6.tgz#69f4a042406cbe284ffe4c56d3bc8d57cad693fe" - integrity sha512-sOLxorH2uqjAA+v1ppkMCc2YyjgqvSGeBDgtR/lyPSDd4CVMoTExszROX2dqG0c8il9RQvzFuufUtQWMY6PgSA== +"@react-stately/tabs@^3.6.3", "@react-stately/tabs@^3.6.8": + version "3.6.8" + resolved "https://registry.yarnpkg.com/@react-stately/tabs/-/tabs-3.6.8.tgz#c02d8f730d0a9fac9d80075636d62ca37bf3f7b2" + integrity sha512-pLRwnMmXk/IWvbIJYSO5hm3/PiJ/VzrQlwKr6dlOcrDOSVIZpTjnGWHd6mJSDoPiDyBThlN/k3+2pUFMEOAcfw== dependencies: - "@react-stately/list" "^3.10.5" - "@react-types/shared" "^3.23.1" - "@react-types/tabs" "^3.3.7" + "@react-stately/list" "^3.10.7" + "@react-types/shared" "^3.24.1" + "@react-types/tabs" "^3.3.9" "@swc/helpers" "^0.5.0" -"@react-stately/toggle@^3.7.0", "@react-stately/toggle@^3.7.4": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@react-stately/toggle/-/toggle-3.7.4.tgz#3345b5c939db96305af7c22b73577db5536220ab" - integrity sha512-CoYFe9WrhLkDP4HGDpJYQKwfiYCRBAeoBQHv+JWl5eyK61S8xSwoHsveYuEZ3bowx71zyCnNAqWRrmNOxJ4CKA== +"@react-stately/toggle@^3.7.0", "@react-stately/toggle@^3.7.6": + version "3.7.6" + resolved "https://registry.yarnpkg.com/@react-stately/toggle/-/toggle-3.7.6.tgz#de31d74b9f9bd73350cf99ba582bc3cd79fa8c74" + integrity sha512-xRZyrjNVu1VCd1xpg5RwmNYs9fXb+JHChoUaRcBmGCCjsPD0R5uR3iNuE17RXJtWS3/8o9IJVn90+/7NW7boOg== dependencies: - "@react-stately/utils" "^3.10.1" - "@react-types/checkbox" "^3.8.1" + "@react-stately/utils" "^3.10.2" + "@react-types/checkbox" "^3.8.3" "@swc/helpers" "^0.5.0" -"@react-stately/tooltip@^3.4.6", "@react-stately/tooltip@^3.4.9": - version "3.4.9" - resolved "https://registry.yarnpkg.com/@react-stately/tooltip/-/tooltip-3.4.9.tgz#a6161db77bd5ad606caa1a302622f92bc381b4ac" - integrity sha512-P7CDJsdoKarz32qFwf3VNS01lyC+63gXpDZG31pUu+EO5BeQd4WKN/AH1Beuswpr4GWzxzFc1aXQgERFGVzraA== +"@react-stately/tooltip@^3.4.11", "@react-stately/tooltip@^3.4.6": + version "3.4.11" + resolved "https://registry.yarnpkg.com/@react-stately/tooltip/-/tooltip-3.4.11.tgz#9fac5c5cd891893b7ea9d7cf2f6f1ee148d0e570" + integrity sha512-r1ScIXau2LZ/lUUBQ5PI01S2TB2urF2zrPzNM2xgngFLlG2uTyfIgMga6/035quQQKd3Bd0qGigMvTgZ3GRGEg== dependencies: - "@react-stately/overlays" "^3.6.7" - "@react-types/tooltip" "^3.4.9" + "@react-stately/overlays" "^3.6.9" + "@react-types/tooltip" "^3.4.11" "@swc/helpers" "^0.5.0" -"@react-stately/tree@^3.7.5", "@react-stately/tree@^3.8.1": - version "3.8.1" - resolved "https://registry.yarnpkg.com/@react-stately/tree/-/tree-3.8.1.tgz#a3ea36d503a0276a860842cc8bf7c759aa7fa75f" - integrity sha512-LOdkkruJWch3W89h4B/bXhfr0t0t1aRfEp+IMrrwdRAl23NaPqwl5ILHs4Xu5XDHqqhg8co73pHrJwUyiTWEjw== +"@react-stately/tree@^3.7.5", "@react-stately/tree@^3.8.3": + version "3.8.3" + resolved "https://registry.yarnpkg.com/@react-stately/tree/-/tree-3.8.3.tgz#c92854f6b10b146f983759243b64cdcba0a1fb28" + integrity sha512-9sRQOxkK7ZMdtSTGHx0sMabHC39PEM4tMl+IdJKkmcp60bfsm3p6LHXhha3E58jwnZaemBfUrlQmTP/E26BbGw== dependencies: - "@react-stately/collections" "^3.10.7" - "@react-stately/selection" "^3.15.1" - "@react-stately/utils" "^3.10.1" - "@react-types/shared" "^3.23.1" + "@react-stately/collections" "^3.10.9" + "@react-stately/selection" "^3.16.1" + "@react-stately/utils" "^3.10.2" + "@react-types/shared" "^3.24.1" "@swc/helpers" "^0.5.0" -"@react-stately/utils@^3.10.1": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.10.1.tgz#dc8685b4994bef0dc10c37b024074be8afbfba62" - integrity sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg== +"@react-stately/utils@^3.10.2": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.10.2.tgz#09377f771592ff537c901aa64178cb3a004a916f" + integrity sha512-fh6OTQtbeQC0ywp6LJuuKs6tKIgFvt/DlIZEcIpGho6/oZG229UnIk6TUekwxnDbumuYyan6D9EgUtEMmT8UIg== dependencies: "@swc/helpers" "^0.5.0" -"@react-stately/virtualizer@^3.7.1": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@react-stately/virtualizer/-/virtualizer-3.7.1.tgz#eb962d2ce700c026ce1b1d901034601db9d370c0" - integrity sha512-voHgE6EQ+oZaLv6u2umKxakvIKNkCQuUihqKACTjdslp7SJh4Mvs3oLBI0hf0JOh+rCcFIKDvQtFwy1fXFRYBA== +"@react-types/breadcrumbs@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@react-types/breadcrumbs/-/breadcrumbs-3.7.7.tgz#35c2733e3387fb8800adffa4e412e245db5c5eec" + integrity sha512-ZmhXwD2LLzfEA2OvOCp/QvXu8A/Edsrn5q0qUDGsmOZj9SCVeT82bIv8P+mQnATM13mi2gyoik6102Jc1OscJA== dependencies: - "@react-aria/utils" "^3.24.1" - "@react-types/shared" "^3.23.1" - "@swc/helpers" "^0.5.0" + "@react-types/link" "^3.5.7" + "@react-types/shared" "^3.24.1" -"@react-types/breadcrumbs@^3.7.5": - version "3.7.5" - resolved "https://registry.yarnpkg.com/@react-types/breadcrumbs/-/breadcrumbs-3.7.5.tgz#72bc6e8881446864d7bf786f4667a2fbdda279f8" - integrity sha512-lV9IDYsMiu2TgdMIjEmsOE0YWwjb3jhUNK1DCZZfq6uWuiHLgyx2EncazJBUWSjHJ4ta32j7xTuXch+8Ai6u/A== +"@react-types/button@^3.9.6": + version "3.9.6" + resolved "https://registry.yarnpkg.com/@react-types/button/-/button-3.9.6.tgz#135fc465a3026f2c5005725b63cf7c3525be2306" + integrity sha512-8lA+D5JLbNyQikf8M/cPP2cji91aVTcqjrGpDqI7sQnaLFikM8eFR6l1ZWGtZS5MCcbfooko77ha35SYplSQvw== dependencies: - "@react-types/link" "^3.5.5" - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/button@^3.9.4": - version "3.9.4" - resolved "https://registry.yarnpkg.com/@react-types/button/-/button-3.9.4.tgz#ec10452e870660d31db1994f6fe4abfe0c800814" - integrity sha512-raeQBJUxBp0axNF74TXB8/H50GY8Q3eV6cEKMbZFP1+Dzr09Ngv0tJBeW0ewAxAguNH5DRoMUAUGIXtSXskVdA== +"@react-types/calendar@^3.4.8": + version "3.4.8" + resolved "https://registry.yarnpkg.com/@react-types/calendar/-/calendar-3.4.8.tgz#e9cc625d286b6a2df17e3d17a2c825b700f01f6c" + integrity sha512-KVampt/X4uJvWU0TsxIdgPdXIAUClGtxcDWHzuFRJ7YUYkA4rH8Lad0kQ1mVehnwOLpuba8j9GCYKorkbln0gw== dependencies: - "@react-types/shared" "^3.23.1" + "@internationalized/date" "^3.5.5" + "@react-types/shared" "^3.24.1" -"@react-types/calendar@^3.4.6": - version "3.4.6" - resolved "https://registry.yarnpkg.com/@react-types/calendar/-/calendar-3.4.6.tgz#66ddcefc3058492b3cce58a6e63b01558048b669" - integrity sha512-WSntZPwtvsIYWvBQRAPvuCn55UTJBZroTvX0vQvWykJRQnPAI20G1hMQ3dNsnAL+gLZUYxBXn66vphmjUuSYew== +"@react-types/checkbox@^3.8.3": + version "3.8.3" + resolved "https://registry.yarnpkg.com/@react-types/checkbox/-/checkbox-3.8.3.tgz#331055cf283dfb01c6bbcb02355a20decab19ada" + integrity sha512-f4c1mnLEt0iS1NMkyZXgT3q3AgcxzDk7w6MSONOKydcnh0xG5L2oefY14DhVDLkAuQS7jThlUFwiAs+MxiO3MA== dependencies: - "@internationalized/date" "^3.5.4" - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/checkbox@^3.8.1": +"@react-types/combobox@^3.12.1": + version "3.12.1" + resolved "https://registry.yarnpkg.com/@react-types/combobox/-/combobox-3.12.1.tgz#ab015d31c160aa0a21d696887ce81467c5996602" + integrity sha512-bd5YwHZWtgnJx4jGbplWbYzXj7IbO5w3IY5suNR7r891rx6IktquZ8GQwyYH0pQ/x+X5LdK2xI59i6+QC2PmlA== + dependencies: + "@react-types/shared" "^3.24.1" + +"@react-types/datepicker@^3.8.1": version "3.8.1" - resolved "https://registry.yarnpkg.com/@react-types/checkbox/-/checkbox-3.8.1.tgz#de82c93542b2dd85c01df2e0c85c33a2e6349d14" - integrity sha512-5/oVByPw4MbR/8QSdHCaalmyWC71H/QGgd4aduTJSaNi825o+v/hsN2/CH7Fq9atkLKsC8fvKD00Bj2VGaKriQ== + resolved "https://registry.yarnpkg.com/@react-types/datepicker/-/datepicker-3.8.1.tgz#2998a40f19cd5dbfd57ead6b45c748639a276990" + integrity sha512-ZpxHHVT3rmZ4YsYP4TWCZSMSfOUm+067mZyyGLmvHxg55eYmctiB4uMgrRCqDoeiSiOjtxad0VtpPjf6ftK1GQ== dependencies: - "@react-types/shared" "^3.23.1" + "@internationalized/date" "^3.5.5" + "@react-types/calendar" "^3.4.8" + "@react-types/overlays" "^3.8.9" + "@react-types/shared" "^3.24.1" -"@react-types/combobox@^3.11.1": - version "3.11.1" - resolved "https://registry.yarnpkg.com/@react-types/combobox/-/combobox-3.11.1.tgz#d5ab2f3c12d01083a3fc7c6ed90b9a2ae9049aa0" - integrity sha512-UNc3OHt5cUt5gCTHqhQIqhaWwKCpaNciD8R7eQazmHiA9fq8ROlV+7l3gdNgdhJbTf5Bu/V5ISnN7Y1xwL3zqQ== +"@react-types/dialog@^3.5.12": + version "3.5.12" + resolved "https://registry.yarnpkg.com/@react-types/dialog/-/dialog-3.5.12.tgz#cba173e3a1ca7efd8859bd995389eaa90070e5ea" + integrity sha512-JmpQbSpXltqEyYfEwoqDolABIiojeExkqolHNdQlayIsfFuSxZxNwXZPOpz58Ri/iwv21JP7K3QF0Gb2Ohxl9w== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/overlays" "^3.8.9" + "@react-types/shared" "^3.24.1" -"@react-types/datepicker@^3.7.4": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@react-types/datepicker/-/datepicker-3.7.4.tgz#8b21df1041d7e51198621984920ac290b2f09744" - integrity sha512-ZfvgscvNzBJpYyVWg3nstJtA/VlWLwErwSkd1ivZYam859N30w8yH+4qoYLa6FzWLCFlrsRHyvtxlEM7lUAt5A== +"@react-types/grid@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@react-types/grid/-/grid-3.2.8.tgz#1855586e309387edcc6a77bb675a624039e9831a" + integrity sha512-6PJrpukwMqlv3IhJSDkJuVbhHM8Oe6hd2supWqd9adMXrlSP7QHt9a8SgFcFblCCTx8JzUaA0PvY5sTudcEtOQ== dependencies: - "@internationalized/date" "^3.5.4" - "@react-types/calendar" "^3.4.6" - "@react-types/overlays" "^3.8.7" - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/dialog@^3.5.10": - version "3.5.10" - resolved "https://registry.yarnpkg.com/@react-types/dialog/-/dialog-3.5.10.tgz#c0fe93c432581eb032c28632733ea80ae242b2c3" - integrity sha512-S9ga+edOLNLZw7/zVOnZdT5T40etpzUYBXEKdFPbxyPYnERvRxJAsC1/ASuBU9fQAXMRgLZzADWV+wJoGS/X9g== +"@react-types/link@^3.5.7": + version "3.5.7" + resolved "https://registry.yarnpkg.com/@react-types/link/-/link-3.5.7.tgz#298447339a5513a007d31c26cb0fd8ab611da2e1" + integrity sha512-2WyaVmm1qr9UrSG3Dq6iz+2ziuVp+DH8CsYZ9CA6aNNb6U18Hxju3LTPb4a5gM0eC7W0mQGNBmrgGlAdDZEJOw== dependencies: - "@react-types/overlays" "^3.8.7" - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/grid@^3.2.6": - version "3.2.6" - resolved "https://registry.yarnpkg.com/@react-types/grid/-/grid-3.2.6.tgz#c0aba4a748d1722bafe85acf87f8d9d5134653b3" - integrity sha512-XfHenL2jEBUYrhKiPdeM24mbLRXUn79wVzzMhrNYh24nBwhsPPpxF+gjFddT3Cy8dt6tRInfT6pMEu9nsXwaHw== +"@react-types/listbox@^3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@react-types/listbox/-/listbox-3.5.1.tgz#e2a95fcb9593b37b5743c96208ea34f82c825752" + integrity sha512-n5bOgD9lgfK1qaLtag9WPnu151SwXBCNn/OgGY/Br9mWRl+nPUEYtFcPX+2VCld7uThf54kwrTmzlFnaraIlcw== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/link@^3.5.5": - version "3.5.5" - resolved "https://registry.yarnpkg.com/@react-types/link/-/link-3.5.5.tgz#5ed829aa32f226fe62efb0d906b1926c110daf02" - integrity sha512-G6P5WagHDR87npN7sEuC5IIgL1GsoY4WFWKO4734i2CXRYx24G9P0Su3AX4GA3qpspz8sK1AWkaCzBMmvnunfw== +"@react-types/menu@^3.9.11": + version "3.9.11" + resolved "https://registry.yarnpkg.com/@react-types/menu/-/menu-3.9.11.tgz#5208ece45f47464bc74f73499fdc14e89679d44f" + integrity sha512-IguQVF70d7aHXgWB1Rd2a/PiIuLZ2Nt7lyayJshLcy/NLOYmgpTmTyn2WCtlA5lTfQwmQrNFf4EvnWkeljJXdA== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/overlays" "^3.8.9" + "@react-types/shared" "^3.24.1" -"@react-types/listbox@^3.4.9": - version "3.4.9" - resolved "https://registry.yarnpkg.com/@react-types/listbox/-/listbox-3.4.9.tgz#92e9990f480b48c1849ffd57ad8f95f5e278df66" - integrity sha512-S5G+WmNKUIOPZxZ4svWwWQupP3C6LmVfnf8QQmPDvwYXGzVc0WovkqUWyhhjJirFDswTXRCO9p0yaTHHIlkdwQ== +"@react-types/meter@^3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@react-types/meter/-/meter-3.4.3.tgz#de886e64759c8200f2958277a4f73abdf463fc18" + integrity sha512-Y2fX5CTAPGRKxVSeepbeyN6/K+wlF9pMRcNxTSU2qDwdoFqNCtTWMcWuCsU/Y2L/zU0jFWu4x0Vo7WkrcsgcMA== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/progress" "^3.5.6" -"@react-types/menu@^3.9.9": - version "3.9.9" - resolved "https://registry.yarnpkg.com/@react-types/menu/-/menu-3.9.9.tgz#d7f81f6ecad7dd04fc730b4ad5c3ca39e3c0883d" - integrity sha512-FamUaPVs1Fxr4KOMI0YcR2rYZHoN7ypGtgiEiJ11v/tEPjPPGgeKDxii0McCrdOkjheatLN1yd2jmMwYj6hTDg== +"@react-types/numberfield@^3.8.5": + version "3.8.5" + resolved "https://registry.yarnpkg.com/@react-types/numberfield/-/numberfield-3.8.5.tgz#de489f8913451e299c3621e8d317e809e20e45af" + integrity sha512-LVWggkxwd1nyVZomXBPfQA1E4I4/i4PBifjcDs2AfcV7q5RE9D+DVIDXsYucVOBxPlDOxiAq/T9ypobspWSwHw== dependencies: - "@react-types/overlays" "^3.8.7" - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/meter@^3.4.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@react-types/meter/-/meter-3.4.1.tgz#ae587293f5f8f8a5876cd10a7001181f62e1eafb" - integrity sha512-AIJV4NDFAqKH94s02c5Da4TH2qgJjfrw978zuFM0KUBFD85WRPKh7MvgWpomvUgmzqE6lMCzIdi1KPKqrRabdw== +"@react-types/overlays@^3.8.9": + version "3.8.9" + resolved "https://registry.yarnpkg.com/@react-types/overlays/-/overlays-3.8.9.tgz#3b5ca1f645f0acb1fefd2cf045cac1d9fd8748d5" + integrity sha512-9ni9upQgXPnR+K9cWmbYWvm3ll9gH8P/XsEZprqIV5zNLMF334jADK48h4jafb1X9RFnj0WbHo6BqcSObzjTig== + dependencies: + "@react-types/shared" "^3.24.1" + +"@react-types/progress@^3.5.6": + version "3.5.6" + resolved "https://registry.yarnpkg.com/@react-types/progress/-/progress-3.5.6.tgz#bc6602e94d2a306a9bfaa118a584b996d95bb015" + integrity sha512-Nh43sjQ5adyN1bTHBPRaIPhXUdBqP0miYeJpeMY3V/KUl4qmouJLwDnccwFG4xLm6gBfYe22lgbbV7nAfNnuTQ== dependencies: - "@react-types/progress" "^3.5.4" + "@react-types/shared" "^3.24.1" -"@react-types/numberfield@^3.8.3": +"@react-types/radio@^3.8.3": version "3.8.3" - resolved "https://registry.yarnpkg.com/@react-types/numberfield/-/numberfield-3.8.3.tgz#85f8c4eceea22b437232250596fbaebfc7318e04" - integrity sha512-z5fGfVj3oh5bmkw9zDvClA1nDBSFL9affOuyk2qZ/M2SRUmykDAPCksbfcMndft0XULWKbF4s2CYbVI+E/yrUA== + resolved "https://registry.yarnpkg.com/@react-types/radio/-/radio-3.8.3.tgz#68752dbc5ae3d60a20e285f37ed156d425efd4b6" + integrity sha512-fUVJt4Bb6jOReFqnhHVNxWXH7t6c60uSFfoPKuXt/xI9LL1i2jhpur0ggpTfIn3qLIAmNBU6bKBCWAdr4KjeVQ== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/overlays@^3.8.7": - version "3.8.7" - resolved "https://registry.yarnpkg.com/@react-types/overlays/-/overlays-3.8.7.tgz#a43faf524cb3fce74acceee43898b265e8dfee05" - integrity sha512-zCOYvI4at2DkhVpviIClJ7bRrLXYhSg3Z3v9xymuPH3mkiuuP/dm8mUCtkyY4UhVeUTHmrQh1bzaOP00A+SSQA== +"@react-types/searchfield@^3.5.7": + version "3.5.7" + resolved "https://registry.yarnpkg.com/@react-types/searchfield/-/searchfield-3.5.7.tgz#f95b5693f09ebb2b4b267a4bc149de2fdc2a1fbd" + integrity sha512-dyuPwNWGswRZfb4i50Q1Q3tCwTBxRLkrAxcMs+Rf2Rl4t93bawBdSdIQuvxu1KEhgd0EXA9ZUW53ZplqfVmtiw== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" + "@react-types/textfield" "^3.9.5" -"@react-types/progress@^3.5.4": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@react-types/progress/-/progress-3.5.4.tgz#22032aa0a64a3ff99fcd6e6e4f22cbc09c9725f3" - integrity sha512-JNc246sTjasPyx5Dp7/s0rp3Bz4qlu4LrZTulZlxWyb53WgBNL7axc26CCi+I20rWL9+c7JjhrRxnLl/1cLN5g== +"@react-types/select@^3.9.6": + version "3.9.6" + resolved "https://registry.yarnpkg.com/@react-types/select/-/select-3.9.6.tgz#234c94d2dd6f0f52d2dcbda3d3a2f54851507a98" + integrity sha512-cVSFR0eJLup/ht1Uto+y8uyLmHO89J6wNh65SIHb3jeVz9oLBAedP3YNI2qB+F9qFMUcA8PBSLXIIuT6gXzLgQ== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/radio@^3.8.1": - version "3.8.1" - resolved "https://registry.yarnpkg.com/@react-types/radio/-/radio-3.8.1.tgz#f12ddd21d88fa278baa8ddc237b778c70b67669f" - integrity sha512-bK0gio/qj1+0Ldu/3k/s9BaOZvnnRgvFtL3u5ky479+aLG5qf1CmYed3SKz8ErZ70JkpuCSrSwSCFf0t1IHovw== +"@react-types/shared@^3.22.0", "@react-types/shared@^3.24.1": + version "3.24.1" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.24.1.tgz#fa06cb681d144fce9c515d8bd296d81440a45d25" + integrity sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw== + +"@react-types/slider@^3.7.5": + version "3.7.5" + resolved "https://registry.yarnpkg.com/@react-types/slider/-/slider-3.7.5.tgz#62f71c5e51a013fe14ad84d3496a0fa281b5b3a7" + integrity sha512-bRitwQRQjQoOcKEdPMljnvm474dwrmsc6pdsVQDh/qynzr+KO9IHuYc3qPW53WVE2hMQJDohlqtCAWQXWQ5Vcg== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/searchfield@^3.5.5": +"@react-types/switch@^3.5.5": version "3.5.5" - resolved "https://registry.yarnpkg.com/@react-types/searchfield/-/searchfield-3.5.5.tgz#61b1c684039b1ff40d1a8da6c5172b4c8b90d530" - integrity sha512-T/NHg12+w23TxlXMdetogLDUldk1z5dDavzbnjKrLkajLb221bp8brlR/+O6C1CtFpuJGALqYHgTasU1qkQFSA== + resolved "https://registry.yarnpkg.com/@react-types/switch/-/switch-3.5.5.tgz#e9d37bf5d974f3cc201503b8d46c24105afa48f0" + integrity sha512-SZx1Bd+COhAOs/RTifbZG+uq/llwba7VAKx7XBeX4LeIz1dtguy5bigOBgFTMQi4qsIVCpybSWEEl+daj4XFPw== dependencies: - "@react-types/shared" "^3.23.1" - "@react-types/textfield" "^3.9.3" + "@react-types/shared" "^3.24.1" -"@react-types/select@^3.9.4": - version "3.9.4" - resolved "https://registry.yarnpkg.com/@react-types/select/-/select-3.9.4.tgz#6283cdcb0583a87d23aa00fd118365f80fe68484" - integrity sha512-xI7dnOW2st91fPPcv6hdtrTdcfetYiqZuuVPZ5TRobY7Q10/Zqqe/KqtOw1zFKUj9xqNJe4Ov3xP5GSdcO60Eg== +"@react-types/table@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@react-types/table/-/table-3.10.1.tgz#a44e871cd163d6838668ffd6821c604cf5fd307a" + integrity sha512-xsNh0Gm4GtNeSknZqkMsfGvc94fycmfhspGO+FzQKim2hB5k4yILwd+lHYQ2UKW6New9GVH/zN2Pd3v67IeZ2g== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/grid" "^3.2.8" + "@react-types/shared" "^3.24.1" -"@react-types/shared@^3.22.0", "@react-types/shared@^3.23.1": - version "3.23.1" - resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.23.1.tgz#2f23c81d819d0ef376df3cd4c944be4d6bce84c3" - integrity sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw== +"@react-types/tabs@^3.3.9": + version "3.3.9" + resolved "https://registry.yarnpkg.com/@react-types/tabs/-/tabs-3.3.9.tgz#a23011bf8fe955461ae25339f4de5b91cd7ee5eb" + integrity sha512-3Q9kRVvg/qDyeJR/W1+C2z2OyvDWQrSLvOCvAezX5UKzww4rBEAA8OqBlyDwn7q3fiwrh/m64l6p+dbln+RdxQ== + dependencies: + "@react-types/shared" "^3.24.1" -"@react-types/slider@^3.7.3": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@react-types/slider/-/slider-3.7.3.tgz#d6de0626c6977dd10faea2dba656193106ffbdb8" - integrity sha512-F8qFQaD2mqug2D0XeWMmjGBikiwbdERFlhFzdvNGbypPLz3AZICBKp1ZLPWdl0DMuy03G/jy6Gl4mDobl7RT2g== +"@react-types/textfield@^3.9.5": + version "3.9.5" + resolved "https://registry.yarnpkg.com/@react-types/textfield/-/textfield-3.9.5.tgz#a9d906ccbe6e5d42f3158320c036e112ae8c61d0" + integrity sha512-0hwZI4WXSEStPzdltKwbNUZWlgHtwbxMWE0LfqIzEW8RB7DyBflYSKzLyTBFqwUZ8j3C1gWy9c9OPSeCOq792Q== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/shared" "^3.24.1" -"@react-types/switch@^3.5.3": - version "3.5.3" - resolved "https://registry.yarnpkg.com/@react-types/switch/-/switch-3.5.3.tgz#2a5faaf513e03972df3077e4ff5ef21738239d7c" - integrity sha512-Nb6+J5MrPaFa8ZNFKGMzAsen/NNzl5UG/BbC65SLGPy7O0VDa/sUpn7dcu8V2xRpRwwIN/Oso4v63bt2sgdkgA== +"@react-types/tooltip@^3.4.11": + version "3.4.11" + resolved "https://registry.yarnpkg.com/@react-types/tooltip/-/tooltip-3.4.11.tgz#6d24fa33d3210400980aa5778f77bea6508588b4" + integrity sha512-WPikHQxeT5Lb09yJEaW6Ja3ecE0g1YM6ukWYS2v/iZLUPn5YlYrGytspuCYQNSh/u7suCz4zRLEHYCl7OCigjw== dependencies: - "@react-types/shared" "^3.23.1" + "@react-types/overlays" "^3.8.9" + "@react-types/shared" "^3.24.1" -"@react-types/table@^3.9.5": - version "3.9.5" - resolved "https://registry.yarnpkg.com/@react-types/table/-/table-3.9.5.tgz#7910debd618405598583a10588a75f97c7b15eeb" - integrity sha512-fgM2j9F/UR4Anmd28CueghCgBwOZoCVyN8fjaIFPd2MN4gCwUUfANwxLav65gZk4BpwUXGoQdsW+X50L3555mg== +"@remix-run/router@1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.0.tgz#745dbffbce67f05386d57ca22c51dfd85c979593" + integrity sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA== + +"@segment/analytics-core@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@segment/analytics-core/-/analytics-core-1.6.0.tgz#f59cdc45a4408a09fdae77910f5a0b43833e8af8" + integrity sha512-bn9X++IScUfpT7aJGjKU/yJAu/Ko2sYD6HsKA70Z2560E89x30pqgqboVKY8kootvQnT4UKCJiUr5NDMgjmWdQ== dependencies: - "@react-types/grid" "^3.2.6" - "@react-types/shared" "^3.23.1" + "@lukeed/uuid" "^2.0.0" + "@segment/analytics-generic-utils" "1.2.0" + dset "^3.1.2" + tslib "^2.4.1" -"@react-types/tabs@^3.3.7": - version "3.3.7" - resolved "https://registry.yarnpkg.com/@react-types/tabs/-/tabs-3.3.7.tgz#8bb7a65998395bad75576f5ce32c8ce61329497f" - integrity sha512-ZdLe5xOcFX6+/ni45Dl2jO0jFATpTnoSqj6kLIS/BYv8oh0n817OjJkLf+DS3CLfNjApJWrHqAk34xNh6nRnEg== +"@segment/analytics-generic-utils@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz#9232162d6dbcd18501813fdff18035ce48fd24bf" + integrity sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw== + dependencies: + tslib "^2.4.1" + +"@segment/analytics-next@^1.70.0": + version "1.72.1" + resolved "https://registry.yarnpkg.com/@segment/analytics-next/-/analytics-next-1.72.1.tgz#e09bc6a31de004986c1822ac39c69b419135c75e" + integrity sha512-OeCe/jMAWxC5a1fY8RRszIzG7zevmkRHEaUQZMXskPFPWu2fB9r8A/JeZtoX9+kKlNg2SMMAdsnIRxsvaHIkMA== + dependencies: + "@lukeed/uuid" "^2.0.0" + "@segment/analytics-core" "1.6.0" + "@segment/analytics-generic-utils" "1.2.0" + "@segment/analytics.js-video-plugins" "^0.2.1" + "@segment/facade" "^3.4.9" + dset "^3.1.2" + js-cookie "3.0.1" + node-fetch "^2.6.7" + tslib "^2.4.1" + unfetch "^4.1.0" + +"@segment/analytics.js-video-plugins@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@segment/analytics.js-video-plugins/-/analytics.js-video-plugins-0.2.1.tgz#3596fa3887dcd9df5978dc566edf4a0aea2a9b1e" + integrity sha512-lZwCyEXT4aaHBLNK433okEKdxGAuyrVmop4BpQqQSJuRz0DglPZgd9B/XjiiWs1UyOankg2aNYMN3VcS8t4eSQ== dependencies: - "@react-types/shared" "^3.23.1" + unfetch "^3.1.1" -"@react-types/textfield@^3.9.3": - version "3.9.3" - resolved "https://registry.yarnpkg.com/@react-types/textfield/-/textfield-3.9.3.tgz#23db9d87ddadc4eddff3f85406af91e442f01dc9" - integrity sha512-DoAY6cYOL0pJhgNGI1Rosni7g72GAt4OVr2ltEx2S9ARmFZ0DBvdhA9lL2nywcnKMf27PEJcKMXzXc10qaHsJw== +"@segment/facade@^3.4.9": + version "3.4.10" + resolved "https://registry.yarnpkg.com/@segment/facade/-/facade-3.4.10.tgz#118fab29cf2250d3128f9b2a16d6ec76f86e3710" + integrity sha512-xVQBbB/lNvk/u8+ey0kC/+g8pT3l0gCT8O2y9Z+StMMn3KAFAQ9w8xfgef67tJybktOKKU7pQGRPolRM1i1pdA== dependencies: - "@react-types/shared" "^3.23.1" + "@segment/isodate-traverse" "^1.1.1" + inherits "^2.0.4" + new-date "^1.0.3" + obj-case "0.2.1" -"@react-types/tooltip@^3.4.9": - version "3.4.9" - resolved "https://registry.yarnpkg.com/@react-types/tooltip/-/tooltip-3.4.9.tgz#fb2291bd0b915f7c7f5024ce146412405843ec9b" - integrity sha512-wZ+uF1+Zc43qG+cOJzioBmLUNjRa7ApdcT0LI1VvaYvH5GdfjzUJOorLX9V/vAci0XMJ50UZ+qsh79aUlw2yqg== +"@segment/isodate-traverse@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@segment/isodate-traverse/-/isodate-traverse-1.1.1.tgz#37e1a68b5e48a841260145f1be86d342995dfc64" + integrity sha512-+G6e1SgAUkcq0EDMi+SRLfT48TNlLPF3QnSgFGVs0V9F3o3fq/woQ2rHFlW20W0yy5NnCUH0QGU3Am2rZy/E3w== dependencies: - "@react-types/overlays" "^3.8.7" - "@react-types/shared" "^3.23.1" + "@segment/isodate" "^1.0.3" -"@remix-run/router@1.17.1": - version "1.17.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.17.1.tgz#bf93997beb81863fde042ebd05013a2618471362" - integrity sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q== +"@segment/isodate@1.0.3", "@segment/isodate@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@segment/isodate/-/isodate-1.0.3.tgz#f44e8202d5edd277ce822785239474b2c9411d4a" + integrity sha512-BtanDuvJqnACFkeeYje7pWULVv8RgZaqKHWwGFnL/g/TH/CcZjkIVTfGDp/MAxmilYHUkrX70SqwnYSTNEaN7A== "@swc/helpers@^0.5.0": - version "0.5.11" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.11.tgz#5bab8c660a6e23c13b2d23fcd1ee44a2db1b0cb7" - integrity sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A== + version "0.5.12" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.12.tgz#37aaca95284019eb5d2207101249435659709f4b" + integrity sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g== dependencies: tslib "^2.4.0" @@ -2180,9 +2205,9 @@ integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== "@types/node@^20.11.10": - version "20.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" - integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== + version "20.14.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.14.tgz#6b655d4a88623b0edb98300bb9dd2107225f885e" + integrity sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ== dependencies: undici-types "~5.26.4" @@ -2191,7 +2216,7 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== -"@types/prop-types@*", "@types/prop-types@^15.7.11": +"@types/prop-types@*", "@types/prop-types@^15.7.12": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== @@ -2479,16 +2504,6 @@ array.prototype.flatmap@^1.3.2: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.toreversed@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" - integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - array.prototype.tosorted@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" @@ -2525,15 +2540,15 @@ attr-accept@^2.2.2: integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== autoprefixer@^10.4.17: - version "10.4.19" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" - integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== + version "10.4.20" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b" + integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g== dependencies: - browserslist "^4.23.0" - caniuse-lite "^1.0.30001599" + browserslist "^4.23.3" + caniuse-lite "^1.0.30001646" fraction.js "^4.3.7" normalize-range "^0.1.2" - picocolors "^1.0.0" + picocolors "^1.0.1" postcss-value-parser "^4.2.0" available-typed-arrays@^1.0.7: @@ -2544,9 +2559,9 @@ available-typed-arrays@^1.0.7: possible-typed-array-names "^1.0.0" axios@^1.6.5: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + version "1.7.3" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.3.tgz#a1125f2faf702bc8e8f2104ec3a76fab40257d85" + integrity sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -2608,15 +2623,15 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.22.2, browserslist@^4.23.0: - version "4.23.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.1.tgz#ce4af0534b3d37db5c1a4ca98b9080f985041e96" - integrity sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw== +browserslist@^4.23.1, browserslist@^4.23.3: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== dependencies: - caniuse-lite "^1.0.30001629" - electron-to-chromium "^1.4.796" - node-releases "^2.0.14" - update-browserslist-db "^1.0.16" + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" buffer@^6.0.3: version "6.0.3" @@ -2647,10 +2662,10 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001629: - version "1.0.30001640" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f" - integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA== +caniuse-lite@^1.0.30001646: + version "1.0.30001651" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" + integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== ccount@^2.0.0: version "2.0.1" @@ -2808,13 +2823,6 @@ concaveman@^1.2.1: robust-predicates "^2.0.4" tinyqueue "^2.0.3" -contrast@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/contrast/-/contrast-1.0.1.tgz#839c66d8852269d33f4eb0a1b92d994bdd16385e" - integrity sha512-cJvPtJOPXxFtkppBYqk1A1+ZtPT7neKmgjBKMtjnwSAZ5k+5GNdJHeDmIjCTLEItLcqna+K7GTtaGjQtDJcF4A== - dependencies: - hex-to-rgb "^1.0.0" - convert-source-map@^1.5.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" @@ -2902,9 +2910,9 @@ cytoscape-cose-bilkent@^4.1.0: cose-base "^1.0.0" cytoscape@^3.23.0: - version "3.30.0" - resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.30.0.tgz#5b0c13f7bb305481e2c70414d4c5f149d92eda82" - integrity sha512-l590mjTHT6/Cbxp13dGPC2Y7VXdgc+rUeF8AnF/JPzhjNevbDJfObnJgaSjlldOgBQZbue+X6IUZ7r5GAgvauQ== + version "3.30.2" + resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.30.2.tgz#94149707fb6547a55e3b44f03ffe232706212161" + integrity sha512-oICxQsjW8uSaRmn4UK/jkczKOqTrVqt5/1WL0POiJUT2EKNc9STM4hYFHv917yu55aTBMFNRzymlJhVAiWPCxw== "d3-dispatch@1 - 3": version "3.0.1" @@ -2965,9 +2973,9 @@ date-fns@^2.30.0: "@babel/runtime" "^7.21.0" debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" @@ -2992,7 +3000,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: es-errors "^1.3.0" gopd "^1.0.1" -define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -3067,15 +3075,20 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dset@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.3.tgz#c194147f159841148e8e34ca41f638556d9542d2" + integrity sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -electron-to-chromium@^1.4.796: - version "1.4.816" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.816.tgz#3624649d1e7fde5cdbadf59d31a524245d8ee85f" - integrity sha512-EKH5X5oqC6hLmiS7/vYtZHZFTNdhsYG5NVPRN6Yn0kQHNBlT59+xSM8HBy66P5fxWpKgZbPqb+diC64ng295Jw== +electron-to-chromium@^1.5.4: + version "1.5.5" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz#03bfdf422bdd2c05ee2657efedde21264a1a566b" + integrity sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA== emoji-regex@^8.0.0: version "8.0.0" @@ -3101,7 +3114,7 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.3.4" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: +es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: version "1.23.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== @@ -3276,33 +3289,33 @@ eslint-plugin-react-hooks@^4.6.0: integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== eslint-plugin-react-refresh@^0.4.3: - version "0.4.7" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz#1f597f9093b254f10ee0961c139a749acb19af7d" - integrity sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw== + version "0.4.9" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz#bf870372b353b12e1e6fb7fc41b282d9cbc8d93d" + integrity sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA== eslint-plugin-react@^7.33.2: - version "7.34.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz#9965f27bd1250a787b5d4cfcc765e5a5d58dcb7b" - integrity sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA== + version "7.35.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41" + integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA== dependencies: array-includes "^3.1.8" array.prototype.findlast "^1.2.5" array.prototype.flatmap "^1.3.2" - array.prototype.toreversed "^1.1.2" array.prototype.tosorted "^1.1.4" doctrine "^2.1.0" es-iterator-helpers "^1.0.19" estraverse "^5.3.0" + hasown "^2.0.2" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" object.entries "^1.1.8" object.fromentries "^2.0.8" - object.hasown "^1.1.4" object.values "^1.2.0" prop-types "^15.8.1" resolve "^2.0.0-next.5" semver "^6.3.1" string.prototype.matchall "^4.0.11" + string.prototype.repeat "^1.0.0" eslint-scope@^7.2.2: version "7.2.2" @@ -3371,9 +3384,9 @@ espree@^9.6.0, espree@^9.6.1: eslint-visitor-keys "^3.4.1" esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -3522,9 +3535,9 @@ for-each@^0.3.3: is-callable "^1.1.3" foreground-child@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" - integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== dependencies: cross-spawn "^7.0.0" signal-exit "^4.0.1" @@ -3563,7 +3576,7 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: +function.prototype.name@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== @@ -3628,9 +3641,9 @@ glob-parent@^6.0.2: is-glob "^4.0.3" glob@^10.3.10: - version "10.4.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" - integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -3818,11 +3831,6 @@ hastscript@^6.0.0: property-information "^5.0.0" space-separated-tokens "^1.0.0" -hex-to-rgb@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hex-to-rgb/-/hex-to-rgb-1.0.1.tgz#100b9df126b32f2762adf8486be68c65ef8ed2a4" - integrity sha512-2GsESdjpJIrRL8I7iPOp0r0wczJBk++Q/zFrviNaTRPWMIgk5bPqDmKPUvRHN0SZrYT9N1e+xZU1niYJ6QxzMg== - highlight.js@^10.4.1, highlight.js@~10.7.0: version "10.7.3" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" @@ -3876,7 +3884,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@~2.0.1: +inherits@2, inherits@^2.0.4, inherits@~2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3886,7 +3894,7 @@ inline-style-parser@0.2.3: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.3.tgz#e35c5fb45f3a83ed7849fe487336eb7efa25971c" integrity sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g== -inline-style-prefixer@^7.0.0: +inline-style-prefixer@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz#9310f3cfa2c6f3901d1480f373981c02691781e8" integrity sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw== @@ -3991,9 +3999,9 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-core-module@^2.13.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" - integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== dependencies: hasown "^2.0.2" @@ -4182,9 +4190,9 @@ iterator.prototype@^1.1.2: set-function-name "^2.0.1" jackspeak@^3.1.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.0.tgz#a75763ff36ad778ede6a156d8ee8b124de445b4a" - integrity sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw== + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -4195,6 +4203,11 @@ jiti@^1.21.0: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +js-cookie@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414" + integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -4340,9 +4353,9 @@ lowlight@^1.17.0: highlight.js "~10.7.0" lru-cache@^10.2.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.3.0.tgz#4a4aaf10c84658ab70f79a85a9a3f1e1fb11196b" - integrity sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ== + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^5.1.1: version "5.1.1" @@ -4574,9 +4587,9 @@ micromark-core-commonmark@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-gfm-autolink-literal@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz#f1e50b42e67d441528f39a67133eddde2bbabfd9" - integrity sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== dependencies: micromark-util-character "^2.0.0" micromark-util-sanitize-uri "^2.0.0" @@ -4584,9 +4597,9 @@ micromark-extension-gfm-autolink-literal@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-gfm-footnote@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz#91afad310065a94b636ab1e9dab2c60d1aab953c" - integrity sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== dependencies: devlop "^1.0.0" micromark-core-commonmark "^2.0.0" @@ -4889,16 +4902,16 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nano-css@^5.6.1: - version "5.6.1" - resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.1.tgz#964120cb1af6cccaa6d0717a473ccd876b34c197" - integrity sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw== +nano-css@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.2.tgz#584884ddd7547278f6d6915b6805069742679a32" + integrity sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" css-tree "^1.1.2" csstype "^3.1.2" fastest-stable-stringify "^2.0.2" - inline-style-prefixer "^7.0.0" + inline-style-prefixer "^7.0.1" rtl-css-js "^1.16.1" stacktrace-js "^2.0.2" stylis "^4.3.0" @@ -4913,38 +4926,52 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -neo4j-driver-bolt-connection@5.22.0: - version "5.22.0" - resolved "https://registry.yarnpkg.com/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-5.22.0.tgz#a6d9b2b52248e533afd73ffa7e5adf8c83f7b74b" - integrity sha512-tpw4URUn8AkpIPaUnz79B3X9hypZYnTKlm7IPrThKNIEOM8U2hUV7/mLoNcaqX39ebiWbuFy4EKK0DOQ+fdd+g== +neo4j-driver-bolt-connection@5.23.0: + version "5.23.0" + resolved "https://registry.yarnpkg.com/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-5.23.0.tgz#2d3e988d3f9f1f0eef92c5a8533923af7036ae87" + integrity sha512-d8aLeeS33KMmUnGCGj/U07AUOch8IVc6hA8Lit/4ExaHnhRoSgEOPAK6/RAbYAfABlVqFfErtV85s1OnrJu64g== dependencies: buffer "^6.0.3" - neo4j-driver-core "5.22.0" + neo4j-driver-core "5.23.0" string_decoder "^1.3.0" -neo4j-driver-core@5.22.0: - version "5.22.0" - resolved "https://registry.yarnpkg.com/neo4j-driver-core/-/neo4j-driver-core-5.22.0.tgz#abd9237556fa534a82c72341095c9dcef804259a" - integrity sha512-tta/zBfbjm24uGgumckYgVxqnTDenPNo2BQwy+eOD7H2tn9oP3U/OikzOvQuRzfRkRtag0ynrTjXw0JsAXZJkw== +neo4j-driver-core@5.23.0: + version "5.23.0" + resolved "https://registry.yarnpkg.com/neo4j-driver-core/-/neo4j-driver-core-5.23.0.tgz#8de25804c50a35cdbc7370de4a833ba63fe8453d" + integrity sha512-kypv0cym8vB9t36MRKSt/mtNZuvCACg6S47yJMDjJx5XSWx69QbBaLNvoa/2//7NPCCj0Fn0i5HCKqBkcBRa2Q== neo4j-driver@^5.14.0: - version "5.22.0" - resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-5.22.0.tgz#6c537ddb1555c05a153069b6c731e827de70fce1" - integrity sha512-DmyUBUMZWOGYIJCYNdze3pCqJE7ZcK3wGORt+WG+yfWnhWUwLS8tT8N4I8FboSl7i7i3X710h6vhWh333n7CNQ== + version "5.23.0" + resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-5.23.0.tgz#fd13a5e5dcd1dc74e4778d2f34a6a6e8b91ab4f4" + integrity sha512-jqAyvIIpD1dl0+GuSXfF7Hzy+bscBIRvO2hrMqQji4Gq2baBaRMcsG/8CRhkF7Hm1bUeBUnnArnUNUGO/c+kow== dependencies: - neo4j-driver-bolt-connection "5.22.0" - neo4j-driver-core "5.22.0" + neo4j-driver-bolt-connection "5.23.0" + neo4j-driver-core "5.23.0" rxjs "^7.8.1" +new-date@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/new-date/-/new-date-1.0.3.tgz#a5956086d3f5ed43d0b210d87a10219ccb7a2326" + integrity sha512-0fsVvQPbo2I18DT2zVHpezmeeNYV2JaJSrseiHLc17GNOxJzUdx5mvSigPu8LtIfZSij5i1wXnXFspEs2CD6hA== + dependencies: + "@segment/isodate" "1.0.3" + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-pid-controller@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/node-pid-controller/-/node-pid-controller-1.0.1.tgz#779d1a19c4a3a54284ed0d50da83bf4125f2abf5" integrity sha512-36gdeRz2emhIsznLpXksJSqmR13NU3vR+rkRlfHWCGGlZu9fK+dcTPRpud0FiH6b2Nhwm0kbcVk7vXFlg8Sw1w== -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -4956,6 +4983,11 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== +obj-case@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/obj-case/-/obj-case-0.2.1.tgz#13a554d04e5ca32dfd9d566451fd2b0e11007f1a" + integrity sha512-PquYBBTy+Y6Ob/O2574XHhDtHJlV1cJHMCgW+rDRc9J5hhmRelJB3k5dTK/3cVmFVtzvAKuENeuLpoyTzMzkOg== + object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5005,15 +5037,6 @@ object.fromentries@^2.0.8: es-abstract "^1.23.2" es-object-atoms "^1.0.0" -object.hasown@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" - integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== - dependencies: - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - object.values@^1.1.6, object.values@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" @@ -5192,16 +5215,16 @@ postcss-load-config@^4.0.1: yaml "^2.3.4" postcss-nested@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" - integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== dependencies: - postcss-selector-parser "^6.0.11" + postcss-selector-parser "^6.1.1" -postcss-selector-parser@^6.0.11: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" - integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38" + integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -5212,9 +5235,9 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.4.23, postcss@^8.4.27, postcss@^8.4.33: - version "8.4.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" - integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== + version "8.4.41" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" + integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -5399,7 +5422,7 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^18.2.0: +react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -5439,19 +5462,19 @@ react-refresh@^0.14.2: integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== react-router-dom@^6.23.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.24.1.tgz#b1a22f7d6c5a1bfce30732bd370713f991ab4de4" - integrity sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg== + version "6.26.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.26.0.tgz#8debe13295c58605c04f93018d659a763245e58c" + integrity sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ== dependencies: - "@remix-run/router" "1.17.1" - react-router "6.24.1" + "@remix-run/router" "1.19.0" + react-router "6.26.0" -react-router@6.24.1, react-router@^6.23.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.24.1.tgz#5a3bbba0000afba68d42915456ca4c806f37a7de" - integrity sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg== +react-router@6.26.0, react-router@^6.23.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.26.0.tgz#d5af4c46835b202348ef2b7ddacd32a2db539fde" + integrity sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg== dependencies: - "@remix-run/router" "1.17.1" + "@remix-run/router" "1.19.0" react-select@5.7.0: version "5.7.0" @@ -5529,9 +5552,9 @@ react-universal-interface@^0.6.2: integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== react-use@^17.4.0: - version "17.5.0" - resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.5.0.tgz#1fae45638828a338291efa0f0c61862db7ee6442" - integrity sha512-PbfwSPMwp/hoL847rLnm/qkjg3sTRCvn6YhUZiHaUa3FA6/aNoFX79ul5Xt70O1rK+9GxSVqkY0eTwMdsR/bWg== + version "17.5.1" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.5.1.tgz#19fc2ae079775d8450339e9fa8dbe25b17f2263c" + integrity sha512-LG/uPEVRflLWMwi3j/sZqR00nF6JGqTTDblkXK2nzXsIvij06hXl1V/MZIlwj1OKIQUtlh1l9jK8gLsRyCQxMg== dependencies: "@types/js-cookie" "^2.2.6" "@xobotyi/scrollbar-width" "^1.9.5" @@ -5539,7 +5562,7 @@ react-use@^17.4.0: fast-deep-equal "^3.1.3" fast-shallow-equal "^1.0.0" js-cookie "^2.2.1" - nano-css "^5.6.1" + nano-css "^5.6.2" react-universal-interface "^0.6.2" resize-observer-polyfill "^1.5.1" screenfull "^5.1.0" @@ -5778,9 +5801,9 @@ semver@^6.3.1: integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.5.4: - version "7.6.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" - integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== set-function-length@^1.2.1: version "1.2.2" @@ -5957,6 +5980,14 @@ string.prototype.matchall@^4.0.11: set-function-name "^2.0.2" side-channel "^1.0.6" +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" @@ -6091,16 +6122,14 @@ tabbable@^6.0.1: integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== tailwind-merge@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.3.0.tgz#27d2134fd00a1f77eca22bcaafdd67055917d286" - integrity sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA== - dependencies: - "@babel/runtime" "^7.24.1" + version "2.4.0" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.4.0.tgz#1345209dc1f484f15159c9180610130587703042" + integrity sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A== tailwindcss@^3.4.1: - version "3.4.4" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.4.tgz#351d932273e6abfa75ce7d226b5bf3a6cb257c05" - integrity sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A== + version "3.4.9" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.9.tgz#9e04cddce1924d530df62af37d3520f0e2a9d85e" + integrity sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" @@ -6157,7 +6186,7 @@ through2@^0.6.3: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" -tinycolor2@^1.4.2: +tinycolor2@1.6.0, tinycolor2@^1.4.2: version "1.6.0" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== @@ -6184,6 +6213,11 @@ toggle-selection@^1.0.6: resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + trim-lines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" @@ -6209,7 +6243,7 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.4.1: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== @@ -6271,9 +6305,9 @@ typed-array-length@^1.0.6: possible-typed-array-names "^1.0.0" typescript@^5.0.2: - version "5.5.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" - integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== unbox-primitive@^1.0.2: version "1.0.2" @@ -6290,6 +6324,16 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +unfetch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-3.1.2.tgz#dc271ef77a2800768f7b459673c5604b5101ef77" + integrity sha512-L0qrK7ZeAudGiKYw6nzFjnJ2D5WHblUBwmHIqtPS6oKUd+Hcpk7/hKsSmcHsTlpd1TbTNsiRBUKRq3bHLNIqIw== + +unfetch@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + unified@^11.0.0: version "11.0.5" resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" @@ -6349,7 +6393,7 @@ unist-util-visit@^5.0.0: unist-util-is "^6.0.0" unist-util-visit-parents "^6.0.0" -update-browserslist-db@^1.0.16: +update-browserslist-db@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== @@ -6394,6 +6438,11 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuid@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" @@ -6408,9 +6457,9 @@ vfile-message@^4.0.0: unist-util-stringify-position "^4.0.0" vfile@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.1.tgz#1e8327f41eac91947d4fe9d237a2dd9209762536" - integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw== + version "6.0.2" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.2.tgz#ef49548ea3d270097a67011921411130ceae7deb" + integrity sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg== dependencies: "@types/unist" "^3.0.0" unist-util-stringify-position "^4.0.0" @@ -6439,6 +6488,19 @@ warning@^4.0.2: dependencies: loose-envify "^1.0.0" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -6451,12 +6513,12 @@ which-boxed-primitive@^1.0.2: is-symbol "^1.0.3" which-builtin-type@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" - integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + version "1.1.4" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3" + integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w== dependencies: - function.prototype.name "^1.1.5" - has-tostringtag "^1.0.0" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" is-async-function "^2.0.0" is-date-object "^1.0.5" is-finalizationregistry "^1.0.2" @@ -6465,10 +6527,10 @@ which-builtin-type@^1.1.3: is-weakref "^1.0.2" isarray "^2.0.5" which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.9" + which-collection "^1.0.2" + which-typed-array "^1.1.15" -which-collection@^1.0.1: +which-collection@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== @@ -6478,7 +6540,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.2" is-weakset "^2.0.3" -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: +which-typed-array@^1.1.14, which-typed-array@^1.1.15: version "1.1.15" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== @@ -6540,9 +6602,9 @@ yaml@^1.10.0: integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yaml@^2.3.4: - version "2.4.5" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e" - integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== + version "2.5.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d" + integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw== yocto-queue@^0.1.0: version "0.1.0" @@ -6552,4 +6614,4 @@ yocto-queue@^0.1.0: zwitch@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== \ No newline at end of file