From 931c56182e70d9fdacafdc6474b9fa0df14e4023 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sun, 13 Oct 2024 03:02:29 -0700 Subject: [PATCH 01/71] Fix default chat model to use user model if no server chat model set - Advanced chat model should also fallback to user chat model if set - Get conversation config should falback to user chat model if set These assume no server chat model settings is configured --- src/khoj/database/adapters/__init__.py | 18 +++++++++--------- src/khoj/processor/tools/online_search.py | 2 -- src/khoj/routers/api_chat.py | 2 -- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 2309bcd4..182ce701 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -939,21 +939,21 @@ class ConversationAdapters: def get_conversation_config(user: KhojUser): subscribed = is_user_subscribed(user) if not subscribed: - return ConversationAdapters.get_default_conversation_config() + return ConversationAdapters.get_default_conversation_config(user) config = UserConversationConfig.objects.filter(user=user).first() if config: return config.setting - return ConversationAdapters.get_advanced_conversation_config() + return ConversationAdapters.get_advanced_conversation_config(user) @staticmethod async def aget_conversation_config(user: KhojUser): subscribed = await ais_user_subscribed(user) if not subscribed: - return await ConversationAdapters.aget_default_conversation_config() + return await ConversationAdapters.aget_default_conversation_config(user) config = await UserConversationConfig.objects.filter(user=user).prefetch_related("setting").afirst() if config: return config.setting - return ConversationAdapters.aget_advanced_conversation_config() + return ConversationAdapters.aget_advanced_conversation_config(user) @staticmethod async def aget_voice_model_config(user: KhojUser) -> Optional[VoiceModelOption]: @@ -1014,22 +1014,22 @@ class ConversationAdapters: return await ChatModelOptions.objects.filter().prefetch_related("openai_config").afirst() @staticmethod - def get_advanced_conversation_config(): + def get_advanced_conversation_config(user: KhojUser): server_chat_settings = ServerChatSettings.objects.first() if server_chat_settings is not None and server_chat_settings.chat_advanced is not None: return server_chat_settings.chat_advanced - return ConversationAdapters.get_default_conversation_config() + return ConversationAdapters.get_default_conversation_config(user) @staticmethod - async def aget_advanced_conversation_config(): + async def aget_advanced_conversation_config(user: KhojUser = None): server_chat_settings: ServerChatSettings = ( await ServerChatSettings.objects.filter() .prefetch_related("chat_advanced", "chat_advanced__openai_config") .afirst() ) - if server_chat_settings is not None or server_chat_settings.chat_advanced is not None: + if server_chat_settings is not None and server_chat_settings.chat_advanced is not None: return server_chat_settings.chat_advanced - return await ConversationAdapters.aget_default_conversation_config() + return await ConversationAdapters.aget_default_conversation_config(user) @staticmethod def create_conversation_from_public_conversation( diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index 16539b5c..f5cb3c12 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -53,7 +53,6 @@ async def search_online( conversation_history: dict, location: LocationData, user: KhojUser, - subscribed: bool = False, send_status_func: Optional[Callable] = None, custom_filters: List[str] = [], uploaded_image_url: str = None, @@ -141,7 +140,6 @@ async def read_webpages( conversation_history: dict, location: LocationData, user: KhojUser, - subscribed: bool = False, send_status_func: Optional[Callable] = None, uploaded_image_url: str = None, agent: Agent = None, diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 2674836d..a67f4936 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -885,7 +885,6 @@ async def chat( meta_log, location, user, - subscribed, partial(send_event, ChatEvent.STATUS), custom_filters, uploaded_image_url=uploaded_image_url, @@ -910,7 +909,6 @@ async def chat( meta_log, location, user, - subscribed, partial(send_event, ChatEvent.STATUS), uploaded_image_url=uploaded_image_url, agent=agent, From 6c5b3625515ada237f620297e07d7023d501d7c5 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Mon, 14 Oct 2024 17:15:26 -0700 Subject: [PATCH 02/71] Remove deprecated GET chat API endpoint --- src/khoj/routers/api_chat.py | 479 ----------------------------------- 1 file changed, 479 deletions(-) diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index a67f4936..228d081c 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -1046,482 +1046,3 @@ async def chat( response_iterator = event_generator(q, image=image) response_data = await read_chat_stream(response_iterator) return Response(content=json.dumps(response_data), media_type="application/json", status_code=200) - - -# Deprecated API. Remove by end of September 2024 -@api_chat.get("") -@requires(["authenticated"]) -async def get_chat( - request: Request, - common: CommonQueryParams, - q: str, - n: int = 7, - d: float = None, - stream: Optional[bool] = False, - title: Optional[str] = None, - conversation_id: Optional[str] = None, - city: Optional[str] = None, - region: Optional[str] = None, - country: Optional[str] = None, - timezone: Optional[str] = None, - image: Optional[str] = None, - rate_limiter_per_minute=Depends( - ApiUserRateLimiter(requests=60, subscribed_requests=60, window=60, slug="chat_minute") - ), - rate_limiter_per_day=Depends( - ApiUserRateLimiter(requests=600, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day") - ), -): - # Issue a deprecation warning - warnings.warn( - "The 'get_chat' API endpoint is deprecated. It will be removed by the end of September 2024.", - DeprecationWarning, - stacklevel=2, - ) - - async def event_generator(q: str, image: str): - start_time = time.perf_counter() - ttft = None - chat_metadata: dict = {} - connection_alive = True - user: KhojUser = request.user.object - subscribed: bool = has_required_scope(request, ["premium"]) - event_delimiter = "␃🔚␗" - q = unquote(q) - nonlocal conversation_id - - uploaded_image_url = None - if image: - decoded_string = unquote(image) - base64_data = decoded_string.split(",", 1)[1] - image_bytes = base64.b64decode(base64_data) - webp_image_bytes = convert_image_to_webp(image_bytes) - try: - uploaded_image_url = upload_image_to_bucket(webp_image_bytes, request.user.object.id) - except: - uploaded_image_url = None - - async def send_event(event_type: ChatEvent, data: str | dict): - nonlocal connection_alive, ttft - if not connection_alive or await request.is_disconnected(): - connection_alive = False - logger.warn(f"User {user} disconnected from {common.client} client") - return - try: - if event_type == ChatEvent.END_LLM_RESPONSE: - collect_telemetry() - if event_type == ChatEvent.START_LLM_RESPONSE: - ttft = time.perf_counter() - start_time - if event_type == ChatEvent.MESSAGE: - yield data - elif event_type == ChatEvent.REFERENCES or stream: - yield json.dumps({"type": event_type.value, "data": data}, ensure_ascii=False) - except asyncio.CancelledError as e: - connection_alive = False - logger.warn(f"User {user} disconnected from {common.client} client: {e}") - return - except Exception as e: - connection_alive = False - logger.error(f"Failed to stream chat API response to {user} on {common.client}: {e}", exc_info=True) - return - finally: - yield event_delimiter - - async def send_llm_response(response: str): - async for result in send_event(ChatEvent.START_LLM_RESPONSE, ""): - yield result - async for result in send_event(ChatEvent.MESSAGE, response): - yield result - async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""): - yield result - - def collect_telemetry(): - # Gather chat response telemetry - nonlocal chat_metadata - latency = time.perf_counter() - start_time - cmd_set = set([cmd.value for cmd in conversation_commands]) - chat_metadata = chat_metadata or {} - chat_metadata["conversation_command"] = cmd_set - chat_metadata["agent"] = conversation.agent.slug if conversation.agent else None - chat_metadata["latency"] = f"{latency:.3f}" - chat_metadata["ttft_latency"] = f"{ttft:.3f}" - - logger.info(f"Chat response time to first token: {ttft:.3f} seconds") - logger.info(f"Chat response total time: {latency:.3f} seconds") - update_telemetry_state( - request=request, - telemetry_type="api", - api="chat", - client=request.user.client_app, - user_agent=request.headers.get("user-agent"), - host=request.headers.get("host"), - metadata=chat_metadata, - ) - - conversation_commands = [get_conversation_command(query=q, any_references=True)] - - conversation = await ConversationAdapters.aget_conversation_by_user( - user, client_application=request.user.client_app, conversation_id=conversation_id, title=title - ) - if not conversation: - async for result in send_llm_response(f"Conversation {conversation_id} not found"): - yield result - return - conversation_id = conversation.id - agent = conversation.agent if conversation.agent else None - - await is_ready_to_chat(user) - - user_name = await aget_user_name(user) - location = None - if city or region or country: - location = LocationData(city=city, region=region, country=country) - - if is_query_empty(q): - async for result in send_llm_response("Please ask your query to get started."): - yield result - return - - user_message_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - meta_log = conversation.conversation_log - is_automated_task = conversation_commands == [ConversationCommand.AutomatedTask] - - if conversation_commands == [ConversationCommand.Default] or is_automated_task: - conversation_commands = await aget_relevant_information_sources( - q, meta_log, is_automated_task, user=user, uploaded_image_url=uploaded_image_url - ) - conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands]) - async for result in send_event( - ChatEvent.STATUS, f"**Chose Data Sources to Search:** {conversation_commands_str}" - ): - yield result - - mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, user, uploaded_image_url) - async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"): - yield result - if mode not in conversation_commands: - conversation_commands.append(mode) - - for cmd in conversation_commands: - await conversation_command_rate_limiter.update_and_check_if_valid(request, cmd) - q = q.replace(f"/{cmd.value}", "").strip() - - used_slash_summarize = conversation_commands == [ConversationCommand.Summarize] - file_filters = conversation.file_filters if conversation else [] - # Skip trying to summarize if - if ( - # summarization intent was inferred - ConversationCommand.Summarize in conversation_commands - # and not triggered via slash command - and not used_slash_summarize - # but we can't actually summarize - and len(file_filters) != 1 - ): - conversation_commands.remove(ConversationCommand.Summarize) - elif ConversationCommand.Summarize in conversation_commands: - response_log = "" - if len(file_filters) == 0: - response_log = "No files selected for summarization. Please add files using the section on the left." - async for result in send_llm_response(response_log): - yield result - elif len(file_filters) > 1: - response_log = "Only one file can be selected for summarization." - async for result in send_llm_response(response_log): - yield result - else: - try: - file_object = await FileObjectAdapters.async_get_file_objects_by_name(user, file_filters[0]) - if len(file_object) == 0: - response_log = "Sorry, we couldn't find the full text of this file. Please re-upload the document and try again." - async for result in send_llm_response(response_log): - yield result - return - contextual_data = " ".join([file.raw_text for file in file_object]) - if not q: - q = "Create a general summary of the file" - async for result in send_event( - ChatEvent.STATUS, f"**Constructing Summary Using:** {file_object[0].file_name}" - ): - yield result - - response = await extract_relevant_summary( - q, - contextual_data, - conversation_history=meta_log, - user=user, - uploaded_image_url=uploaded_image_url, - ) - response_log = str(response) - async for result in send_llm_response(response_log): - yield result - except Exception as e: - response_log = "Error summarizing file." - logger.error(f"Error summarizing file for {user.email}: {e}", exc_info=True) - async for result in send_llm_response(response_log): - yield result - await sync_to_async(save_to_conversation_log)( - q, - response_log, - user, - meta_log, - user_message_time, - intent_type="summarize", - client_application=request.user.client_app, - conversation_id=conversation_id, - uploaded_image_url=uploaded_image_url, - ) - return - - custom_filters = [] - if conversation_commands == [ConversationCommand.Help]: - if not q: - conversation_config = await ConversationAdapters.aget_user_conversation_config(user) - if conversation_config == None: - conversation_config = await ConversationAdapters.aget_default_conversation_config() - model_type = conversation_config.model_type - formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device()) - async for result in send_llm_response(formatted_help): - yield result - return - # Adding specification to search online specifically on khoj.dev pages. - custom_filters.append("site:khoj.dev") - conversation_commands.append(ConversationCommand.Online) - - if ConversationCommand.Automation in conversation_commands: - try: - automation, crontime, query_to_run, subject = await create_automation( - q, timezone, user, request.url, meta_log - ) - except Exception as e: - logger.error(f"Error scheduling task {q} for {user.email}: {e}") - error_message = f"Unable to create automation. Ensure the automation doesn't already exist." - async for result in send_llm_response(error_message): - yield result - return - - llm_response = construct_automation_created_message(automation, crontime, query_to_run, subject) - await sync_to_async(save_to_conversation_log)( - q, - llm_response, - user, - meta_log, - user_message_time, - intent_type="automation", - client_application=request.user.client_app, - conversation_id=conversation_id, - inferred_queries=[query_to_run], - automation_id=automation.id, - uploaded_image_url=uploaded_image_url, - ) - async for result in send_llm_response(llm_response): - yield result - return - - # Gather Context - ## Extract Document References - compiled_references, inferred_queries, defiltered_query = [], [], None - async for result in extract_references_and_questions( - request, - meta_log, - q, - (n or 7), - d, - conversation_id, - conversation_commands, - location, - partial(send_event, ChatEvent.STATUS), - uploaded_image_url=uploaded_image_url, - ): - if isinstance(result, dict) and ChatEvent.STATUS in result: - yield result[ChatEvent.STATUS] - else: - compiled_references.extend(result[0]) - inferred_queries.extend(result[1]) - defiltered_query = result[2] - - if not is_none_or_empty(compiled_references): - headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references])) - # Strip only leading # from headings - headings = headings.replace("#", "") - async for result in send_event(ChatEvent.STATUS, f"**Found Relevant Notes**: {headings}"): - yield result - - online_results: Dict = dict() - - if conversation_commands == [ConversationCommand.Notes] and not await EntryAdapters.auser_has_entries(user): - async for result in send_llm_response(f"{no_entries_found.format()}"): - yield result - return - - if ConversationCommand.Notes in conversation_commands and is_none_or_empty(compiled_references): - conversation_commands.remove(ConversationCommand.Notes) - - ## Gather Online References - if ConversationCommand.Online in conversation_commands: - try: - async for result in search_online( - defiltered_query, - meta_log, - location, - user, - subscribed, - partial(send_event, ChatEvent.STATUS), - custom_filters, - uploaded_image_url=uploaded_image_url, - ): - if isinstance(result, dict) and ChatEvent.STATUS in result: - yield result[ChatEvent.STATUS] - else: - online_results = result - except ValueError as e: - error_message = f"Error searching online: {e}. Attempting to respond without online results" - logger.warning(error_message) - async for result in send_llm_response(error_message): - yield result - return - - ## Gather Webpage References - if ConversationCommand.Webpage in conversation_commands: - try: - async for result in read_webpages( - defiltered_query, - meta_log, - location, - user, - subscribed, - partial(send_event, ChatEvent.STATUS), - uploaded_image_url=uploaded_image_url, - ): - if isinstance(result, dict) and ChatEvent.STATUS in result: - yield result[ChatEvent.STATUS] - else: - direct_web_pages = result - webpages = [] - for query in direct_web_pages: - if online_results.get(query): - online_results[query]["webpages"] = direct_web_pages[query]["webpages"] - else: - online_results[query] = {"webpages": direct_web_pages[query]["webpages"]} - - for webpage in direct_web_pages[query]["webpages"]: - webpages.append(webpage["link"]) - async for result in send_event(ChatEvent.STATUS, f"**Read web pages**: {webpages}"): - yield result - except ValueError as e: - logger.warning( - f"Error directly reading webpages: {e}. Attempting to respond without online results", - exc_info=True, - ) - - ## Send Gathered References - async for result in send_event( - ChatEvent.REFERENCES, - { - "inferredQueries": inferred_queries, - "context": compiled_references, - "onlineContext": online_results, - }, - ): - yield result - - # Generate Output - ## Generate Image Output - if ConversationCommand.Image in conversation_commands: - async for result in text_to_image( - q, - user, - meta_log, - location_data=location, - references=compiled_references, - online_results=online_results, - send_status_func=partial(send_event, ChatEvent.STATUS), - uploaded_image_url=uploaded_image_url, - ): - if isinstance(result, dict) and ChatEvent.STATUS in result: - yield result[ChatEvent.STATUS] - else: - image, status_code, improved_image_prompt, intent_type = result - - if image is None or status_code != 200: - content_obj = { - "content-type": "application/json", - "intentType": intent_type, - "detail": improved_image_prompt, - "image": image, - } - async for result in send_llm_response(json.dumps(content_obj)): - yield result - return - - await sync_to_async(save_to_conversation_log)( - q, - image, - user, - meta_log, - user_message_time, - intent_type=intent_type, - inferred_queries=[improved_image_prompt], - client_application=request.user.client_app, - conversation_id=conversation_id, - compiled_references=compiled_references, - online_results=online_results, - uploaded_image_url=uploaded_image_url, - ) - content_obj = { - "intentType": intent_type, - "inferredQueries": [improved_image_prompt], - "image": image, - } - async for result in send_llm_response(json.dumps(content_obj)): - yield result - return - - ## Generate Text Output - async for result in send_event(ChatEvent.STATUS, f"**Generating a well-informed response**"): - yield result - llm_response, chat_metadata = await agenerate_chat_response( - defiltered_query, - meta_log, - conversation, - compiled_references, - online_results, - inferred_queries, - conversation_commands, - user, - request.user.client_app, - conversation_id, - location, - user_name, - uploaded_image_url, - ) - - # Send Response - async for result in send_event(ChatEvent.START_LLM_RESPONSE, ""): - yield result - - continue_stream = True - iterator = AsyncIteratorWrapper(llm_response) - async for item in iterator: - if item is None: - async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""): - yield result - logger.debug("Finished streaming response") - return - if not connection_alive or not continue_stream: - continue - try: - async for result in send_event(ChatEvent.MESSAGE, f"{item}"): - yield result - except Exception as e: - continue_stream = False - logger.info(f"User {user} disconnected. Emitting rest of responses to clear thread: {e}") - - ## Stream Text Response - if stream: - return StreamingResponse(event_generator(q, image=image), media_type="text/plain") - ## Non-Streaming Text Response - else: - response_iterator = event_generator(q, image=image) - response_data = await read_chat_stream(response_iterator) - return Response(content=json.dumps(response_data), media_type="application/json", status_code=200) From 19c65fb82b601e2fdd53983a098ca67791382714 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 15 Oct 2024 11:49:17 -0700 Subject: [PATCH 03/71] Show user uuid field in django admin panel --- src/khoj/database/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/khoj/database/admin.py b/src/khoj/database/admin.py index ea15760e..3e192952 100644 --- a/src/khoj/database/admin.py +++ b/src/khoj/database/admin.py @@ -69,10 +69,11 @@ class KhojUserAdmin(UserAdmin): "id", "email", "username", + "phone_number", "is_active", + "uuid", "is_staff", "is_superuser", - "phone_number", ) search_fields = ("email", "username", "phone_number", "uuid") filter_horizontal = ("groups", "user_permissions") From 27835628e624fe4c987e9e06b9e35a963dafb7d2 Mon Sep 17 00:00:00 2001 From: Rehan Daphedar Date: Thu, 17 Oct 2024 11:45:43 +0530 Subject: [PATCH 04/71] Fix typo in docs for error 400 fix when self-hosting (#938) --- documentation/docs/get-started/setup.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/get-started/setup.mdx b/documentation/docs/get-started/setup.mdx index 2d9ffc50..2777d1d3 100644 --- a/documentation/docs/get-started/setup.mdx +++ b/documentation/docs/get-started/setup.mdx @@ -240,7 +240,7 @@ Ensure you are using **localhost, not 127.0.0.1**, to access the admin panel to :::info[DISALLOWED HOST or Bad Request (400) Error] You may hit this if you try access Khoj exposed on a custom domain (e.g. 192.168.12.3 or example.com) or over HTTP. -Set the environment variables KHOJ_DOMAIN=your-domain and KHOJ_NO_HTTPS=false if required to avoid this error. +Set the environment variables KHOJ_DOMAIN=your-domain and KHOJ_NO_HTTPS=True if required to avoid this error. ::: :::tip[Note] From 07ab8ab9313bf6bc9be993658c81eb77a6ed53b2 Mon Sep 17 00:00:00 2001 From: sabaimran Date: Thu, 17 Oct 2024 09:00:01 -0700 Subject: [PATCH 05/71] Update handling of gemini response with new API changes. Per documentation: finish_reason (google.ai.generativelanguage_v1beta.types.Candidate.FinishReason): Optional. Output only. The reason why the model stopped generating tokens. If empty, the model has not stopped generating the tokens. --- src/khoj/processor/conversation/google/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/khoj/processor/conversation/google/utils.py b/src/khoj/processor/conversation/google/utils.py index 7ef99a92..5679ba4d 100644 --- a/src/khoj/processor/conversation/google/utils.py +++ b/src/khoj/processor/conversation/google/utils.py @@ -148,6 +148,10 @@ def handle_gemini_response(candidates, prompt_feedback=None): elif candidates[0].finish_reason == FinishReason.SAFETY: message = generate_safety_response(candidates[0].safety_ratings) stopped = True + # Check if finish reason is empty, therefore generation is in progress + elif not candidates[0].finish_reason: + message = None + stopped = False # Check if the response was stopped due to reaching maximum token limit or other reasons elif candidates[0].finish_reason != FinishReason.STOP: message = f"\nI can't talk further about that because of **{candidates[0].finish_reason.name} issue.**" From ea59dde4a0f057a5c1fc82bde71279577b7b90dd Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 16 Oct 2024 03:45:07 -0700 Subject: [PATCH 06/71] Upgrade documentation website dependencies --- documentation/yarn.lock | 1645 ++++++++++++++++++--------------------- 1 file changed, 761 insertions(+), 884 deletions(-) diff --git a/documentation/yarn.lock b/documentation/yarn.lock index 677b8d7a..fd6ac335 100644 --- a/documentation/yarn.lock +++ b/documentation/yarn.lock @@ -163,96 +163,96 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.8.3": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" - integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.25.7", "@babel/code-frame@^7.8.3": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== dependencies: - "@babel/highlight" "^7.24.7" + "@babel/highlight" "^7.25.7" picocolors "^1.0.0" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2", "@babel/compat-data@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" - integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.7", "@babel/compat-data@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402" + integrity sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA== "@babel/core@^7.21.3", "@babel/core@^7.23.3": - 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== + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.8.tgz#a57137d2a51bbcffcfaeba43cb4dd33ae3e0e1c6" + integrity sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^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" + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/helper-compilation-targets" "^7.25.7" + "@babel/helper-module-transforms" "^7.25.7" + "@babel/helpers" "^7.25.7" + "@babel/parser" "^7.25.8" + "@babel/template" "^7.25.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.8" 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.23.3", "@babel/generator@^7.25.0", "@babel/generator@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" - integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== +"@babel/generator@^7.23.3", "@babel/generator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" + integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA== dependencies: - "@babel/types" "^7.25.6" + "@babel/types" "^7.25.7" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" + jsesc "^3.0.2" -"@babel/helper-annotate-as-pure@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" - integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== +"@babel/helper-annotate-as-pure@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz#63f02dbfa1f7cb75a9bdb832f300582f30bb8972" + integrity sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA== dependencies: - "@babel/types" "^7.24.7" + "@babel/types" "^7.25.7" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz#37d66feb012024f2422b762b9b2a7cfe27c7fba3" - integrity sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.7.tgz#d721650c1f595371e0a23ee816f1c3c488c0d622" + integrity sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg== dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8", "@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== +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz#11260ac3322dda0ef53edfae6e97b961449f5fa4" + integrity sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A== dependencies: - "@babel/compat-data" "^7.25.2" - "@babel/helper-validator-option" "^7.24.8" - browserslist "^4.23.1" + "@babel/compat-data" "^7.25.7" + "@babel/helper-validator-option" "^7.25.7" + browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.0", "@babel/helper-create-class-features-plugin@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14" - integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ== +"@babel/helper-create-class-features-plugin@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz#5d65074c76cae75607421c00d6bd517fe1892d6b" + integrity sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-member-expression-to-functions" "^7.24.8" - "@babel/helper-optimise-call-expression" "^7.24.7" - "@babel/helper-replace-supers" "^7.25.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/traverse" "^7.25.4" + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-member-expression-to-functions" "^7.25.7" + "@babel/helper-optimise-call-expression" "^7.25.7" + "@babel/helper-replace-supers" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" + "@babel/traverse" "^7.25.7" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7", "@babel/helper-create-regexp-features-plugin@^7.25.0", "@babel/helper-create-regexp-features-plugin@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz#24c75974ed74183797ffd5f134169316cd1808d9" - integrity sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.7.tgz#dcb464f0e2cdfe0c25cc2a0a59c37ab940ce894e" + integrity sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - regexpu-core "^5.3.1" + "@babel/helper-annotate-as-pure" "^7.25.7" + regexpu-core "^6.1.1" semver "^6.3.1" "@babel/helper-define-polyfill-provider@^0.6.2": @@ -266,192 +266,171 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-member-expression-to-functions@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" - integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA== +"@babel/helper-member-expression-to-functions@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz#541a33b071f0355a63a0fa4bdf9ac360116b8574" + integrity sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA== dependencies: - "@babel/traverse" "^7.24.8" - "@babel/types" "^7.24.8" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.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" - integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== +"@babel/helper-module-imports@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472" + integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw== dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" -"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.0", "@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== +"@babel/helper-module-transforms@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz#2ac9372c5e001b19bc62f1fe7d96a18cb0901d1a" + integrity sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ== dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-validator-identifier" "^7.24.7" - "@babel/traverse" "^7.25.2" + "@babel/helper-module-imports" "^7.25.7" + "@babel/helper-simple-access" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + "@babel/traverse" "^7.25.7" -"@babel/helper-optimise-call-expression@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" - integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== +"@babel/helper-optimise-call-expression@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz#1de1b99688e987af723eed44fa7fc0ee7b97d77a" + integrity sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng== dependencies: - "@babel/types" "^7.24.7" + "@babel/types" "^7.25.7" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - 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-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.7", "@babel/helper-plugin-utils@^7.8.0": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c" + integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw== -"@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz#d2f0fbba059a42d68e5e378feaf181ef6055365e" - integrity sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw== +"@babel/helper-remap-async-to-generator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.7.tgz#9efdc39df5f489bcd15533c912b6c723a0a65021" + integrity sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-wrap-function" "^7.25.0" - "@babel/traverse" "^7.25.0" + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-wrap-function" "^7.25.7" + "@babel/traverse" "^7.25.7" -"@babel/helper-replace-supers@^7.24.7", "@babel/helper-replace-supers@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" - integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg== +"@babel/helper-replace-supers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz#38cfda3b6e990879c71d08d0fef9236b62bd75f5" + integrity sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw== dependencies: - "@babel/helper-member-expression-to-functions" "^7.24.8" - "@babel/helper-optimise-call-expression" "^7.24.7" - "@babel/traverse" "^7.25.0" + "@babel/helper-member-expression-to-functions" "^7.25.7" + "@babel/helper-optimise-call-expression" "^7.25.7" + "@babel/traverse" "^7.25.7" -"@babel/helper-simple-access@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" - integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== +"@babel/helper-simple-access@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz#5eb9f6a60c5d6b2e0f76057004f8dacbddfae1c0" + integrity sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ== dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" -"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" - integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== +"@babel/helper-skip-transparent-expression-wrappers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz#382831c91038b1a6d32643f5f49505b8442cb87c" + integrity sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA== dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" -"@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-string-parser@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" + integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== -"@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-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== -"@babel/helper-validator-option@^7.24.7", "@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/helper-validator-option@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729" + integrity sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ== -"@babel/helper-wrap-function@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz#dab12f0f593d6ca48c0062c28bcfb14ebe812f81" - integrity sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ== +"@babel/helper-wrap-function@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.7.tgz#9f6021dd1c4fdf4ad515c809967fc4bac9a70fe7" + integrity sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg== dependencies: - "@babel/template" "^7.25.0" - "@babel/traverse" "^7.25.0" - "@babel/types" "^7.25.0" + "@babel/template" "^7.25.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" -"@babel/helpers@^7.25.0": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" - integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== +"@babel/helpers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.7.tgz#091b52cb697a171fe0136ab62e54e407211f09c2" + integrity sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA== dependencies: - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" -"@babel/highlight@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== dependencies: - "@babel/helper-validator-identifier" "^7.24.7" + "@babel/helper-validator-identifier" "^7.25.7" chalk "^2.4.2" js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.25.0", "@babel/parser@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" - integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== +"@babel/parser@^7.25.7", "@babel/parser@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" + integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== dependencies: - "@babel/types" "^7.25.6" + "@babel/types" "^7.25.8" -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3": - version "7.25.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz#dca427b45a6c0f5c095a1c639dfe2476a3daba7f" - integrity sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA== +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.7.tgz#93969ac50ef4d68b2504b01b758af714e4cbdd64" + integrity sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/traverse" "^7.25.3" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/traverse" "^7.25.7" -"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz#cd0c583e01369ef51676bdb3d7b603e17d2b3f73" - integrity sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA== +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.7.tgz#a338d611adb9dcd599b8b1efa200c88ebeffe046" + integrity sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz#749bde80356b295390954643de7635e0dffabe73" - integrity sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.7.tgz#c5f755e911dfac7ef6957300c0f9c4a8c18c06f4" + integrity sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz#e4eabdd5109acc399b38d7999b2ef66fc2022f89" - integrity sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.7.tgz#3b7ea04492ded990978b6deaa1dfca120ad4455a" + integrity sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-transform-optional-chaining" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" + "@babel/plugin-transform-optional-chaining" "^7.25.7" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz#3a82a70e7cb7294ad2559465ebcb871dfbf078fb" - integrity sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.7.tgz#9622b1d597a703aa3a921e6f58c9c2d9a028d2c5" + integrity sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/traverse" "^7.25.0" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/traverse" "^7.25.7" "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -459,110 +438,33 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== +"@babel/plugin-syntax-import-assertions@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.7.tgz#8ce248f9f4ed4b7ed4cb2e0eb4ed9efd9f52921f" + integrity sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-syntax-import-assertions@^7.24.7": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz#bb918905c58711b86f9710d74a3744b6c56573b5" - integrity sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ== +"@babel/plugin-syntax-import-attributes@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz#d78dd0499d30df19a598e63ab895e21b909bc43f" + integrity sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz#6d4c78f042db0e82fd6436cd65fec5dc78ad2bde" - integrity sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ== +"@babel/plugin-syntax-jsx@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz#5352d398d11ea5e7ef330c854dea1dae0bf18165" + integrity sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== +"@babel/plugin-syntax-typescript@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.7.tgz#bfc05b0cc31ebd8af09964650cee723bb228108b" + integrity sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" - integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.24.7": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff" - integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" @@ -572,549 +474,522 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz#4f6886c11e423bd69f3ce51dbf42424a5f275514" - integrity sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ== +"@babel/plugin-transform-arrow-functions@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.7.tgz#1b9ed22e6890a0e9ff470371c73b8c749bcec386" + integrity sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-async-generator-functions@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz#2afd4e639e2d055776c9f091b6c0c180ed8cf083" - integrity sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg== +"@babel/plugin-transform-async-generator-functions@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.8.tgz#3331de02f52cc1f2c75b396bec52188c85b0b1ec" + integrity sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-remap-async-to-generator" "^7.25.0" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/traverse" "^7.25.4" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-remap-async-to-generator" "^7.25.7" + "@babel/traverse" "^7.25.7" -"@babel/plugin-transform-async-to-generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz#72a3af6c451d575842a7e9b5a02863414355bdcc" - integrity sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA== +"@babel/plugin-transform-async-to-generator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.7.tgz#a44c7323f8d4285a6c568dd43c5c361d6367ec52" + integrity sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg== dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-remap-async-to-generator" "^7.24.7" + "@babel/helper-module-imports" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-remap-async-to-generator" "^7.25.7" -"@babel/plugin-transform-block-scoped-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz#a4251d98ea0c0f399dafe1a35801eaba455bbf1f" - integrity sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ== +"@babel/plugin-transform-block-scoped-functions@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.7.tgz#e0b8843d5571719a2f1bf7e284117a3379fcc17c" + integrity sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-block-scoping@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz#23a6ed92e6b006d26b1869b1c91d1b917c2ea2ac" - integrity sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ== +"@babel/plugin-transform-block-scoping@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.7.tgz#6dab95e98adf780ceef1b1c3ab0e55cd20dd410a" + integrity sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-class-properties@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz#bae7dbfcdcc2e8667355cd1fb5eda298f05189fd" - integrity sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g== +"@babel/plugin-transform-class-properties@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.7.tgz#a389cfca7a10ac80e3ff4c75fca08bd097ad1523" + integrity sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g== dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.4" - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-create-class-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-class-static-block@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" - integrity sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ== +"@babel/plugin-transform-class-static-block@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.8.tgz#a8af22028920fe404668031eceb4c3aadccb5262" + integrity sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/helper-create-class-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-classes@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz#d29dbb6a72d79f359952ad0b66d88518d65ef89a" - integrity sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg== +"@babel/plugin-transform-classes@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.7.tgz#5103206cf80d02283bbbd044509ea3b65d0906bb" + integrity sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-compilation-targets" "^7.25.2" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-replace-supers" "^7.25.0" - "@babel/traverse" "^7.25.4" + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-compilation-targets" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-replace-supers" "^7.25.7" + "@babel/traverse" "^7.25.7" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz#4cab3214e80bc71fae3853238d13d097b004c707" - integrity sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ== +"@babel/plugin-transform-computed-properties@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.7.tgz#7f621f0aa1354b5348a935ab12e3903842466f65" + integrity sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/template" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/template" "^7.25.7" -"@babel/plugin-transform-destructuring@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz#c828e814dbe42a2718a838c2a2e16a408e055550" - integrity sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ== +"@babel/plugin-transform-destructuring@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.7.tgz#f6f26a9feefb5aa41fd45b6f5838901b5333d560" + integrity sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-dotall-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz#5f8bf8a680f2116a7207e16288a5f974ad47a7a0" - integrity sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw== +"@babel/plugin-transform-dotall-regex@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.7.tgz#9d775c4a3ff1aea64045300fcd4309b4a610ef02" + integrity sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-create-regexp-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-duplicate-keys@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz#dd20102897c9a2324e5adfffb67ff3610359a8ee" - integrity sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw== +"@babel/plugin-transform-duplicate-keys@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.7.tgz#fbba7d1155eab76bd4f2a038cbd5d65883bd7a93" + integrity sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz#809af7e3339466b49c034c683964ee8afb3e2604" - integrity sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g== +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.7.tgz#102b31608dcc22c08fbca1894e104686029dc141" + integrity sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.0" - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-create-regexp-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-dynamic-import@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" - integrity sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg== +"@babel/plugin-transform-dynamic-import@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.8.tgz#f1edbe75b248cf44c70c8ca8ed3818a668753aaa" + integrity sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-exponentiation-operator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz#b629ee22645f412024297d5245bce425c31f9b0d" - integrity sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ== +"@babel/plugin-transform-exponentiation-operator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.7.tgz#5961a3a23a398faccd6cddb34a2182807d75fb5f" + integrity sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-export-namespace-from@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz#176d52d8d8ed516aeae7013ee9556d540c53f197" - integrity sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA== +"@babel/plugin-transform-export-namespace-from@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.8.tgz#d1988c3019a380b417e0516418b02804d3858145" + integrity sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-for-of@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz#f25b33f72df1d8be76399e1b8f3f9d366eb5bc70" - integrity sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g== +"@babel/plugin-transform-for-of@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.7.tgz#0acfea0f27aa290818b5b48a5a44b3f03fc13669" + integrity sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" -"@babel/plugin-transform-function-name@^7.25.1": - version "7.25.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz#b85e773097526c1a4fc4ba27322748643f26fc37" - integrity sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA== +"@babel/plugin-transform-function-name@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.7.tgz#7e394ccea3693902a8b50ded8b6ae1fa7b8519fd" + integrity sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ== dependencies: - "@babel/helper-compilation-targets" "^7.24.8" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/traverse" "^7.25.1" + "@babel/helper-compilation-targets" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/traverse" "^7.25.7" -"@babel/plugin-transform-json-strings@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz#f3e9c37c0a373fee86e36880d45b3664cedaf73a" - integrity sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw== +"@babel/plugin-transform-json-strings@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.8.tgz#6fb3ec383a2ea92652289fdba653e3f9de722694" + integrity sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-literals@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz#deb1ad14fc5490b9a65ed830e025bca849d8b5f3" - integrity sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw== +"@babel/plugin-transform-literals@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.7.tgz#70cbdc742f2cfdb1a63ea2cbd018d12a60b213c3" + integrity sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-logical-assignment-operators@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz#a58fb6eda16c9dc8f9ff1c7b1ba6deb7f4694cb0" - integrity sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw== +"@babel/plugin-transform-logical-assignment-operators@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.8.tgz#01868ff92daa9e525b4c7902aa51979082a05710" + integrity sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-member-expression-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz#3b4454fb0e302e18ba4945ba3246acb1248315df" - integrity sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw== +"@babel/plugin-transform-member-expression-literals@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.7.tgz#0a36c3fbd450cc9e6485c507f005fa3d1bc8fca5" + integrity sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-modules-amd@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz#65090ed493c4a834976a3ca1cde776e6ccff32d7" - integrity sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg== +"@babel/plugin-transform-modules-amd@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.7.tgz#bb4e543b5611f6c8c685a2fd485408713a3adf3d" + integrity sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA== dependencies: - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-module-transforms" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-modules-commonjs@^7.24.7", "@babel/plugin-transform-modules-commonjs@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" - integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA== +"@babel/plugin-transform-modules-commonjs@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz#173f0c791bb7407c092ce6d77ee90eb3f2d1d2fd" + integrity sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg== dependencies: - "@babel/helper-module-transforms" "^7.24.8" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-module-transforms" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-simple-access" "^7.25.7" -"@babel/plugin-transform-modules-systemjs@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz#8f46cdc5f9e5af74f3bd019485a6cbe59685ea33" - integrity sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw== +"@babel/plugin-transform-modules-systemjs@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.7.tgz#8b14d319a177cc9c85ef8b0512afd429d9e2e60b" + integrity sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g== dependencies: - "@babel/helper-module-transforms" "^7.25.0" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-validator-identifier" "^7.24.7" - "@babel/traverse" "^7.25.0" + "@babel/helper-module-transforms" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + "@babel/traverse" "^7.25.7" -"@babel/plugin-transform-modules-umd@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz#edd9f43ec549099620df7df24e7ba13b5c76efc8" - integrity sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A== +"@babel/plugin-transform-modules-umd@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.7.tgz#00ee7a7e124289549381bfb0e24d87fd7f848367" + integrity sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw== dependencies: - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-module-transforms" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz#9042e9b856bc6b3688c0c2e4060e9e10b1460923" - integrity sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g== +"@babel/plugin-transform-named-capturing-groups-regex@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.7.tgz#a2f3f6d7f38693b462542951748f0a72a34d196d" + integrity sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-create-regexp-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-new-target@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz#31ff54c4e0555cc549d5816e4ab39241dfb6ab00" - integrity sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA== +"@babel/plugin-transform-new-target@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.7.tgz#52b2bde523b76c548749f38dc3054f1f45e82bc9" + integrity sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz#1de4534c590af9596f53d67f52a92f12db984120" - integrity sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ== +"@babel/plugin-transform-nullish-coalescing-operator@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.8.tgz#befb4900c130bd52fccf2b926314557987f1b552" + integrity sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-numeric-separator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz#bea62b538c80605d8a0fac9b40f48e97efa7de63" - integrity sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA== +"@babel/plugin-transform-numeric-separator@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.8.tgz#91e370486371637bd42161052f2602c701386891" + integrity sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-object-rest-spread@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz#d13a2b93435aeb8a197e115221cab266ba6e55d6" - integrity sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q== +"@babel/plugin-transform-object-rest-spread@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.8.tgz#0904ac16bcce41df4db12d915d6780f85c7fb04b" + integrity sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g== dependencies: - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/helper-compilation-targets" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/plugin-transform-parameters" "^7.25.7" -"@babel/plugin-transform-object-super@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz#66eeaff7830bba945dd8989b632a40c04ed625be" - integrity sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg== +"@babel/plugin-transform-object-super@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.7.tgz#582a9cea8cf0a1e02732be5b5a703a38dedf5661" + integrity sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-replace-supers" "^7.25.7" -"@babel/plugin-transform-optional-catch-binding@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz#00eabd883d0dd6a60c1c557548785919b6e717b4" - integrity sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA== +"@babel/plugin-transform-optional-catch-binding@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.8.tgz#2649b86a3bb202c6894ec81a6ddf41b94d8f3103" + integrity sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz#bb02a67b60ff0406085c13d104c99a835cdf365d" - integrity sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw== +"@babel/plugin-transform-optional-chaining@^7.25.7", "@babel/plugin-transform-optional-chaining@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.8.tgz#f46283b78adcc5b6ab988a952f989e7dce70653f" + integrity sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" -"@babel/plugin-transform-parameters@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz#5881f0ae21018400e320fc7eb817e529d1254b68" - integrity sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA== +"@babel/plugin-transform-parameters@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.7.tgz#80c38b03ef580f6d6bffe1c5254bb35986859ac7" + integrity sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-private-methods@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz#9bbefbe3649f470d681997e0b64a4b254d877242" - integrity sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw== +"@babel/plugin-transform-private-methods@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz#c790a04f837b4bd61d6b0317b43aa11ff67dce80" + integrity sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.4" - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-create-class-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-private-property-in-object@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz#4eec6bc701288c1fab5f72e6a4bbc9d67faca061" - integrity sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA== +"@babel/plugin-transform-private-property-in-object@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.8.tgz#1234f856ce85e061f9688764194e51ea7577c434" + integrity sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-create-class-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-property-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz#f0d2ed8380dfbed949c42d4d790266525d63bbdc" - integrity sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA== +"@babel/plugin-transform-property-literals@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.7.tgz#a8612b4ea4e10430f00012ecf0155662c7d6550d" + integrity sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" "@babel/plugin-transform-react-constant-elements@^7.21.3": - version "7.25.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.1.tgz#71a665ed16ce618067d05f4a98130207349d82ae" - integrity sha512-SLV/giH/V4SmloZ6Dt40HjTGTAIkxn33TVIHxNGNvo8ezMhrxBkzisj4op1KZYPIOHFLqhv60OHvX+YRu4xbmQ== + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.7.tgz#b7f18dcdfac137a635a3f1242ea7c931df82a666" + integrity sha512-/qXt69Em8HgsjCLu7G3zdIQn7A2QwmYND7Wa0LTp09Na+Zn8L5d0A7wSXrKi18TJRc/Q5S1i1De/SU1LzVkSvA== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-react-display-name@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz#9caff79836803bc666bcfe210aeb6626230c293b" - integrity sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg== +"@babel/plugin-transform-react-display-name@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.7.tgz#2753e875a1b702fb1d806c4f5d4c194d64cadd88" + integrity sha512-r0QY7NVU8OnrwE+w2IWiRom0wwsTbjx4+xH2RTd7AVdof3uurXOF+/mXHQDRk+2jIvWgSaCHKMgggfvM4dyUGA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-react-jsx-development@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz#eaee12f15a93f6496d852509a850085e6361470b" - integrity sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ== +"@babel/plugin-transform-react-jsx-development@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.7.tgz#2fbd77887b8fa2942d7cb61edf1029ea1b048554" + integrity sha512-5yd3lH1PWxzW6IZj+p+Y4OLQzz0/LzlOG8vGqonHfVR3euf1vyzyMUJk9Ac+m97BH46mFc/98t9PmYLyvgL3qg== dependencies: - "@babel/plugin-transform-react-jsx" "^7.24.7" + "@babel/plugin-transform-react-jsx" "^7.25.7" -"@babel/plugin-transform-react-jsx@^7.24.7": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz#e37e8ebfa77e9f0b16ba07fadcb6adb47412227a" - integrity sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA== +"@babel/plugin-transform-react-jsx@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.7.tgz#f5e2af6020a562fe048dd343e571c4428e6c5632" + integrity sha512-vILAg5nwGlR9EXE8JIOX4NHXd49lrYbN8hnjffDtoULwpL9hUx/N55nqh2qd0q6FyNDfjl9V79ecKGvFbcSA0Q== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/plugin-syntax-jsx" "^7.24.7" - "@babel/types" "^7.25.2" + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-module-imports" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/plugin-syntax-jsx" "^7.25.7" + "@babel/types" "^7.25.7" -"@babel/plugin-transform-react-pure-annotations@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz#bdd9d140d1c318b4f28b29a00fb94f97ecab1595" - integrity sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA== +"@babel/plugin-transform-react-pure-annotations@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.7.tgz#6d0b8dadb2d3c5cbb8ade68c5efd49470b0d65f7" + integrity sha512-6YTHJ7yjjgYqGc8S+CbEXhLICODk0Tn92j+vNJo07HFk9t3bjFgAKxPLFhHwF2NjmQVSI1zBRfBWUeVBa2osfA== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-regenerator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz#021562de4534d8b4b1851759fd7af4e05d2c47f8" - integrity sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA== +"@babel/plugin-transform-regenerator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.7.tgz#6eb006e6d26f627bc2f7844a9f19770721ad6f3e" + integrity sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" regenerator-transform "^0.15.2" -"@babel/plugin-transform-reserved-words@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz#80037fe4fbf031fc1125022178ff3938bb3743a4" - integrity sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ== +"@babel/plugin-transform-reserved-words@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.7.tgz#dc56b25e02afaabef3ce0c5b06b0916e8523e995" + integrity sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" "@babel/plugin-transform-runtime@^7.22.9": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.4.tgz#96e4ad7bfbbe0b4a7b7e6f2a533ca326cf204963" - integrity sha512-8hsyG+KUYGY0coX6KUCDancA0Vw225KJ2HJO0yCNr1vq5r+lJTleDaJf0K7iOhjw4SWhu03TMBzYTJ9krmzULQ== + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.7.tgz#435a4fab67273f00047dc806e05069c9c6344e12" + integrity sha512-Y9p487tyTzB0yDYQOtWnC+9HGOuogtP3/wNpun1xJXEEvI6vip59BSBTsHnekZLqxmPcgsrAKt46HAAb//xGhg== dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-module-imports" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" babel-plugin-polyfill-corejs2 "^0.4.10" babel-plugin-polyfill-corejs3 "^0.10.6" babel-plugin-polyfill-regenerator "^0.6.1" semver "^6.3.1" -"@babel/plugin-transform-shorthand-properties@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz#85448c6b996e122fa9e289746140aaa99da64e73" - integrity sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA== +"@babel/plugin-transform-shorthand-properties@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.7.tgz#92690a9c671915602d91533c278cc8f6bf12275f" + integrity sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-spread@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz#e8a38c0fde7882e0fb8f160378f74bd885cc7bb3" - integrity sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng== +"@babel/plugin-transform-spread@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.7.tgz#df83e899a9fc66284ee601a7b738568435b92998" + integrity sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" -"@babel/plugin-transform-sticky-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz#96ae80d7a7e5251f657b5cf18f1ea6bf926f5feb" - integrity sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g== +"@babel/plugin-transform-sticky-regex@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.7.tgz#341c7002bef7f29037be7fb9684e374442dd0d17" + integrity sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-template-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz#a05debb4a9072ae8f985bcf77f3f215434c8f8c8" - integrity sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw== +"@babel/plugin-transform-template-literals@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.7.tgz#e566c581bb16d8541dd8701093bb3457adfce16b" + integrity sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-typeof-symbol@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz#383dab37fb073f5bfe6e60c654caac309f92ba1c" - integrity sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw== +"@babel/plugin-transform-typeof-symbol@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.7.tgz#debb1287182efd20488f126be343328c679b66eb" + integrity sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-typescript@^7.24.7": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz#237c5d10de6d493be31637c6b9fa30b6c5461add" - integrity sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A== +"@babel/plugin-transform-typescript@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.7.tgz#8fc7c3d28ddd36bce45b9b48594129d0e560cfbe" + integrity sha512-VKlgy2vBzj8AmEzunocMun2fF06bsSWV+FvVXohtL6FGve/+L217qhHxRTVGHEDO/YR8IANcjzgJsd04J8ge5Q== dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-create-class-features-plugin" "^7.25.0" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-syntax-typescript" "^7.24.7" + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-create-class-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" + "@babel/plugin-syntax-typescript" "^7.25.7" -"@babel/plugin-transform-unicode-escapes@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz#2023a82ced1fb4971630a2e079764502c4148e0e" - integrity sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw== +"@babel/plugin-transform-unicode-escapes@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.7.tgz#973592b6d13a914794e1de8cf1383e50e0f87f81" + integrity sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-unicode-property-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz#9073a4cd13b86ea71c3264659590ac086605bbcd" - integrity sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w== +"@babel/plugin-transform-unicode-property-regex@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.7.tgz#25349197cce964b1343f74fa7cfdf791a1b1919e" + integrity sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-create-regexp-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-unicode-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz#dfc3d4a51127108099b19817c0963be6a2adf19f" - integrity sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg== +"@babel/plugin-transform-unicode-regex@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.7.tgz#f93a93441baf61f713b6d5552aaa856bfab34809" + integrity sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-create-regexp-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-transform-unicode-sets-regex@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz#be664c2a0697ffacd3423595d5edef6049e8946c" - integrity sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA== +"@babel/plugin-transform-unicode-sets-regex@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.7.tgz#d1b3295d29e0f8f4df76abc909ad1ebee919560c" + integrity sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.2" - "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-create-regexp-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" "@babel/preset-env@^7.20.2", "@babel/preset-env@^7.22.9": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.4.tgz#be23043d43a34a2721cd0f676c7ba6f1481f6af6" - integrity sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw== + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.8.tgz#dc6b719627fb29cd9cccbbbe041802fd575b524c" + integrity sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg== dependencies: - "@babel/compat-data" "^7.25.4" - "@babel/helper-compilation-targets" "^7.25.2" - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-validator-option" "^7.24.8" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.3" - "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.0" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.0" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.0" + "@babel/compat-data" "^7.25.8" + "@babel/helper-compilation-targets" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-validator-option" "^7.25.7" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.7" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.7" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.24.7" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-import-assertions" "^7.25.7" + "@babel/plugin-syntax-import-attributes" "^7.25.7" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.24.7" - "@babel/plugin-transform-async-generator-functions" "^7.25.4" - "@babel/plugin-transform-async-to-generator" "^7.24.7" - "@babel/plugin-transform-block-scoped-functions" "^7.24.7" - "@babel/plugin-transform-block-scoping" "^7.25.0" - "@babel/plugin-transform-class-properties" "^7.25.4" - "@babel/plugin-transform-class-static-block" "^7.24.7" - "@babel/plugin-transform-classes" "^7.25.4" - "@babel/plugin-transform-computed-properties" "^7.24.7" - "@babel/plugin-transform-destructuring" "^7.24.8" - "@babel/plugin-transform-dotall-regex" "^7.24.7" - "@babel/plugin-transform-duplicate-keys" "^7.24.7" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.0" - "@babel/plugin-transform-dynamic-import" "^7.24.7" - "@babel/plugin-transform-exponentiation-operator" "^7.24.7" - "@babel/plugin-transform-export-namespace-from" "^7.24.7" - "@babel/plugin-transform-for-of" "^7.24.7" - "@babel/plugin-transform-function-name" "^7.25.1" - "@babel/plugin-transform-json-strings" "^7.24.7" - "@babel/plugin-transform-literals" "^7.25.2" - "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" - "@babel/plugin-transform-member-expression-literals" "^7.24.7" - "@babel/plugin-transform-modules-amd" "^7.24.7" - "@babel/plugin-transform-modules-commonjs" "^7.24.8" - "@babel/plugin-transform-modules-systemjs" "^7.25.0" - "@babel/plugin-transform-modules-umd" "^7.24.7" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" - "@babel/plugin-transform-new-target" "^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" - "@babel/plugin-transform-numeric-separator" "^7.24.7" - "@babel/plugin-transform-object-rest-spread" "^7.24.7" - "@babel/plugin-transform-object-super" "^7.24.7" - "@babel/plugin-transform-optional-catch-binding" "^7.24.7" - "@babel/plugin-transform-optional-chaining" "^7.24.8" - "@babel/plugin-transform-parameters" "^7.24.7" - "@babel/plugin-transform-private-methods" "^7.25.4" - "@babel/plugin-transform-private-property-in-object" "^7.24.7" - "@babel/plugin-transform-property-literals" "^7.24.7" - "@babel/plugin-transform-regenerator" "^7.24.7" - "@babel/plugin-transform-reserved-words" "^7.24.7" - "@babel/plugin-transform-shorthand-properties" "^7.24.7" - "@babel/plugin-transform-spread" "^7.24.7" - "@babel/plugin-transform-sticky-regex" "^7.24.7" - "@babel/plugin-transform-template-literals" "^7.24.7" - "@babel/plugin-transform-typeof-symbol" "^7.24.8" - "@babel/plugin-transform-unicode-escapes" "^7.24.7" - "@babel/plugin-transform-unicode-property-regex" "^7.24.7" - "@babel/plugin-transform-unicode-regex" "^7.24.7" - "@babel/plugin-transform-unicode-sets-regex" "^7.25.4" + "@babel/plugin-transform-arrow-functions" "^7.25.7" + "@babel/plugin-transform-async-generator-functions" "^7.25.8" + "@babel/plugin-transform-async-to-generator" "^7.25.7" + "@babel/plugin-transform-block-scoped-functions" "^7.25.7" + "@babel/plugin-transform-block-scoping" "^7.25.7" + "@babel/plugin-transform-class-properties" "^7.25.7" + "@babel/plugin-transform-class-static-block" "^7.25.8" + "@babel/plugin-transform-classes" "^7.25.7" + "@babel/plugin-transform-computed-properties" "^7.25.7" + "@babel/plugin-transform-destructuring" "^7.25.7" + "@babel/plugin-transform-dotall-regex" "^7.25.7" + "@babel/plugin-transform-duplicate-keys" "^7.25.7" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.7" + "@babel/plugin-transform-dynamic-import" "^7.25.8" + "@babel/plugin-transform-exponentiation-operator" "^7.25.7" + "@babel/plugin-transform-export-namespace-from" "^7.25.8" + "@babel/plugin-transform-for-of" "^7.25.7" + "@babel/plugin-transform-function-name" "^7.25.7" + "@babel/plugin-transform-json-strings" "^7.25.8" + "@babel/plugin-transform-literals" "^7.25.7" + "@babel/plugin-transform-logical-assignment-operators" "^7.25.8" + "@babel/plugin-transform-member-expression-literals" "^7.25.7" + "@babel/plugin-transform-modules-amd" "^7.25.7" + "@babel/plugin-transform-modules-commonjs" "^7.25.7" + "@babel/plugin-transform-modules-systemjs" "^7.25.7" + "@babel/plugin-transform-modules-umd" "^7.25.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.7" + "@babel/plugin-transform-new-target" "^7.25.7" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.25.8" + "@babel/plugin-transform-numeric-separator" "^7.25.8" + "@babel/plugin-transform-object-rest-spread" "^7.25.8" + "@babel/plugin-transform-object-super" "^7.25.7" + "@babel/plugin-transform-optional-catch-binding" "^7.25.8" + "@babel/plugin-transform-optional-chaining" "^7.25.8" + "@babel/plugin-transform-parameters" "^7.25.7" + "@babel/plugin-transform-private-methods" "^7.25.7" + "@babel/plugin-transform-private-property-in-object" "^7.25.8" + "@babel/plugin-transform-property-literals" "^7.25.7" + "@babel/plugin-transform-regenerator" "^7.25.7" + "@babel/plugin-transform-reserved-words" "^7.25.7" + "@babel/plugin-transform-shorthand-properties" "^7.25.7" + "@babel/plugin-transform-spread" "^7.25.7" + "@babel/plugin-transform-sticky-regex" "^7.25.7" + "@babel/plugin-transform-template-literals" "^7.25.7" + "@babel/plugin-transform-typeof-symbol" "^7.25.7" + "@babel/plugin-transform-unicode-escapes" "^7.25.7" + "@babel/plugin-transform-unicode-property-regex" "^7.25.7" + "@babel/plugin-transform-unicode-regex" "^7.25.7" + "@babel/plugin-transform-unicode-sets-regex" "^7.25.7" "@babel/preset-modules" "0.1.6-no-external-plugins" babel-plugin-polyfill-corejs2 "^0.4.10" babel-plugin-polyfill-corejs3 "^0.10.6" babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.37.1" + core-js-compat "^3.38.1" semver "^6.3.1" "@babel/preset-modules@0.1.6-no-external-plugins": @@ -1127,77 +1002,72 @@ esutils "^2.0.2" "@babel/preset-react@^7.18.6", "@babel/preset-react@^7.22.5": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.24.7.tgz#480aeb389b2a798880bf1f889199e3641cbb22dc" - integrity sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag== + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.25.7.tgz#081cbe1dea363b732764d06a0fdda67ffa17735d" + integrity sha512-GjV0/mUEEXpi1U5ZgDprMRRgajGMRW3G5FjMr5KLKD8nT2fTG8+h/klV3+6Dm5739QE+K5+2e91qFKAYI3pmRg== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - "@babel/plugin-transform-react-display-name" "^7.24.7" - "@babel/plugin-transform-react-jsx" "^7.24.7" - "@babel/plugin-transform-react-jsx-development" "^7.24.7" - "@babel/plugin-transform-react-pure-annotations" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-validator-option" "^7.25.7" + "@babel/plugin-transform-react-display-name" "^7.25.7" + "@babel/plugin-transform-react-jsx" "^7.25.7" + "@babel/plugin-transform-react-jsx-development" "^7.25.7" + "@babel/plugin-transform-react-pure-annotations" "^7.25.7" "@babel/preset-typescript@^7.21.0", "@babel/preset-typescript@^7.22.5": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz#66cd86ea8f8c014855671d5ea9a737139cbbfef1" - integrity sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ== + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.25.7.tgz#43c5b68eccb856ae5b52274b77b1c3c413cde1b7" + integrity sha512-rkkpaXJZOFN45Fb+Gki0c+KMIglk4+zZXOoMJuyEK8y8Kkc8Jd3BDmP7qPsz0zQMJj+UD7EprF+AqAXcILnexw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - "@babel/plugin-syntax-jsx" "^7.24.7" - "@babel/plugin-transform-modules-commonjs" "^7.24.7" - "@babel/plugin-transform-typescript" "^7.24.7" - -"@babel/regjsgen@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" - integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/helper-validator-option" "^7.25.7" + "@babel/plugin-syntax-jsx" "^7.25.7" + "@babel/plugin-transform-modules-commonjs" "^7.25.7" + "@babel/plugin-transform-typescript" "^7.25.7" "@babel/runtime-corejs3@^7.22.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.25.6.tgz#5e3facf42775cc95bcde95746e940061931286e4" - integrity sha512-Gz0Nrobx8szge6kQQ5Z5MX9L3ObqNwCQY1PSwSNzreFL7aHGxv8Fp2j3ETV6/wWdbiV+mW6OSm8oQhg3Tcsniw== + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.25.7.tgz#29ca319b1272e9d78faa3f7ee891d0af63c53aa2" + integrity sha512-gMmIEhg35sXk9Te5qbGp3W9YKrvLt3HV658/d3odWrHSqT0JeG5OzsJWFHRLiOohRyjRsJc/x03DhJm3i8VJxg== dependencies: core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.22.6", "@babel/runtime@^7.8.4": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" - integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" + integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.24.7", "@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== +"@babel/template@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769" + integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA== dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.25.0" - "@babel/types" "^7.25.0" + "@babel/code-frame" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/types" "^7.25.7" -"@babel/traverse@^7.22.8", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.4": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" - integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== +"@babel/traverse@^7.22.8", "@babel/traverse@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" + integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.6" - "@babel/parser" "^7.25.6" - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.21.3", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6", "@babel/types@^7.4.4": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" - integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== +"@babel/types@^7.21.3", "@babel/types@^7.25.7", "@babel/types@^7.25.8", "@babel/types@^7.4.4": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" + integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg== dependencies: - "@babel/helper-string-parser" "^7.24.8" - "@babel/helper-validator-identifier" "^7.24.7" + "@babel/helper-string-parser" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" to-fast-properties "^2.0.0" "@colors/colors@1.5.0": @@ -1210,19 +1080,19 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@docsearch/css@3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.6.1.tgz#f0a728ecb486c81f2d282650fc1820c914913408" - integrity sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg== +"@docsearch/css@3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.6.2.tgz#ccd9c83dbfeaf34efe4e3547ee596714ae7e5891" + integrity sha512-vKNZepO2j7MrYBTZIGXvlUOIR+v9KRf70FApRgovWrj3GTs1EITz/Xb0AOlm1xsQBp16clVZj1SY/qaOJbQtZw== "@docsearch/react@^3.5.2": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.6.1.tgz#0f826df08693293806d64277d6d9c38636211b97" - integrity sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw== + version "3.6.2" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.6.2.tgz#32b16dd7d5614f0d39e6bc018549816b68d171b8" + integrity sha512-rtZce46OOkVflCQH71IdbXSFK+S8iJZlUF56XBW5rIgx/eG5qoomC7Ag3anZson1bBac/JFQn7XOBfved/IMRA== dependencies: "@algolia/autocomplete-core" "1.9.3" "@algolia/autocomplete-preset-algolia" "1.9.3" - "@docsearch/css" "3.6.1" + "@docsearch/css" "3.6.2" algoliasearch "^4.19.1" "@docusaurus/core@3.5.2", "@docusaurus/core@^3.2.1": @@ -2013,17 +1883,37 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.19.5" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" - integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz#91f06cda1049e8f17eeab364798ed79c97488a1c" + integrity sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/send" "*" -"@types/express@*", "@types/express@^4.17.13": +"@types/express-serve-static-core@^4.17.33": + version "4.19.6" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" + integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c" + integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/express@^4.17.13": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -2126,9 +2016,9 @@ "@types/node" "*" "@types/node@*": - version "22.5.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.5.tgz#52f939dd0f65fc552a4ad0b392f3c466cc5d7a44" - integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA== + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== dependencies: undici-types "~6.19.2" @@ -2189,9 +2079,9 @@ "@types/react" "*" "@types/react@*": - version "18.3.8" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.8.tgz#1672ab19993f8aca7c7dc844c07d5d9e467d5a79" - integrity sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q== + version "18.3.11" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.11.tgz#9d530601ff843ee0d7030d4227ea4360236bd537" + integrity sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ== dependencies: "@types/prop-types" "*" csstype "^3.0.2" @@ -2430,9 +2320,9 @@ acorn-walk@^8.0.0: acorn "^8.11.0" acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.7.1, acorn@^8.8.2: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + version "8.13.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3" + integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w== address@^1.0.1, address@^1.1.2: version "1.2.2" @@ -2750,13 +2640,13 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.23.0, 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== +browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.23.0, browserslist@^4.23.3, browserslist@^4.24.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== dependencies: - caniuse-lite "^1.0.30001646" - electron-to-chromium "^1.5.4" + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" node-releases "^2.0.18" update-browserslist-db "^1.1.0" @@ -2837,10 +2727,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646: - version "1.0.30001662" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz#3574b22dfec54a3f3b6787331da1040fe8e763ec" - integrity sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663: + version "1.0.30001669" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3" + integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w== ccount@^2.0.0: version "2.0.1" @@ -3144,10 +3034,10 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== copy-text-to-clipboard@^3.2.0: version "3.2.0" @@ -3166,7 +3056,7 @@ copy-webpack-plugin@^11.0.0: schema-utils "^4.0.0" serialize-javascript "^6.0.0" -core-js-compat@^3.37.1, core-js-compat@^3.38.0: +core-js-compat@^3.38.0, core-js-compat@^3.38.1: version "3.38.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.38.1.tgz#2bc7a298746ca5a7bcb9c164bcb120f2ebc09a09" integrity sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw== @@ -3624,10 +3514,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.5.4: - version "1.5.27" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz#5203ce5d6054857d84ba84d3681cbe59132ade78" - integrity sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw== +electron-to-chromium@^1.5.28: + version "1.5.39" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.39.tgz#5cbe5200b43dff7b7c2bcb6bdacf65d514c76bb2" + integrity sha512-4xkpSR6CjuiaNyvwiWDI85N9AxsvbPawB8xc7yzLPonYTuP19BVgYweKyUMFtHEZgIcHWMt1ks5Cqx2m+6/Grg== emoji-regex@^8.0.0: version "8.0.0" @@ -3677,7 +3567,7 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.2.0, entities@^4.4.0: +entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -3706,7 +3596,7 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== -escalade@^3.1.1, escalade@^3.1.2: +escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -3868,16 +3758,16 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.3: - version "4.21.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" - integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== + version "4.21.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281" + integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.6.0" + cookie "0.7.1" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -3938,16 +3828,9 @@ fast-json-stable-stringify@^2.0.0: integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-uri@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" - integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== - -fast-url-parser@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== - dependencies: - punycode "^1.3.2" + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== fastq@^1.6.0: version "1.17.1" @@ -4405,9 +4288,9 @@ hast-util-to-estree@^3.0.0: zwitch "^2.0.0" hast-util-to-jsx-runtime@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz#3ed27caf8dc175080117706bf7269404a0aa4f7c" - integrity sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ== + version "2.3.2" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz#6d11b027473e69adeaa00ca4cfb5bb68e3d282fa" + integrity sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg== dependencies: "@types/estree" "^1.0.0" "@types/hast" "^3.0.0" @@ -4604,9 +4487,9 @@ http-parser-js@>=0.5.1: integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" @@ -5010,15 +4893,10 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +jsesc@^3.0.2, jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== json-buffer@3.0.1: version "3.0.1" @@ -6253,19 +6131,19 @@ parse-numeric-range@^1.3.0: integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== parse5-htmlparser2-tree-adapter@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" - integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== dependencies: - domhandler "^5.0.2" + domhandler "^5.0.3" parse5 "^7.0.0" parse5@^7.0.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + version "7.2.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.0.tgz#8a0591ce9b7c5e2027173ab737d4d3fc3d826fab" + integrity sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA== dependencies: - entities "^4.4.0" + entities "^4.5.0" parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" @@ -6320,10 +6198,10 @@ path-to-regexp@0.1.10: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== -path-to-regexp@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" - integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== +path-to-regexp@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" + integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== path-to-regexp@^1.7.0: version "1.9.0" @@ -6725,11 +6603,6 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -6985,7 +6858,7 @@ recursive-readdir@^2.2.2: dependencies: minimatch "^3.0.5" -regenerate-unicode-properties@^10.1.0: +regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== @@ -7009,15 +6882,15 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regexpu-core@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" - integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== +regexpu-core@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.1.1.tgz#b469b245594cb2d088ceebc6369dceb8c00becac" + integrity sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw== dependencies: - "@babel/regjsgen" "^0.8.0" regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsparser "^0.9.1" + regenerate-unicode-properties "^10.2.0" + regjsgen "^0.8.0" + regjsparser "^0.11.0" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" @@ -7035,12 +6908,17 @@ registry-url@^6.0.0: dependencies: rc "1.2.8" -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.11.1.tgz#ae55c74f646db0c8fcb922d4da635e33da405149" + integrity sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ== dependencies: - jsesc "~0.5.0" + jsesc "~3.0.2" rehype-raw@^7.0.0: version "7.0.0" @@ -7353,17 +7231,16 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: randombytes "^2.1.0" serve-handler@^6.1.5: - version "6.1.5" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375" - integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg== + version "6.1.6" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1" + integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ== dependencies: bytes "3.0.0" content-disposition "0.5.2" - fast-url-parser "1.1.3" mime-types "2.1.18" minimatch "3.1.2" path-is-inside "1.0.2" - path-to-regexp "2.2.1" + path-to-regexp "3.3.0" range-parser "1.2.0" serve-index@^1.9.1: @@ -7774,9 +7651,9 @@ terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.9: terser "^5.26.0" terser@^5.10.0, terser@^5.15.1, terser@^5.26.0: - version "5.33.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.33.0.tgz#8f9149538c7468ffcb1246cfec603c16720d2db1" - integrity sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g== + version "5.35.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.35.0.tgz#38e29d3f4fd09dacfa3fcd5d381086d0816ba943" + integrity sha512-TmYbQnzVfrx3RQsPoItoPplymixIAtp2R2xlpyVBYmFmvI34IzLhCLj8SimRb/kZXlq4t1gA+vbcTqLQ3+5Q5g== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -7836,9 +7713,9 @@ trough@^2.0.0: integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== tslib@^2.0.3, tslib@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" - integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + version "2.8.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" + integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== type-fest@^1.0.1: version "1.4.0" @@ -7974,12 +7851,12 @@ unpipe@1.0.0, unpipe@~1.0.0: integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== 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== + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== dependencies: - escalade "^3.1.2" - picocolors "^1.0.1" + escalade "^3.2.0" + picocolors "^1.1.0" update-notifier@^6.0.2: version "6.0.2" @@ -8176,9 +8053,9 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.88.1: - version "5.94.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" - integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== + version "5.95.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.95.0.tgz#8fd8c454fa60dad186fbe36c400a55848307b4c0" + integrity sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q== dependencies: "@types/estree" "^1.0.5" "@webassemblyjs/ast" "^1.12.1" From 7ebfc24a9660c6836df5d1e72be13b45331fa7fd Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 16 Oct 2024 03:47:43 -0700 Subject: [PATCH 07/71] Upgrade Django version used by Khoj server --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ededa792..960d8d72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ dependencies = [ "tenacity == 8.3.0", "anyio == 3.7.1", "pymupdf >= 1.23.5", - "django == 5.0.8", + "django == 5.0.9", "authlib == 1.2.1", "llama-cpp-python == 0.2.88", "itsdangerous == 2.1.2", From 42acc324dcdd3d8657365b8033719e31c7fdb132 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 01:27:28 -0700 Subject: [PATCH 08/71] Handle correctly setting file filters as array when API call fails - Only set addedFiles to selectedFiles when selectedFiles is an array - Only set seleectedFiles, addedFiles to API response json when response succeeded. Previously we set it to response json on errors as well. This made the variables into json objects instead of arrays on API call failure - Check if selectedFiles, addedFiles are arrays before running operations on them. Previously the addedFiles.includes was where the code would fail --- src/interface/web/app/common/chatFunctions.ts | 6 ++++- .../sidePanel/chatHistorySidePanel.tsx | 27 ++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/interface/web/app/common/chatFunctions.ts b/src/interface/web/app/common/chatFunctions.ts index 6d7c9bc1..b2dd75fd 100644 --- a/src/interface/web/app/common/chatFunctions.ts +++ b/src/interface/web/app/common/chatFunctions.ts @@ -175,7 +175,11 @@ export function modifyFileFilterForConversation( }, body: JSON.stringify(body), }) - .then((response) => response.json()) + .then((res) => { + if (!res.ok) + throw new Error(`Failed to call API at ${addUrl} with error ${res.statusText}`); + return res.json(); + }) .then((data) => { setAddedFiles(data); }) diff --git a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx index 4786f2a1..6c447377 100644 --- a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx +++ b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx @@ -98,7 +98,11 @@ import { KhojLogoType } from "@/app/components/logo/khojLogo"; import NavMenu from "@/app/components/navMenu/navMenu"; // Define a fetcher function -const fetcher = (url: string) => fetch(url).then((res) => res.json()); +const fetcher = (url: string) => + fetch(url).then((res) => { + if (!res.ok) throw new Error(`Failed to call API at ${url} with error ${res.statusText}`); + return res.json(); + }); interface GroupedChatHistory { [key: string]: ChatHistory[]; @@ -181,20 +185,15 @@ function FilesMenu(props: FilesMenuProps) { useEffect(() => { if (!files) return; - const uniqueFiles = Array.from(new Set(files)); + let sortedUniqueFiles = Array.from(new Set(files)).sort(); - // First, sort lexically - uniqueFiles.sort(); - - let sortedFiles = uniqueFiles; - - if (addedFiles) { - sortedFiles = addedFiles.concat( - sortedFiles.filter((filename: string) => !addedFiles.includes(filename)), + if (Array.isArray(addedFiles)) { + sortedUniqueFiles = addedFiles.concat( + sortedUniqueFiles.filter((filename: string) => !addedFiles.includes(filename)), ); } - setUnfilteredFiles(sortedFiles); + setUnfilteredFiles(sortedUniqueFiles); }, [files, addedFiles]); useEffect(() => { @@ -204,8 +203,10 @@ function FilesMenu(props: FilesMenuProps) { }, [props.uploadedFiles]); useEffect(() => { - if (selectedFiles) { + if (Array.isArray(selectedFiles)) { setAddedFiles(selectedFiles); + } else { + setAddedFiles([]); } }, [selectedFiles]); @@ -269,7 +270,7 @@ function FilesMenu(props: FilesMenuProps) { )} {unfilteredFiles.map((filename: string) => - addedFiles && addedFiles.includes(filename) ? ( + Array.isArray(addedFiles) && addedFiles.includes(filename) ? ( Date: Thu, 17 Oct 2024 03:22:08 -0700 Subject: [PATCH 09/71] Use Khoj icons. Add automation & improve agent text on web login page --- README.md | 4 +-- .../interface/web/assets/icons/agents.svg | 1 + .../interface/web/assets/icons/automation.svg | 1 + src/khoj/interface/web/assets/icons/chat.svg | 24 ++++++++++++++ src/khoj/interface/web/login.html | 33 +++++++------------ 5 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 src/khoj/interface/web/assets/icons/chat.svg diff --git a/README.md b/README.md index 76feba33..07e23457 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ - Chat with any local or online LLM (e.g llama3, qwen, gemma, mistral, gpt, claude, gemini). - Get answers from the internet and your docs (including image, pdf, markdown, org-mode, word, notion files). - Access it from your Browser, Obsidian, Emacs, Desktop, Phone or Whatsapp. -- Build agents with custom knowledge bases and tools. -- Create automations to get personal newsletters and smart notifications. +- Create agents with custom knowledge, persona, chat model and tools to take on any role. +- Automate away repetitive research. Get personal newsletters and smart notifications delivered to your inbox. - Find relevant docs quickly and easily using our advanced semantic search. - Generate images, talk out loud, play your messages. - Khoj is open-source, self-hostable. Always. diff --git a/src/khoj/interface/web/assets/icons/agents.svg b/src/khoj/interface/web/assets/icons/agents.svg index 27540107..0c0b63dd 100644 --- a/src/khoj/interface/web/assets/icons/agents.svg +++ b/src/khoj/interface/web/assets/icons/agents.svg @@ -14,5 +14,6 @@ clip-rule="evenodd" fill-rule="evenodd" fill="currentColor" + stroke="currentColor" stroke-width="0.95844" /> diff --git a/src/khoj/interface/web/assets/icons/automation.svg b/src/khoj/interface/web/assets/icons/automation.svg index d49a0c5b..a2227d1c 100644 --- a/src/khoj/interface/web/assets/icons/automation.svg +++ b/src/khoj/interface/web/assets/icons/automation.svg @@ -12,6 +12,7 @@ fill-rule="evenodd" clip-rule="evenodd" fill-opacity="1" + stroke="currentColor" stroke-width="1.16584" stroke-dasharray="none" /> diff --git a/src/khoj/interface/web/assets/icons/chat.svg b/src/khoj/interface/web/assets/icons/chat.svg new file mode 100644 index 00000000..f474ab89 --- /dev/null +++ b/src/khoj/interface/web/assets/icons/chat.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/src/khoj/interface/web/login.html b/src/khoj/interface/web/login.html index a1090ed0..b7db3a1a 100644 --- a/src/khoj/interface/web/login.html +++ b/src/khoj/interface/web/login.html @@ -46,33 +46,16 @@

Transform the way you think, create, and remember

- - - - - - - + Chat Get answers across your documents and the internet
- - - - - Go deeper in the topics personal to you + Agents + Create agents with the knowledge and tools to take on any role
- - - - - - Use specialized agents + Automations + Automate away repetitive research
@@ -160,6 +143,12 @@ height: 24px; stroke: white; } + .feature img { + width: 24px; + height: 24px; + filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%); + stroke: white; + } #login-modal { display: grid; From 884fe42602f6aa196098d3f3a5524d0f596e14ce Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 11:56:43 -0700 Subject: [PATCH 10/71] Allow automation as an output mode supported by custom agents --- .../0068_alter_agent_output_modes.py | 24 +++++++++++++++++++ src/khoj/database/models/__init__.py | 1 + src/khoj/utils/helpers.py | 3 ++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/khoj/database/migrations/0068_alter_agent_output_modes.py diff --git a/src/khoj/database/migrations/0068_alter_agent_output_modes.py b/src/khoj/database/migrations/0068_alter_agent_output_modes.py new file mode 100644 index 00000000..31c01bd4 --- /dev/null +++ b/src/khoj/database/migrations/0068_alter_agent_output_modes.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.8 on 2024-10-17 18:13 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("database", "0067_alter_agent_style_icon"), + ] + + operations = [ + migrations.AlterField( + model_name="agent", + name="output_modes", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + choices=[("text", "Text"), ("image", "Image"), ("automation", "Automation")], max_length=200 + ), + default=list, + size=None, + ), + ), + ] diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index dfe91b14..ec4b61d1 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -174,6 +174,7 @@ class Agent(BaseModel): # These map to various ConversationCommand types TEXT = "text" IMAGE = "image" + AUTOMATION = "automation" creator = models.ForeignKey( KhojUser, on_delete=models.CASCADE, default=None, null=True, blank=True diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index 4c7bf985..e0908e51 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -347,12 +347,13 @@ tool_descriptions_for_llm = { mode_descriptions_for_llm = { ConversationCommand.Image: "Use this if the user is requesting you to generate a picture based on their description.", - ConversationCommand.Automation: "Use this if the user is requesting a response at a scheduled date or time.", + ConversationCommand.Automation: "Use this if you are confident the user is requesting a response at a scheduled date, time and frequency", ConversationCommand.Text: "Use this if the other response modes don't seem to fit the query.", } mode_descriptions_for_agent = { ConversationCommand.Image: "Agent can generate image in response.", + ConversationCommand.Automation: "Agent can schedule a task to run at a scheduled date, time and frequency in response.", ConversationCommand.Text: "Agent can generate text in response.", } From 9affeb9e855b96bd510abc0f00da75317e026219 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 15:24:43 -0700 Subject: [PATCH 11/71] Fix to log the client app calling the chat API - Remove unused subscribed variable from the chat API - Unexpectedly dropped client app logging when migrated API chat to do advanced streaming in july --- src/khoj/routers/api_chat.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 228d081c..9022a7dc 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -574,7 +574,6 @@ async def chat( chat_metadata: dict = {} connection_alive = True user: KhojUser = request.user.object - subscribed: bool = has_required_scope(request, ["premium"]) event_delimiter = "␃🔚␗" q = unquote(q) nonlocal conversation_id @@ -641,7 +640,7 @@ async def chat( request=request, telemetry_type="api", api="chat", - client=request.user.client_app, + client=common.client, user_agent=request.headers.get("user-agent"), host=request.headers.get("host"), metadata=chat_metadata, From 1b04b801c6e6e62a09a16d4a5eddff7dbafe9590 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Mon, 14 Oct 2024 17:39:44 -0700 Subject: [PATCH 12/71] Try respond even if document search via inference endpoint fails The huggingface endpoint can be flaky. Khoj shouldn't refuse to respond to user if document search fails. It should transparently mention that document lookup failed. But try respond as best as it can without the document references This changes provides graceful failover when inference endpoint requests fail either when encoding query or reranking retrieved docs --- src/khoj/processor/embeddings.py | 1 + src/khoj/routers/api_chat.py | 47 +++++++++++++++++------------ src/khoj/search_type/text_search.py | 9 ++++-- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/khoj/processor/embeddings.py b/src/khoj/processor/embeddings.py index 71af5b7d..a19d85fa 100644 --- a/src/khoj/processor/embeddings.py +++ b/src/khoj/processor/embeddings.py @@ -114,6 +114,7 @@ class CrossEncoderModel: payload = {"inputs": {"query": query, "passages": [hit.additional[key] for hit in hits]}} headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"} response = requests.post(target_url, json=payload, headers=headers) + response.raise_for_status() return response.json()["scores"] cross_inp = [[query, hit.additional[key]] for hit in hits] diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 9022a7dc..93c905b6 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -3,7 +3,6 @@ import base64 import json import logging import time -import warnings from datetime import datetime from functools import partial from typing import Dict, Optional @@ -839,25 +838,33 @@ async def chat( # Gather Context ## Extract Document References compiled_references, inferred_queries, defiltered_query = [], [], None - async for result in extract_references_and_questions( - request, - meta_log, - q, - (n or 7), - d, - conversation_id, - conversation_commands, - location, - partial(send_event, ChatEvent.STATUS), - uploaded_image_url=uploaded_image_url, - agent=agent, - ): - if isinstance(result, dict) and ChatEvent.STATUS in result: - yield result[ChatEvent.STATUS] - else: - compiled_references.extend(result[0]) - inferred_queries.extend(result[1]) - defiltered_query = result[2] + try: + async for result in extract_references_and_questions( + request, + meta_log, + q, + (n or 7), + d, + conversation_id, + conversation_commands, + location, + partial(send_event, ChatEvent.STATUS), + uploaded_image_url=uploaded_image_url, + agent=agent, + ): + if isinstance(result, dict) and ChatEvent.STATUS in result: + yield result[ChatEvent.STATUS] + else: + compiled_references.extend(result[0]) + inferred_queries.extend(result[1]) + defiltered_query = result[2] + except Exception as e: + error_message = f"Error searching knowledge base: {e}. Attempting to respond without document references." + logger.warning(error_message) + async for result in send_event( + ChatEvent.STATUS, "Document search failed. I'll try respond without document references" + ): + yield result if not is_none_or_empty(compiled_references): headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references])) diff --git a/src/khoj/search_type/text_search.py b/src/khoj/search_type/text_search.py index 52e23f29..b67132e4 100644 --- a/src/khoj/search_type/text_search.py +++ b/src/khoj/search_type/text_search.py @@ -3,6 +3,7 @@ import math from pathlib import Path from typing import List, Optional, Tuple, Type, Union +import requests import torch from asgiref.sync import sync_to_async from sentence_transformers import util @@ -231,8 +232,12 @@ def setup( def cross_encoder_score(query: str, hits: List[SearchResponse], search_model_name: str) -> List[SearchResponse]: """Score all retrieved entries using the cross-encoder""" - with timer("Cross-Encoder Predict Time", logger, state.device): - cross_scores = state.cross_encoder_model[search_model_name].predict(query, hits) + try: + with timer("Cross-Encoder Predict Time", logger, state.device): + cross_scores = state.cross_encoder_model[search_model_name].predict(query, hits) + except requests.exceptions.HTTPError as e: + logger.error(f"Failed to rerank documents using the inference endpoint. Error: {e}.", exc_info=True) + cross_scores = [0.0] * len(hits) # Convert cross-encoder scores to distances and pass in hits for reranking for idx in range(len(cross_scores)): From a9325641696dc2a718f9b2e8b85f5cdcfdd9aea6 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Mon, 14 Oct 2024 17:44:46 -0700 Subject: [PATCH 13/71] Try respond even if web search, webpage read fails during chat Khoj shouldn't refuse to respond to user if web lookups fail. It should transparently mention that online search etc. failed. But try respond as best as it can without those references This change ensures a response to the users query is attempted even when web info retrieval fails. --- src/khoj/routers/api_chat.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 93c905b6..0d367029 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -900,12 +900,13 @@ async def chat( yield result[ChatEvent.STATUS] else: online_results = result - except ValueError as e: + except Exception as e: error_message = f"Error searching online: {e}. Attempting to respond without online results" logger.warning(error_message) - async for result in send_llm_response(error_message): + async for result in send_event( + ChatEvent.STATUS, "Online search failed. I'll try respond without online references" + ): yield result - return ## Gather Webpage References if ConversationCommand.Webpage in conversation_commands: @@ -934,11 +935,15 @@ async def chat( webpages.append(webpage["link"]) async for result in send_event(ChatEvent.STATUS, f"**Read web pages**: {webpages}"): yield result - except ValueError as e: + except Exception as e: logger.warning( - f"Error directly reading webpages: {e}. Attempting to respond without online results", + f"Error reading webpages: {e}. Attempting to respond without webpage results", exc_info=True, ) + async for result in send_event( + ChatEvent.STATUS, "Webpage read failed. I'll try respond without webpage references" + ): + yield result ## Send Gathered References async for result in send_event( From 731ea3779eca33d6ada46b3f652b75e38b27121c Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Mon, 14 Oct 2024 18:14:40 -0700 Subject: [PATCH 14/71] Return data sources to use if exception in data source chat actor Previously no value was returned if an exception got triggered when collecting information sources to search. --- src/khoj/routers/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index 245fdf09..a80864ba 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -353,13 +353,13 @@ async def aget_relevant_information_sources( final_response = [ConversationCommand.Default] else: final_response = [ConversationCommand.General] - return final_response - except Exception as e: + except Exception: logger.error(f"Invalid response for determining relevant tools: {response}") if len(agent_tools) == 0: final_response = [ConversationCommand.Default] else: final_response = agent_tools + return final_response async def aget_relevant_output_modes( From 993fd7cd2b4e6e95b308be5617f109c4e62748bd Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 15 Oct 2024 10:52:19 -0700 Subject: [PATCH 15/71] Support using Firecrawl to read webpages Firecrawl is open-source, self-hostable with a default hosted service provided, similar to Jina.ai. So it can be 1. Self-hosted as part of a private Khoj cloud deployment 2. Used directly by getting an API key from the Firecrawl.dev service This is as an alternative to Olostep and Jina.ai for reading webpages. --- src/khoj/processor/tools/online_search.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index f5cb3c12..a9dd2476 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -29,6 +29,9 @@ JINA_READER_API_URL = "https://r.jina.ai/" JINA_SEARCH_API_URL = "https://s.jina.ai/" JINA_API_KEY = os.getenv("JINA_API_KEY") +FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY") +FIRECRAWL_API_URL = os.getenv("FIRECRAWL_API_URL", "https://api.firecrawl.dev") + OLOSTEP_API_KEY = os.getenv("OLOSTEP_API_KEY") OLOSTEP_API_URL = "https://agent.olostep.com/olostep-p2p-incomingAPI" OLOSTEP_QUERY_PARAMS = { @@ -172,7 +175,12 @@ async def read_webpage_and_extract_content( try: if is_none_or_empty(content): with timer(f"Reading web page at '{url}' took", logger): - content = await read_webpage_with_olostep(url) if OLOSTEP_API_KEY else await read_webpage_with_jina(url) + if FIRECRAWL_API_KEY: + content = await read_webpage_with_firecrawl(url) + elif OLOSTEP_API_KEY: + content = await read_webpage_with_olostep(url) + else: + content = await read_webpage_with_jina(url) with timer(f"Extracting relevant information from web page at '{url}' took", logger): extracted_info = await extract_relevant_info(subquery, content, user=user, agent=agent) return subquery, extracted_info, url @@ -220,6 +228,18 @@ async def read_webpage_with_jina(web_url: str) -> str: return response_json["data"]["content"] +async def read_webpage_with_firecrawl(web_url: str) -> str: + firecrawl_api_url = f"{FIRECRAWL_API_URL}/v1/scrape" + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {FIRECRAWL_API_KEY}"} + params = {"url": web_url, "formats": ["markdown"], "excludeTags": ["script", ".ad"]} + + async with aiohttp.ClientSession() as session: + async with session.post(firecrawl_api_url, json=params, headers=headers) as response: + response.raise_for_status() + response_json = await response.json() + return response_json["data"]["markdown"] + + async def search_with_jina(query: str, location: LocationData) -> Tuple[str, Dict[str, List[Dict]]]: encoded_query = urllib.parse.quote(query) jina_search_api_url = f"{JINA_SEARCH_API_URL}/{encoded_query}" From 98f99fa6f86c613f2ec6974e5684b1f8f5f3ddc1 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 15 Oct 2024 12:54:18 -0700 Subject: [PATCH 16/71] Allow using Firecrawl to extract web page content Set the FIRECRAWL_TO_EXTRACT environment variable to true to have Firecrawl scrape and extract content from webpage using their LLM This could be faster, not sure about quality as LLM used is obfuscated --- src/khoj/processor/tools/online_search.py | 51 ++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index a9dd2476..2b4cac65 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -11,6 +11,7 @@ from bs4 import BeautifulSoup from markdownify import markdownify from khoj.database.models import Agent, KhojUser +from khoj.processor.conversation import prompts from khoj.routers.helpers import ( ChatEvent, extract_relevant_info, @@ -31,6 +32,7 @@ JINA_API_KEY = os.getenv("JINA_API_KEY") FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY") FIRECRAWL_API_URL = os.getenv("FIRECRAWL_API_URL", "https://api.firecrawl.dev") +FIRECRAWL_TO_EXTRACT = os.getenv("FIRECRAWL_TO_EXTRACT", "False").lower() == "true" OLOSTEP_API_KEY = os.getenv("OLOSTEP_API_KEY") OLOSTEP_API_URL = "https://agent.olostep.com/olostep-p2p-incomingAPI" @@ -172,21 +174,26 @@ async def read_webpages( async def read_webpage_and_extract_content( subquery: str, url: str, content: str = None, user: KhojUser = None, agent: Agent = None ) -> Tuple[str, Union[None, str], str]: + extracted_info = None try: if is_none_or_empty(content): with timer(f"Reading web page at '{url}' took", logger): if FIRECRAWL_API_KEY: - content = await read_webpage_with_firecrawl(url) + if FIRECRAWL_TO_EXTRACT: + extracted_info = await read_webpage_and_extract_content_with_firecrawl(url, subquery, agent) + else: + content = await read_webpage_with_firecrawl(url) elif OLOSTEP_API_KEY: content = await read_webpage_with_olostep(url) else: content = await read_webpage_with_jina(url) - with timer(f"Extracting relevant information from web page at '{url}' took", logger): - extracted_info = await extract_relevant_info(subquery, content, user=user, agent=agent) - return subquery, extracted_info, url + if is_none_or_empty(extracted_info): + with timer(f"Extracting relevant information from web page at '{url}' took", logger): + extracted_info = await extract_relevant_info(subquery, content, user=user, agent=agent) except Exception as e: logger.error(f"Failed to read web page at '{url}' with {e}") - return subquery, None, url + + return subquery, extracted_info, url async def read_webpage_at_url(web_url: str) -> str: @@ -240,6 +247,40 @@ async def read_webpage_with_firecrawl(web_url: str) -> str: return response_json["data"]["markdown"] +async def read_webpage_and_extract_content_with_firecrawl(web_url: str, query: str, agent: Agent = None) -> str: + firecrawl_api_url = f"{FIRECRAWL_API_URL}/v1/scrape" + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {FIRECRAWL_API_KEY}"} + schema = { + "type": "object", + "properties": { + "relevant_extract": {"type": "string"}, + }, + "required": [ + "relevant_extract", + ], + } + + personality_context = ( + prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else "" + ) + system_prompt = f""" +{prompts.system_prompt_extract_relevant_information} + +{personality_context} +User Query: {query} + +Collate only relevant information from the website to answer the target query and in the provided JSON schema. +""".strip() + + params = {"url": web_url, "formats": ["extract"], "extract": {"systemPrompt": system_prompt, "schema": schema}} + + async with aiohttp.ClientSession() as session: + async with session.post(firecrawl_api_url, json=params, headers=headers) as response: + response.raise_for_status() + response_json = await response.json() + return response_json["data"]["extract"]["relevant_extract"] + + async def search_with_jina(query: str, location: LocationData) -> Tuple[str, Dict[str, List[Dict]]]: encoded_query = urllib.parse.quote(query) jina_search_api_url = f"{JINA_SEARCH_API_URL}/{encoded_query}" From e47922e53a3f91b5909a44670256f1049327ef0c Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 15 Oct 2024 16:07:31 -0700 Subject: [PATCH 17/71] Aggregate webpage extract queries to run once for each distinct webpage This should reduce webpage read and response generation time. Previously, we'd run separate webpage read and extract relevant content pipes for each distinct (query, url) pair. Now we aggregate all queries for each url to extract information from and run the webpage read and extract relevant content pipes once for each distinct url. Even though the webpage content extraction pipes were previously being in parallel. They increased response time by 1. adding more context for the response generation chat actor to respond from 2. and by being more susceptible to page read and extract latencies of the parallel jobs The aggregated retrieval of context for all queries for a given webpage could result in some hit to context quality. But it should improve and reduce variability in response time, quality and costs. --- src/khoj/processor/tools/online_search.py | 49 ++++++++++++----------- src/khoj/routers/helpers.py | 8 ++-- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index 2b4cac65..ea45846b 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -88,33 +88,36 @@ async def search_online( search_results = await asyncio.gather(*search_tasks) response_dict = {subquery: search_result for subquery, search_result in search_results} - # Gather distinct web page data from organic results of each subquery without an instant answer. + # Gather distinct web pages from organic results for subqueries without an instant answer. # Content of web pages is directly available when Jina is used for search. - webpages = { - (organic.get("link"), subquery, organic.get("content")) - for subquery in response_dict - for organic in response_dict[subquery].get("organic", [])[:MAX_WEBPAGES_TO_READ] - if "answerBox" not in response_dict[subquery] - } + webpages: Dict[str, Dict] = {} + for subquery in response_dict: + if "answerBox" in response_dict[subquery]: + continue + for organic in response_dict[subquery].get("organic", [])[:MAX_WEBPAGES_TO_READ]: + link = organic.get("link") + if link in webpages: + webpages[link]["queries"].add(subquery) + else: + webpages[link] = {"queries": {subquery}, "content": organic.get("content")} # Read, extract relevant info from the retrieved web pages if webpages: - webpage_links = set([link for link, _, _ in webpages]) - logger.info(f"Reading web pages at: {list(webpage_links)}") + logger.info(f"Reading web pages at: {webpages.keys()}") if send_status_func: - webpage_links_str = "\n- " + "\n- ".join(list(webpage_links)) + webpage_links_str = "\n- " + "\n- ".join(webpages.keys()) async for event in send_status_func(f"**Reading web pages**: {webpage_links_str}"): yield {ChatEvent.STATUS: event} tasks = [ - read_webpage_and_extract_content(subquery, link, content, user=user, agent=agent) - for link, subquery, content in webpages + read_webpage_and_extract_content(data["queries"], link, data["content"], user=user, agent=agent) + for link, data in webpages.items() ] results = await asyncio.gather(*tasks) # Collect extracted info from the retrieved web pages - for subquery, webpage_extract, url in results: + for subqueries, url, webpage_extract in results: if webpage_extract is not None: - response_dict[subquery]["webpages"] = {"link": url, "snippet": webpage_extract} + response_dict[subqueries.pop()]["webpages"] = {"link": url, "snippet": webpage_extract} yield response_dict @@ -161,26 +164,26 @@ async def read_webpages( webpage_links_str = "\n- " + "\n- ".join(list(urls)) async for event in send_status_func(f"**Reading web pages**: {webpage_links_str}"): yield {ChatEvent.STATUS: event} - tasks = [read_webpage_and_extract_content(query, url, user=user, agent=agent) for url in urls] + tasks = [read_webpage_and_extract_content({query}, url, user=user, agent=agent) for url in urls] results = await asyncio.gather(*tasks) response: Dict[str, Dict] = defaultdict(dict) response[query]["webpages"] = [ - {"query": q, "link": url, "snippet": web_extract} for q, web_extract, url in results if web_extract is not None + {"query": qs.pop(), "link": url, "snippet": extract} for qs, url, extract in results if extract is not None ] yield response async def read_webpage_and_extract_content( - subquery: str, url: str, content: str = None, user: KhojUser = None, agent: Agent = None -) -> Tuple[str, Union[None, str], str]: + subqueries: set[str], url: str, content: str = None, user: KhojUser = None, agent: Agent = None +) -> Tuple[set[str], str, Union[None, str]]: extracted_info = None try: if is_none_or_empty(content): with timer(f"Reading web page at '{url}' took", logger): if FIRECRAWL_API_KEY: if FIRECRAWL_TO_EXTRACT: - extracted_info = await read_webpage_and_extract_content_with_firecrawl(url, subquery, agent) + extracted_info = await read_webpage_and_extract_content_with_firecrawl(url, subqueries, agent) else: content = await read_webpage_with_firecrawl(url) elif OLOSTEP_API_KEY: @@ -189,11 +192,11 @@ async def read_webpage_and_extract_content( content = await read_webpage_with_jina(url) if is_none_or_empty(extracted_info): with timer(f"Extracting relevant information from web page at '{url}' took", logger): - extracted_info = await extract_relevant_info(subquery, content, user=user, agent=agent) + extracted_info = await extract_relevant_info(subqueries, content, user=user, agent=agent) except Exception as e: logger.error(f"Failed to read web page at '{url}' with {e}") - return subquery, extracted_info, url + return subqueries, url, extracted_info async def read_webpage_at_url(web_url: str) -> str: @@ -247,7 +250,7 @@ async def read_webpage_with_firecrawl(web_url: str) -> str: return response_json["data"]["markdown"] -async def read_webpage_and_extract_content_with_firecrawl(web_url: str, query: str, agent: Agent = None) -> str: +async def read_webpage_and_extract_content_with_firecrawl(web_url: str, queries: set[str], agent: Agent = None) -> str: firecrawl_api_url = f"{FIRECRAWL_API_URL}/v1/scrape" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {FIRECRAWL_API_KEY}"} schema = { @@ -267,7 +270,7 @@ async def read_webpage_and_extract_content_with_firecrawl(web_url: str, query: s {prompts.system_prompt_extract_relevant_information} {personality_context} -User Query: {query} +User Query: {", ".join(queries)} Collate only relevant information from the website to answer the target query and in the provided JSON schema. """.strip() diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index a80864ba..4edef61d 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -551,12 +551,14 @@ async def schedule_query( raise AssertionError(f"Invalid response for scheduling query: {raw_response}") -async def extract_relevant_info(q: str, corpus: str, user: KhojUser = None, agent: Agent = None) -> Union[str, None]: +async def extract_relevant_info( + qs: set[str], corpus: str, user: KhojUser = None, agent: Agent = None +) -> Union[str, None]: """ Extract relevant information for a given query from the target corpus """ - if is_none_or_empty(corpus) or is_none_or_empty(q): + if is_none_or_empty(corpus) or is_none_or_empty(qs): return None personality_context = ( @@ -564,7 +566,7 @@ async def extract_relevant_info(q: str, corpus: str, user: KhojUser = None, agen ) extract_relevant_information = prompts.extract_relevant_information.format( - query=q, + query=", ".join(qs), corpus=corpus.strip(), personality_context=personality_context, ) From c841abe13f3cde06690e5c818ccd05dc40b4e74f Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 15 Oct 2024 17:17:36 -0700 Subject: [PATCH 18/71] Change webpage scraper to use via server admin panel --- src/khoj/database/adapters/__init__.py | 13 ++++++++++++ src/khoj/database/admin.py | 1 + .../0068_serverchatsettings_web_scraper.py | 21 +++++++++++++++++++ src/khoj/database/models/__init__.py | 7 +++++++ src/khoj/processor/tools/online_search.py | 13 +++++++----- 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 182ce701..51b8afe6 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -1031,6 +1031,19 @@ class ConversationAdapters: return server_chat_settings.chat_advanced return await ConversationAdapters.aget_default_conversation_config(user) + @staticmethod + async def aget_webscraper(FIRECRAWL_API_KEY: str = None, OLOSTEP_API_KEY: str = None): + server_chat_settings: ServerChatSettings = await ServerChatSettings.objects.filter().afirst() + if server_chat_settings is not None and server_chat_settings.web_scraper is not None: + web_scraper = ServerChatSettings.WebScraper(server_chat_settings.web_scraper) + if (web_scraper == ServerChatSettings.WebScraper.FIRECRAWL and FIRECRAWL_API_KEY) or ( + web_scraper == ServerChatSettings.WebScraper.OLOSTEP and OLOSTEP_API_KEY + ): + return web_scraper + # Fallback to JinaAI if the API keys for the other providers are not set + # JinaAI is the default web scraper as it does not require an API key + return ServerChatSettings.WebScraper.JINAAI + @staticmethod def create_conversation_from_public_conversation( user: KhojUser, public_conversation: PublicConversation, client_app: ClientApplication diff --git a/src/khoj/database/admin.py b/src/khoj/database/admin.py index 3e192952..51988752 100644 --- a/src/khoj/database/admin.py +++ b/src/khoj/database/admin.py @@ -198,6 +198,7 @@ class ServerChatSettingsAdmin(admin.ModelAdmin): list_display = ( "chat_default", "chat_advanced", + "web_scraper", ) diff --git a/src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py b/src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py new file mode 100644 index 00000000..89482dbd --- /dev/null +++ b/src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.8 on 2024-10-16 00:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("database", "0067_alter_agent_style_icon"), + ] + + operations = [ + migrations.AddField( + model_name="serverchatsettings", + name="web_scraper", + field=models.CharField( + choices=[("firecrawl", "Firecrawl"), ("olostep", "Olostep"), ("jinaai", "JinaAI")], + default="jinaai", + max_length=20, + ), + ), + ] diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index ec4b61d1..7c4a16fa 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.db.models.signals import pre_save from django.dispatch import receiver +from django.utils.translation import gettext_lazy from pgvector.django import VectorField from phonenumber_field.modelfields import PhoneNumberField @@ -245,12 +246,18 @@ class GithubRepoConfig(BaseModel): class ServerChatSettings(BaseModel): + class WebScraper(models.TextChoices): + FIRECRAWL = "firecrawl", gettext_lazy("Firecrawl") + OLOSTEP = "olostep", gettext_lazy("Olostep") + JINAAI = "jinaai", gettext_lazy("JinaAI") + chat_default = models.ForeignKey( ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_default" ) chat_advanced = models.ForeignKey( ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_advanced" ) + web_scraper = models.CharField(max_length=20, choices=WebScraper.choices, default=WebScraper.JINAAI) class LocalOrgConfig(BaseModel): diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index ea45846b..df9b180f 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -10,7 +10,8 @@ import aiohttp from bs4 import BeautifulSoup from markdownify import markdownify -from khoj.database.models import Agent, KhojUser +from khoj.database.adapters import ConversationAdapters +from khoj.database.models import Agent, KhojUser, ServerChatSettings from khoj.processor.conversation import prompts from khoj.routers.helpers import ( ChatEvent, @@ -177,16 +178,18 @@ async def read_webpages( async def read_webpage_and_extract_content( subqueries: set[str], url: str, content: str = None, user: KhojUser = None, agent: Agent = None ) -> Tuple[set[str], str, Union[None, str]]: + # Select the web scraper to use for reading the web page + web_scraper = await ConversationAdapters.aget_webscraper(FIRECRAWL_API_KEY, OLOSTEP_API_KEY) extracted_info = None try: if is_none_or_empty(content): - with timer(f"Reading web page at '{url}' took", logger): - if FIRECRAWL_API_KEY: + with timer(f"Reading web page with {web_scraper.value} at '{url}' took", logger): + if web_scraper == ServerChatSettings.WebScraper.FIRECRAWL: if FIRECRAWL_TO_EXTRACT: extracted_info = await read_webpage_and_extract_content_with_firecrawl(url, subqueries, agent) else: content = await read_webpage_with_firecrawl(url) - elif OLOSTEP_API_KEY: + elif web_scraper == ServerChatSettings.WebScraper.OLOSTEP: content = await read_webpage_with_olostep(url) else: content = await read_webpage_with_jina(url) @@ -194,7 +197,7 @@ async def read_webpage_and_extract_content( with timer(f"Extracting relevant information from web page at '{url}' took", logger): extracted_info = await extract_relevant_info(subqueries, content, user=user, agent=agent) except Exception as e: - logger.error(f"Failed to read web page at '{url}' with {e}") + logger.error(f"Failed to read web page with {web_scraper.value} at '{url}' with {e}") return subqueries, url, extracted_info From 11c64791aa53a8715b5423fae82fe256d34dff9d Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 15 Oct 2024 17:56:21 -0700 Subject: [PATCH 19/71] Allow changing perf timer log level. Info log time for webpage read --- src/khoj/processor/tools/online_search.py | 2 +- src/khoj/routers/helpers.py | 11 +++++------ src/khoj/utils/helpers.py | 8 ++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index df9b180f..2fbe8cf3 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -183,7 +183,7 @@ async def read_webpage_and_extract_content( extracted_info = None try: if is_none_or_empty(content): - with timer(f"Reading web page with {web_scraper.value} at '{url}' took", logger): + with timer(f"Reading web page with {web_scraper.value} at '{url}' took", logger, log_level=logging.INFO): if web_scraper == ServerChatSettings.WebScraper.FIRECRAWL: if FIRECRAWL_TO_EXTRACT: extracted_info = await read_webpage_and_extract_content_with_firecrawl(url, subqueries, agent) diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index 4edef61d..c3d997e9 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -571,12 +571,11 @@ async def extract_relevant_info( personality_context=personality_context, ) - with timer("Chat actor: Extract relevant information from data", logger): - response = await send_message_to_model_wrapper( - extract_relevant_information, - prompts.system_prompt_extract_relevant_information, - user=user, - ) + response = await send_message_to_model_wrapper( + extract_relevant_information, + prompts.system_prompt_extract_relevant_information, + user=user, + ) return response.strip() diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index e0908e51..f16f922c 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -164,9 +164,9 @@ def get_class_by_name(name: str) -> object: class timer: """Context manager to log time taken for a block of code to run""" - def __init__(self, message: str, logger: logging.Logger, device: torch.device = None): + def __init__(self, message: str, logger: logging.Logger, device: torch.device = None, log_level=logging.DEBUG): self.message = message - self.logger = logger + self.logger = logger.debug if log_level == logging.DEBUG else logger.info self.device = device def __enter__(self): @@ -176,9 +176,9 @@ class timer: def __exit__(self, *_): elapsed = perf_counter() - self.start if self.device is None: - self.logger.debug(f"{self.message}: {elapsed:.3f} seconds") + self.logger(f"{self.message}: {elapsed:.3f} seconds") else: - self.logger.debug(f"{self.message}: {elapsed:.3f} seconds on device: {self.device}") + self.logger(f"{self.message}: {elapsed:.3f} seconds on device: {self.device}") class LRU(OrderedDict): From d94abba2dc8ed6e8f31a86b8792c9e57413abc5e Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 16 Oct 2024 00:37:46 -0700 Subject: [PATCH 20/71] Fallback through enabled scrapers to reduce web page read failures - Set up scrapers via API keys, explicitly adding them via admin panel or enabling only a single scraper to use via server chat settings. - Use validation to ensure only valid scrapers added via admin panel Example API key is present for scrapers that require it etc. - Modularize the read webpage functions to take api key, url as args Removes dependence on constants loaded in online_search. Functions are now mostly self contained - Improve ability to read webpages by using the speed, success rate of different scrapers. Optimal configuration needs to be discovered --- src/khoj/database/adapters/__init__.py | 49 +++++++--- src/khoj/database/admin.py | 14 +++ .../0068_serverchatsettings_web_scraper.py | 21 ----- ...bscraper_serverchatsettings_web_scraper.py | 47 ++++++++++ src/khoj/database/models/__init__.py | 50 ++++++++-- src/khoj/processor/tools/online_search.py | 91 +++++++++++-------- 6 files changed, 196 insertions(+), 76 deletions(-) delete mode 100644 src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py create mode 100644 src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 51b8afe6..8c6aa5e4 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -1,6 +1,7 @@ import json import logging import math +import os import random import re import secrets @@ -10,7 +11,6 @@ from enum import Enum from typing import Callable, Iterable, List, Optional, Type import cron_descriptor -import django from apscheduler.job import Job from asgiref.sync import sync_to_async from django.contrib.sessions.backends.db import SessionStore @@ -52,6 +52,7 @@ from khoj.database.models import ( UserTextToImageModelConfig, UserVoiceModelConfig, VoiceModelOption, + WebScraper, ) from khoj.processor.conversation import prompts from khoj.search_filter.date_filter import DateFilter @@ -1032,17 +1033,43 @@ class ConversationAdapters: return await ConversationAdapters.aget_default_conversation_config(user) @staticmethod - async def aget_webscraper(FIRECRAWL_API_KEY: str = None, OLOSTEP_API_KEY: str = None): - server_chat_settings: ServerChatSettings = await ServerChatSettings.objects.filter().afirst() + async def aget_server_webscraper(): + server_chat_settings = await ServerChatSettings.objects.filter().prefetch_related("web_scraper").afirst() if server_chat_settings is not None and server_chat_settings.web_scraper is not None: - web_scraper = ServerChatSettings.WebScraper(server_chat_settings.web_scraper) - if (web_scraper == ServerChatSettings.WebScraper.FIRECRAWL and FIRECRAWL_API_KEY) or ( - web_scraper == ServerChatSettings.WebScraper.OLOSTEP and OLOSTEP_API_KEY - ): - return web_scraper - # Fallback to JinaAI if the API keys for the other providers are not set - # JinaAI is the default web scraper as it does not require an API key - return ServerChatSettings.WebScraper.JINAAI + return server_chat_settings.web_scraper + return None + + @staticmethod + async def aget_enabled_webscrapers(): + enabled_scrapers = [] + server_webscraper = await ConversationAdapters.aget_server_webscraper() + if server_webscraper: + # Only use the webscraper set in the server chat settings + enabled_scrapers = [ + (server_webscraper.type, server_webscraper.api_key, server_webscraper.api_url, server_webscraper.name) + ] + if not enabled_scrapers: + # Use the enabled web scrapers, using the newest created scraper first, until get web page content + enabled_scrapers = [ + (scraper.type, scraper.api_key, scraper.api_url, scraper.name) + async for scraper in WebScraper.objects.all().order_by("-created_at").aiterator() + ] + if not enabled_scrapers: + # Use scrapers enabled via environment variables + if os.getenv("FIRECRAWL_API_KEY"): + api_url = os.getenv("FIRECRAWL_API_URL", "https://api.firecrawl.dev") + enabled_scrapers.append( + (WebScraper.WebScraperType.FIRECRAWL, os.getenv("FIRECRAWL_API_KEY"), api_url, "Firecrawl") + ) + if os.getenv("OLOSTEP_API_KEY"): + api_url = os.getenv("OLOSTEP_API_URL", "https://agent.olostep.com/olostep-p2p-incomingAPI") + enabled_scrapers.append( + (WebScraper.WebScraperType.OLOSTEP, os.getenv("OLOSTEP_API_KEY"), api_url, "Olostep") + ) + # Jina is the default fallback scraper to use as it does not require an API key + api_url = os.getenv("JINA_READER_API_URL", "https://r.jina.ai/") + enabled_scrapers.append((WebScraper.WebScraperType.JINA, os.getenv("JINA_API_KEY"), api_url, "Jina")) + return enabled_scrapers @staticmethod def create_conversation_from_public_conversation( diff --git a/src/khoj/database/admin.py b/src/khoj/database/admin.py index 51988752..8e650922 100644 --- a/src/khoj/database/admin.py +++ b/src/khoj/database/admin.py @@ -31,6 +31,7 @@ from khoj.database.models import ( UserSearchModelConfig, UserVoiceModelConfig, VoiceModelOption, + WebScraper, ) from khoj.utils.helpers import ImageIntentType @@ -202,6 +203,19 @@ class ServerChatSettingsAdmin(admin.ModelAdmin): ) +@admin.register(WebScraper) +class WebScraperAdmin(admin.ModelAdmin): + list_display = ( + "name", + "type", + "api_key", + "api_url", + "created_at", + ) + search_fields = ("name", "api_key", "api_url", "type") + ordering = ("-created_at",) + + @admin.register(Conversation) class ConversationAdmin(admin.ModelAdmin): list_display = ( diff --git a/src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py b/src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py deleted file mode 100644 index 89482dbd..00000000 --- a/src/khoj/database/migrations/0068_serverchatsettings_web_scraper.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.0.8 on 2024-10-16 00:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("database", "0067_alter_agent_style_icon"), - ] - - operations = [ - migrations.AddField( - model_name="serverchatsettings", - name="web_scraper", - field=models.CharField( - choices=[("firecrawl", "Firecrawl"), ("olostep", "Olostep"), ("jinaai", "JinaAI")], - default="jinaai", - max_length=20, - ), - ), - ] diff --git a/src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py b/src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py new file mode 100644 index 00000000..41d9c80b --- /dev/null +++ b/src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py @@ -0,0 +1,47 @@ +# Generated by Django 5.0.8 on 2024-10-16 06:51 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("database", "0067_alter_agent_style_icon"), + ] + + operations = [ + migrations.CreateModel( + name="WebScraper", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("name", models.CharField(blank=True, default=None, max_length=200, null=True, unique=True)), + ( + "type", + models.CharField( + choices=[("firecrawl", "Firecrawl"), ("olostep", "Olostep"), ("jina", "Jina")], + default="jina", + max_length=20, + ), + ), + ("api_key", models.CharField(blank=True, default=None, max_length=200, null=True)), + ("api_url", models.URLField(blank=True, default=None, null=True)), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="serverchatsettings", + name="web_scraper", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="web_scraper", + to="database.webscraper", + ), + ), + ] diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index 7c4a16fa..ec36c6f3 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -1,3 +1,4 @@ +import os import re import uuid from random import choice @@ -12,8 +13,6 @@ from django.utils.translation import gettext_lazy from pgvector.django import VectorField from phonenumber_field.modelfields import PhoneNumberField -from khoj.utils.helpers import ConversationCommand - class BaseModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) @@ -245,19 +244,58 @@ class GithubRepoConfig(BaseModel): github_config = models.ForeignKey(GithubConfig, on_delete=models.CASCADE, related_name="githubrepoconfig") -class ServerChatSettings(BaseModel): - class WebScraper(models.TextChoices): +class WebScraper(BaseModel): + class WebScraperType(models.TextChoices): FIRECRAWL = "firecrawl", gettext_lazy("Firecrawl") OLOSTEP = "olostep", gettext_lazy("Olostep") - JINAAI = "jinaai", gettext_lazy("JinaAI") + JINA = "jina", gettext_lazy("Jina") + name = models.CharField(max_length=200, default=None, null=True, blank=True, unique=True) + type = models.CharField(max_length=20, choices=WebScraperType.choices, default=WebScraperType.JINA) + api_key = models.CharField(max_length=200, default=None, null=True, blank=True) + api_url = models.URLField(max_length=200, default=None, null=True, blank=True) + + def clean(self): + error = {} + if self.name is None: + self.name = self.type.capitalize() + if self.api_url is None: + if self.type == self.WebScraperType.FIRECRAWL: + self.api_url = os.getenv("FIRECRAWL_API_URL", "https://api.firecrawl.dev") + elif self.type == self.WebScraperType.OLOSTEP: + self.api_url = os.getenv("OLOSTEP_API_URL", "https://agent.olostep.com/olostep-p2p-incomingAPI") + elif self.type == self.WebScraperType.JINA: + self.api_url = os.getenv("JINA_READER_API_URL", "https://r.jina.ai/") + if self.api_key is None: + if self.type == self.WebScraperType.FIRECRAWL: + self.api_key = os.getenv("FIRECRAWL_API_KEY") + if not self.api_key and self.api_url == "https://api.firecrawl.dev": + error["api_key"] = "Set API key to use default Firecrawl. Get API key from https://firecrawl.dev." + elif self.type == self.WebScraperType.OLOSTEP: + self.api_key = os.getenv("OLOSTEP_API_KEY") + if self.api_key is None: + error["api_key"] = "Set API key to use Olostep. Get API key from https://olostep.com/." + elif self.type == self.WebScraperType.JINA: + self.api_key = os.getenv("JINA_API_KEY") + + if error: + raise ValidationError(error) + + def save(self, *args, **kwargs): + self.clean() + super().save(*args, **kwargs) + + +class ServerChatSettings(BaseModel): chat_default = models.ForeignKey( ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_default" ) chat_advanced = models.ForeignKey( ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_advanced" ) - web_scraper = models.CharField(max_length=20, choices=WebScraper.choices, default=WebScraper.JINAAI) + web_scraper = models.ForeignKey( + WebScraper, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="web_scraper" + ) class LocalOrgConfig(BaseModel): diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index 2fbe8cf3..c111415b 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -11,7 +11,7 @@ from bs4 import BeautifulSoup from markdownify import markdownify from khoj.database.adapters import ConversationAdapters -from khoj.database.models import Agent, KhojUser, ServerChatSettings +from khoj.database.models import Agent, KhojUser, WebScraper from khoj.processor.conversation import prompts from khoj.routers.helpers import ( ChatEvent, @@ -27,16 +27,11 @@ logger = logging.getLogger(__name__) SERPER_DEV_API_KEY = os.getenv("SERPER_DEV_API_KEY") SERPER_DEV_URL = "https://google.serper.dev/search" -JINA_READER_API_URL = "https://r.jina.ai/" JINA_SEARCH_API_URL = "https://s.jina.ai/" JINA_API_KEY = os.getenv("JINA_API_KEY") -FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY") -FIRECRAWL_API_URL = os.getenv("FIRECRAWL_API_URL", "https://api.firecrawl.dev") FIRECRAWL_TO_EXTRACT = os.getenv("FIRECRAWL_TO_EXTRACT", "False").lower() == "true" -OLOSTEP_API_KEY = os.getenv("OLOSTEP_API_KEY") -OLOSTEP_API_URL = "https://agent.olostep.com/olostep-p2p-incomingAPI" OLOSTEP_QUERY_PARAMS = { "timeout": 35, # seconds "waitBeforeScraping": 1, # seconds @@ -175,29 +170,47 @@ async def read_webpages( yield response +async def read_webpage( + url, scraper_type=None, api_key=None, api_url=None, subqueries=None, agent=None +) -> Tuple[str | None, str | None]: + if scraper_type == WebScraper.WebScraperType.FIRECRAWL and FIRECRAWL_TO_EXTRACT: + return None, await query_webpage_with_firecrawl(url, subqueries, api_key, api_url, agent) + elif scraper_type == WebScraper.WebScraperType.FIRECRAWL: + return await read_webpage_with_firecrawl(url, api_key, api_url), None + elif scraper_type == WebScraper.WebScraperType.OLOSTEP: + return await read_webpage_with_olostep(url, api_key, api_url), None + else: + return await read_webpage_with_jina(url, api_key, api_url), None + + async def read_webpage_and_extract_content( subqueries: set[str], url: str, content: str = None, user: KhojUser = None, agent: Agent = None ) -> Tuple[set[str], str, Union[None, str]]: - # Select the web scraper to use for reading the web page - web_scraper = await ConversationAdapters.aget_webscraper(FIRECRAWL_API_KEY, OLOSTEP_API_KEY) + # Select the web scrapers to use for reading the web page + web_scrapers = await ConversationAdapters.aget_enabled_webscrapers() + + # Fallback through enabled web scrapers until we successfully read the web page extracted_info = None - try: - if is_none_or_empty(content): - with timer(f"Reading web page with {web_scraper.value} at '{url}' took", logger, log_level=logging.INFO): - if web_scraper == ServerChatSettings.WebScraper.FIRECRAWL: - if FIRECRAWL_TO_EXTRACT: - extracted_info = await read_webpage_and_extract_content_with_firecrawl(url, subqueries, agent) - else: - content = await read_webpage_with_firecrawl(url) - elif web_scraper == ServerChatSettings.WebScraper.OLOSTEP: - content = await read_webpage_with_olostep(url) - else: - content = await read_webpage_with_jina(url) - if is_none_or_empty(extracted_info): - with timer(f"Extracting relevant information from web page at '{url}' took", logger): - extracted_info = await extract_relevant_info(subqueries, content, user=user, agent=agent) - except Exception as e: - logger.error(f"Failed to read web page with {web_scraper.value} at '{url}' with {e}") + for scraper_type, api_key, api_url, api_name in web_scrapers: + try: + # Read the web page + if is_none_or_empty(content): + with timer(f"Reading web page with {scraper_type} at '{url}' took", logger, log_level=logging.INFO): + content, extracted_info = await read_webpage(url, scraper_type, api_key, api_url, subqueries, agent) + + # Extract relevant information from the web page + if is_none_or_empty(extracted_info): + with timer(f"Extracting relevant information from web page at '{url}' took", logger): + extracted_info = await extract_relevant_info(subqueries, content, user=user, agent=agent) + + # If we successfully extracted information, break the loop + if not is_none_or_empty(extracted_info): + break + except Exception as e: + logger.warning(f"Failed to read web page with {scraper_type} at '{url}' with {e}") + # If this is the last web scraper in the list, log an error + if api_name == web_scrapers[-1][-1]: + logger.error(f"All web scrapers failed for '{url}'") return subqueries, url, extracted_info @@ -216,23 +229,23 @@ async def read_webpage_at_url(web_url: str) -> str: return markdownify(body) -async def read_webpage_with_olostep(web_url: str) -> str: - headers = {"Authorization": f"Bearer {OLOSTEP_API_KEY}"} +async def read_webpage_with_olostep(web_url: str, api_key: str, api_url: str) -> str: + headers = {"Authorization": f"Bearer {api_key}"} web_scraping_params: Dict[str, Union[str, int, bool]] = OLOSTEP_QUERY_PARAMS.copy() # type: ignore web_scraping_params["url"] = web_url async with aiohttp.ClientSession() as session: - async with session.get(OLOSTEP_API_URL, params=web_scraping_params, headers=headers) as response: + async with session.get(api_url, params=web_scraping_params, headers=headers) as response: response.raise_for_status() response_json = await response.json() return response_json["markdown_content"] -async def read_webpage_with_jina(web_url: str) -> str: - jina_reader_api_url = f"{JINA_READER_API_URL}/{web_url}" +async def read_webpage_with_jina(web_url: str, api_key: str, api_url: str) -> str: + jina_reader_api_url = f"{api_url}/{web_url}" headers = {"Accept": "application/json", "X-Timeout": "30"} - if JINA_API_KEY: - headers["Authorization"] = f"Bearer {JINA_API_KEY}" + if api_key: + headers["Authorization"] = f"Bearer {api_key}" async with aiohttp.ClientSession() as session: async with session.get(jina_reader_api_url, headers=headers) as response: @@ -241,9 +254,9 @@ async def read_webpage_with_jina(web_url: str) -> str: return response_json["data"]["content"] -async def read_webpage_with_firecrawl(web_url: str) -> str: - firecrawl_api_url = f"{FIRECRAWL_API_URL}/v1/scrape" - headers = {"Content-Type": "application/json", "Authorization": f"Bearer {FIRECRAWL_API_KEY}"} +async def read_webpage_with_firecrawl(web_url: str, api_key: str, api_url: str) -> str: + firecrawl_api_url = f"{api_url}/v1/scrape" + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"} params = {"url": web_url, "formats": ["markdown"], "excludeTags": ["script", ".ad"]} async with aiohttp.ClientSession() as session: @@ -253,9 +266,11 @@ async def read_webpage_with_firecrawl(web_url: str) -> str: return response_json["data"]["markdown"] -async def read_webpage_and_extract_content_with_firecrawl(web_url: str, queries: set[str], agent: Agent = None) -> str: - firecrawl_api_url = f"{FIRECRAWL_API_URL}/v1/scrape" - headers = {"Content-Type": "application/json", "Authorization": f"Bearer {FIRECRAWL_API_KEY}"} +async def query_webpage_with_firecrawl( + web_url: str, queries: set[str], api_key: str, api_url: str, agent: Agent = None +) -> str: + firecrawl_api_url = f"{api_url}/v1/scrape" + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"} schema = { "type": "object", "properties": { From 20b6f0c2f4857157f47b32cf7dd199aac1f40d9b Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 16 Oct 2024 02:57:51 -0700 Subject: [PATCH 21/71] Access internal links directly via a simple get request The other webpage scrapers will not work for internal webpages. Try access those urls directly if they are visible to the Khoj server over the network. Only enable this by default for self-hosted, single user setups. Otherwise ability to scan internal network would be a liability! For use-cases where it makes sense, the Khoj server admin can explicitly add the direct webpage scraper via the admin panel --- src/khoj/database/adapters/__init__.py | 16 +++++++- src/khoj/database/models/__init__.py | 1 + src/khoj/processor/tools/online_search.py | 17 +++++++-- src/khoj/utils/helpers.py | 46 +++++++++++++++++++++++ 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 8c6aa5e4..7be931c5 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -60,7 +60,12 @@ from khoj.search_filter.file_filter import FileFilter from khoj.search_filter.word_filter import WordFilter from khoj.utils import state from khoj.utils.config import OfflineChatProcessorModel -from khoj.utils.helpers import generate_random_name, is_none_or_empty, timer +from khoj.utils.helpers import ( + generate_random_name, + in_debug_mode, + is_none_or_empty, + timer, +) logger = logging.getLogger(__name__) @@ -1066,9 +1071,16 @@ class ConversationAdapters: enabled_scrapers.append( (WebScraper.WebScraperType.OLOSTEP, os.getenv("OLOSTEP_API_KEY"), api_url, "Olostep") ) - # Jina is the default fallback scraper to use as it does not require an API key + + # Jina is the default fallback scrapers to use as it does not require an API key api_url = os.getenv("JINA_READER_API_URL", "https://r.jina.ai/") enabled_scrapers.append((WebScraper.WebScraperType.JINA, os.getenv("JINA_API_KEY"), api_url, "Jina")) + + # Only enable the direct web page scraper by default in self-hosted single user setups. + # Useful for reading webpages on your intranet. + if state.anonymous_mode or in_debug_mode(): + enabled_scrapers.append((WebScraper.WebScraperType.DIRECT, None, None, "Direct")) + return enabled_scrapers @staticmethod diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index ec36c6f3..56f482ae 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -249,6 +249,7 @@ class WebScraper(BaseModel): FIRECRAWL = "firecrawl", gettext_lazy("Firecrawl") OLOSTEP = "olostep", gettext_lazy("Olostep") JINA = "jina", gettext_lazy("Jina") + DIRECT = "direct", gettext_lazy("Direct") name = models.CharField(max_length=200, default=None, null=True, blank=True, unique=True) type = models.CharField(max_length=20, choices=WebScraperType.choices, default=WebScraperType.JINA) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index c111415b..c00660e3 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -19,7 +19,13 @@ from khoj.routers.helpers import ( generate_online_subqueries, infer_webpage_urls, ) -from khoj.utils.helpers import is_internet_connected, is_none_or_empty, timer +from khoj.utils.helpers import ( + is_env_var_true, + is_internal_url, + is_internet_connected, + is_none_or_empty, + timer, +) from khoj.utils.rawconfig import LocationData logger = logging.getLogger(__name__) @@ -30,7 +36,7 @@ SERPER_DEV_URL = "https://google.serper.dev/search" JINA_SEARCH_API_URL = "https://s.jina.ai/" JINA_API_KEY = os.getenv("JINA_API_KEY") -FIRECRAWL_TO_EXTRACT = os.getenv("FIRECRAWL_TO_EXTRACT", "False").lower() == "true" +FIRECRAWL_TO_EXTRACT = is_env_var_true("FIRECRAWL_TO_EXTRACT") OLOSTEP_QUERY_PARAMS = { "timeout": 35, # seconds @@ -179,8 +185,10 @@ async def read_webpage( return await read_webpage_with_firecrawl(url, api_key, api_url), None elif scraper_type == WebScraper.WebScraperType.OLOSTEP: return await read_webpage_with_olostep(url, api_key, api_url), None - else: + elif scraper_type == WebScraper.WebScraperType.JINA: return await read_webpage_with_jina(url, api_key, api_url), None + else: + return await read_webpage_at_url(url), None async def read_webpage_and_extract_content( @@ -188,6 +196,9 @@ async def read_webpage_and_extract_content( ) -> Tuple[set[str], str, Union[None, str]]: # Select the web scrapers to use for reading the web page web_scrapers = await ConversationAdapters.aget_enabled_webscrapers() + # Only use the direct web scraper for internal URLs + if is_internal_url(url): + web_scrapers = [scraper for scraper in web_scrapers if scraper[0] == WebScraper.WebScraperType.DIRECT] # Fallback through enabled web scrapers until we successfully read the web page extracted_info = None diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index f16f922c..4e5736a2 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -2,10 +2,12 @@ from __future__ import annotations # to avoid quoting type hints import datetime import io +import ipaddress import logging import os import platform import random +import urllib.parse import uuid from collections import OrderedDict from enum import Enum @@ -436,6 +438,50 @@ def is_internet_connected(): return False +def is_internal_url(url: str) -> bool: + """ + Check if a URL is likely to be internal/non-public. + + Args: + url (str): The URL to check. + + Returns: + bool: True if the URL is likely internal, False otherwise. + """ + try: + parsed_url = urllib.parse.urlparse(url) + hostname = parsed_url.hostname + + # Check for localhost + if hostname in ["localhost", "127.0.0.1", "::1"]: + return True + + # Check for IP addresses in private ranges + try: + ip = ipaddress.ip_address(hostname) + return ip.is_private + except ValueError: + pass # Not an IP address, continue with other checks + + # Check for common internal TLDs + internal_tlds = [".local", ".internal", ".private", ".corp", ".home", ".lan"] + if any(hostname.endswith(tld) for tld in internal_tlds): + return True + + # Check for non-standard ports + # if parsed_url.port and parsed_url.port not in [80, 443]: + # return True + + # Check for URLs without a TLD + if "." not in hostname: + return True + + return False + except Exception: + # If we can't parse the URL or something else goes wrong, assume it's not internal + return False + + def convert_image_to_webp(image_bytes): """Convert image bytes to webp format for faster loading""" image_io = io.BytesIO(image_bytes) From 0db52786ed0a353a003a52453f4010286b71eebb Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 16:22:46 -0700 Subject: [PATCH 22/71] Make web scraper priority configurable via admin panel - Simplifies changing order in which web scrapers are invoked to read web page by just changing their priority number on the admin panel. Previously you'd have to delete/, re-add the scrapers to change their priority. - Add help text for each scraper field to ease admin setup experience - Friendlier env var to use Firecrawl's LLM to extract content - Remove use of separate friendly name for scraper types. Reuse actual name and just make actual name better --- src/khoj/database/adapters/__init__.py | 4 +- src/khoj/database/admin.py | 3 +- ...bscraper_serverchatsettings_web_scraper.py | 47 ---------- ...bscraper_serverchatsettings_web_scraper.py | 89 +++++++++++++++++++ src/khoj/database/models/__init__.py | 47 ++++++++-- src/khoj/processor/tools/online_search.py | 4 +- 6 files changed, 133 insertions(+), 61 deletions(-) delete mode 100644 src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py create mode 100644 src/khoj/database/migrations/0069_webscraper_serverchatsettings_web_scraper.py diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 7be931c5..0f078a00 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -1054,10 +1054,10 @@ class ConversationAdapters: (server_webscraper.type, server_webscraper.api_key, server_webscraper.api_url, server_webscraper.name) ] if not enabled_scrapers: - # Use the enabled web scrapers, using the newest created scraper first, until get web page content + # Use the enabled web scrapers, ordered by priority, until get web page content enabled_scrapers = [ (scraper.type, scraper.api_key, scraper.api_url, scraper.name) - async for scraper in WebScraper.objects.all().order_by("-created_at").aiterator() + async for scraper in WebScraper.objects.all().order_by("priority").aiterator() ] if not enabled_scrapers: # Use scrapers enabled via environment variables diff --git a/src/khoj/database/admin.py b/src/khoj/database/admin.py index 8e650922..5aa9204b 100644 --- a/src/khoj/database/admin.py +++ b/src/khoj/database/admin.py @@ -206,6 +206,7 @@ class ServerChatSettingsAdmin(admin.ModelAdmin): @admin.register(WebScraper) class WebScraperAdmin(admin.ModelAdmin): list_display = ( + "priority", "name", "type", "api_key", @@ -213,7 +214,7 @@ class WebScraperAdmin(admin.ModelAdmin): "created_at", ) search_fields = ("name", "api_key", "api_url", "type") - ordering = ("-created_at",) + ordering = ("priority",) @admin.register(Conversation) diff --git a/src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py b/src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py deleted file mode 100644 index 41d9c80b..00000000 --- a/src/khoj/database/migrations/0068_webscraper_serverchatsettings_web_scraper.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 5.0.8 on 2024-10-16 06:51 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("database", "0067_alter_agent_style_icon"), - ] - - operations = [ - migrations.CreateModel( - name="WebScraper", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ("name", models.CharField(blank=True, default=None, max_length=200, null=True, unique=True)), - ( - "type", - models.CharField( - choices=[("firecrawl", "Firecrawl"), ("olostep", "Olostep"), ("jina", "Jina")], - default="jina", - max_length=20, - ), - ), - ("api_key", models.CharField(blank=True, default=None, max_length=200, null=True)), - ("api_url", models.URLField(blank=True, default=None, null=True)), - ], - options={ - "abstract": False, - }, - ), - migrations.AddField( - model_name="serverchatsettings", - name="web_scraper", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="web_scraper", - to="database.webscraper", - ), - ), - ] diff --git a/src/khoj/database/migrations/0069_webscraper_serverchatsettings_web_scraper.py b/src/khoj/database/migrations/0069_webscraper_serverchatsettings_web_scraper.py new file mode 100644 index 00000000..3ea8ebe3 --- /dev/null +++ b/src/khoj/database/migrations/0069_webscraper_serverchatsettings_web_scraper.py @@ -0,0 +1,89 @@ +# Generated by Django 5.0.8 on 2024-10-18 00:41 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("database", "0068_alter_agent_output_modes"), + ] + + operations = [ + migrations.CreateModel( + name="WebScraper", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "name", + models.CharField( + blank=True, + default=None, + help_text="Friendly name. If not set, it will be set to the type of the scraper.", + max_length=200, + null=True, + unique=True, + ), + ), + ( + "type", + models.CharField( + choices=[ + ("Firecrawl", "Firecrawl"), + ("Olostep", "Olostep"), + ("Jina", "Jina"), + ("Direct", "Direct"), + ], + default="Jina", + max_length=20, + ), + ), + ( + "api_key", + models.CharField( + blank=True, + default=None, + help_text="API key of the web scraper. Only set if scraper service requires an API key. Default is set from env var.", + max_length=200, + null=True, + ), + ), + ( + "api_url", + models.URLField( + blank=True, + default=None, + help_text="API URL of the web scraper. Only set if scraper service on non-default URL.", + null=True, + ), + ), + ( + "priority", + models.IntegerField( + blank=True, + default=None, + help_text="Priority of the web scraper. Lower numbers run first.", + null=True, + unique=True, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="serverchatsettings", + name="web_scraper", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="web_scraper", + to="database.webscraper", + ), + ), + ] diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index 56f482ae..2b2fde2d 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -9,7 +9,6 @@ from django.core.exceptions import ValidationError from django.db import models from django.db.models.signals import pre_save from django.dispatch import receiver -from django.utils.translation import gettext_lazy from pgvector.django import VectorField from phonenumber_field.modelfields import PhoneNumberField @@ -246,15 +245,41 @@ class GithubRepoConfig(BaseModel): class WebScraper(BaseModel): class WebScraperType(models.TextChoices): - FIRECRAWL = "firecrawl", gettext_lazy("Firecrawl") - OLOSTEP = "olostep", gettext_lazy("Olostep") - JINA = "jina", gettext_lazy("Jina") - DIRECT = "direct", gettext_lazy("Direct") + FIRECRAWL = "Firecrawl" + OLOSTEP = "Olostep" + JINA = "Jina" + DIRECT = "Direct" - name = models.CharField(max_length=200, default=None, null=True, blank=True, unique=True) + name = models.CharField( + max_length=200, + default=None, + null=True, + blank=True, + unique=True, + help_text="Friendly name. If not set, it will be set to the type of the scraper.", + ) type = models.CharField(max_length=20, choices=WebScraperType.choices, default=WebScraperType.JINA) - api_key = models.CharField(max_length=200, default=None, null=True, blank=True) - api_url = models.URLField(max_length=200, default=None, null=True, blank=True) + api_key = models.CharField( + max_length=200, + default=None, + null=True, + blank=True, + help_text="API key of the web scraper. Only set if scraper service requires an API key. Default is set from env var.", + ) + api_url = models.URLField( + max_length=200, + default=None, + null=True, + blank=True, + help_text="API URL of the web scraper. Only set if scraper service on non-default URL.", + ) + priority = models.IntegerField( + default=None, + null=True, + blank=True, + unique=True, + help_text="Priority of the web scraper. Lower numbers run first.", + ) def clean(self): error = {} @@ -278,12 +303,16 @@ class WebScraper(BaseModel): error["api_key"] = "Set API key to use Olostep. Get API key from https://olostep.com/." elif self.type == self.WebScraperType.JINA: self.api_key = os.getenv("JINA_API_KEY") - if error: raise ValidationError(error) def save(self, *args, **kwargs): self.clean() + + if self.priority is None: + max_priority = WebScraper.objects.aggregate(models.Max("priority"))["priority__max"] + self.priority = max_priority + 1 if max_priority else 1 + super().save(*args, **kwargs) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index c00660e3..fee0fa03 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -36,7 +36,7 @@ SERPER_DEV_URL = "https://google.serper.dev/search" JINA_SEARCH_API_URL = "https://s.jina.ai/" JINA_API_KEY = os.getenv("JINA_API_KEY") -FIRECRAWL_TO_EXTRACT = is_env_var_true("FIRECRAWL_TO_EXTRACT") +FIRECRAWL_USE_LLM_EXTRACT = is_env_var_true("FIRECRAWL_USE_LLM_EXTRACT") OLOSTEP_QUERY_PARAMS = { "timeout": 35, # seconds @@ -179,7 +179,7 @@ async def read_webpages( async def read_webpage( url, scraper_type=None, api_key=None, api_url=None, subqueries=None, agent=None ) -> Tuple[str | None, str | None]: - if scraper_type == WebScraper.WebScraperType.FIRECRAWL and FIRECRAWL_TO_EXTRACT: + if scraper_type == WebScraper.WebScraperType.FIRECRAWL and FIRECRAWL_USE_LLM_EXTRACT: return None, await query_webpage_with_firecrawl(url, subqueries, api_key, api_url, agent) elif scraper_type == WebScraper.WebScraperType.FIRECRAWL: return await read_webpage_with_firecrawl(url, api_key, api_url), None From 2c20f49bc59c69192b56a97883e7a47ac95287b1 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 17:15:53 -0700 Subject: [PATCH 23/71] Return enabled scrapers as WebScraper objects for more ergonomic code --- src/khoj/database/adapters/__init__.py | 46 ++++++++++++++++------- src/khoj/processor/tools/online_search.py | 14 ++++--- src/khoj/utils/helpers.py | 4 -- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 0f078a00..28946557 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -1045,41 +1045,59 @@ class ConversationAdapters: return None @staticmethod - async def aget_enabled_webscrapers(): - enabled_scrapers = [] + async def aget_enabled_webscrapers() -> list[WebScraper]: + enabled_scrapers: list[WebScraper] = [] server_webscraper = await ConversationAdapters.aget_server_webscraper() if server_webscraper: # Only use the webscraper set in the server chat settings - enabled_scrapers = [ - (server_webscraper.type, server_webscraper.api_key, server_webscraper.api_url, server_webscraper.name) - ] + enabled_scrapers = [server_webscraper] if not enabled_scrapers: # Use the enabled web scrapers, ordered by priority, until get web page content - enabled_scrapers = [ - (scraper.type, scraper.api_key, scraper.api_url, scraper.name) - async for scraper in WebScraper.objects.all().order_by("priority").aiterator() - ] + enabled_scrapers = [scraper async for scraper in WebScraper.objects.all().order_by("priority").aiterator()] if not enabled_scrapers: # Use scrapers enabled via environment variables if os.getenv("FIRECRAWL_API_KEY"): api_url = os.getenv("FIRECRAWL_API_URL", "https://api.firecrawl.dev") enabled_scrapers.append( - (WebScraper.WebScraperType.FIRECRAWL, os.getenv("FIRECRAWL_API_KEY"), api_url, "Firecrawl") + WebScraper( + type=WebScraper.WebScraperType.FIRECRAWL, + name=WebScraper.WebScraperType.FIRECRAWL.capitalize(), + api_key=os.getenv("FIRECRAWL_API_KEY"), + api_url=api_url, + ) ) if os.getenv("OLOSTEP_API_KEY"): api_url = os.getenv("OLOSTEP_API_URL", "https://agent.olostep.com/olostep-p2p-incomingAPI") enabled_scrapers.append( - (WebScraper.WebScraperType.OLOSTEP, os.getenv("OLOSTEP_API_KEY"), api_url, "Olostep") + WebScraper( + type=WebScraper.WebScraperType.OLOSTEP, + name=WebScraper.WebScraperType.OLOSTEP.capitalize(), + api_key=os.getenv("OLOSTEP_API_KEY"), + api_url=api_url, + ) ) - # Jina is the default fallback scrapers to use as it does not require an API key api_url = os.getenv("JINA_READER_API_URL", "https://r.jina.ai/") - enabled_scrapers.append((WebScraper.WebScraperType.JINA, os.getenv("JINA_API_KEY"), api_url, "Jina")) + enabled_scrapers.append( + WebScraper( + type=WebScraper.WebScraperType.JINA, + name=WebScraper.WebScraperType.JINA.capitalize(), + api_key=os.getenv("JINA_API_KEY"), + api_url=api_url, + ) + ) # Only enable the direct web page scraper by default in self-hosted single user setups. # Useful for reading webpages on your intranet. if state.anonymous_mode or in_debug_mode(): - enabled_scrapers.append((WebScraper.WebScraperType.DIRECT, None, None, "Direct")) + enabled_scrapers.append( + WebScraper( + type=WebScraper.WebScraperType.DIRECT, + name=WebScraper.WebScraperType.DIRECT.capitalize(), + api_key=None, + api_url=None, + ) + ) return enabled_scrapers diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index fee0fa03..70972eac 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -198,16 +198,18 @@ async def read_webpage_and_extract_content( web_scrapers = await ConversationAdapters.aget_enabled_webscrapers() # Only use the direct web scraper for internal URLs if is_internal_url(url): - web_scrapers = [scraper for scraper in web_scrapers if scraper[0] == WebScraper.WebScraperType.DIRECT] + web_scrapers = [scraper for scraper in web_scrapers if scraper.type == WebScraper.WebScraperType.DIRECT] # Fallback through enabled web scrapers until we successfully read the web page extracted_info = None - for scraper_type, api_key, api_url, api_name in web_scrapers: + for scraper in web_scrapers: try: # Read the web page if is_none_or_empty(content): - with timer(f"Reading web page with {scraper_type} at '{url}' took", logger, log_level=logging.INFO): - content, extracted_info = await read_webpage(url, scraper_type, api_key, api_url, subqueries, agent) + with timer(f"Reading web page with {scraper.type} at '{url}' took", logger, log_level=logging.INFO): + content, extracted_info = await read_webpage( + url, scraper.type, scraper.api_key, scraper.api_url, subqueries, agent + ) # Extract relevant information from the web page if is_none_or_empty(extracted_info): @@ -218,9 +220,9 @@ async def read_webpage_and_extract_content( if not is_none_or_empty(extracted_info): break except Exception as e: - logger.warning(f"Failed to read web page with {scraper_type} at '{url}' with {e}") + logger.warning(f"Failed to read web page with {scraper.type} at '{url}' with {e}") # If this is the last web scraper in the list, log an error - if api_name == web_scrapers[-1][-1]: + if scraper.name == web_scrapers[-1].name: logger.error(f"All web scrapers failed for '{url}'") return subqueries, url, extracted_info diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index 4e5736a2..7006d7d4 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -468,10 +468,6 @@ def is_internal_url(url: str) -> bool: if any(hostname.endswith(tld) for tld in internal_tlds): return True - # Check for non-standard ports - # if parsed_url.port and parsed_url.port not in [80, 443]: - # return True - # Check for URLs without a TLD if "." not in hostname: return True From f0dcfe4777bcba715450d05efe804e8b4b42bad1 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 18:12:02 -0700 Subject: [PATCH 24/71] Explicitly ask Gemini models to format their response with markdown Otherwise it can get confused by the format of the passed context (e.g respond in org-mode if context contains org-mode notes) --- src/khoj/processor/conversation/prompts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/khoj/processor/conversation/prompts.py b/src/khoj/processor/conversation/prompts.py index fa62dbb2..ad164c8d 100644 --- a/src/khoj/processor/conversation/prompts.py +++ b/src/khoj/processor/conversation/prompts.py @@ -49,7 +49,7 @@ Instructions:\n{bio} # Prompt forked from https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models gemini_verbose_language_personality = """ All questions should be answered comprehensively with details, unless the user requests a concise response specifically. -Respond in the same language as the query. +Respond in the same language as the query. Use markdown to format your responses. """.strip() ## General Conversation From 35015e720e9d79460e43fc61d967633bd6ff08aa Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 18:25:53 -0700 Subject: [PATCH 25/71] Release Khoj version 1.26.0 --- manifest.json | 2 +- src/interface/desktop/package.json | 2 +- src/interface/emacs/khoj.el | 2 +- src/interface/obsidian/manifest.json | 2 +- src/interface/obsidian/package.json | 2 +- src/interface/obsidian/versions.json | 3 ++- src/interface/web/package.json | 2 +- versions.json | 3 ++- 8 files changed, 10 insertions(+), 8 deletions(-) diff --git a/manifest.json b/manifest.json index 05b35dd3..e4fab58e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "khoj", "name": "Khoj", - "version": "1.25.0", + "version": "1.26.0", "minAppVersion": "0.15.0", "description": "Your Second Brain", "author": "Khoj Inc.", diff --git a/src/interface/desktop/package.json b/src/interface/desktop/package.json index 092272b1..87904130 100644 --- a/src/interface/desktop/package.json +++ b/src/interface/desktop/package.json @@ -1,6 +1,6 @@ { "name": "Khoj", - "version": "1.25.0", + "version": "1.26.0", "description": "Your Second Brain", "author": "Khoj Inc. ", "license": "GPL-3.0-or-later", diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 84141553..02d1dd6f 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -6,7 +6,7 @@ ;; Saba Imran ;; Description: Your Second Brain ;; Keywords: search, chat, ai, org-mode, outlines, markdown, pdf, image -;; Version: 1.25.0 +;; Version: 1.26.0 ;; Package-Requires: ((emacs "27.1") (transient "0.3.0") (dash "2.19.1")) ;; URL: https://github.com/khoj-ai/khoj/tree/master/src/interface/emacs diff --git a/src/interface/obsidian/manifest.json b/src/interface/obsidian/manifest.json index 05b35dd3..e4fab58e 100644 --- a/src/interface/obsidian/manifest.json +++ b/src/interface/obsidian/manifest.json @@ -1,7 +1,7 @@ { "id": "khoj", "name": "Khoj", - "version": "1.25.0", + "version": "1.26.0", "minAppVersion": "0.15.0", "description": "Your Second Brain", "author": "Khoj Inc.", diff --git a/src/interface/obsidian/package.json b/src/interface/obsidian/package.json index 5fcda33d..42ee8c26 100644 --- a/src/interface/obsidian/package.json +++ b/src/interface/obsidian/package.json @@ -1,6 +1,6 @@ { "name": "Khoj", - "version": "1.25.0", + "version": "1.26.0", "description": "Your Second Brain", "author": "Debanjum Singh Solanky, Saba Imran ", "license": "GPL-3.0-or-later", diff --git a/src/interface/obsidian/versions.json b/src/interface/obsidian/versions.json index 24ed5a84..8c9fd580 100644 --- a/src/interface/obsidian/versions.json +++ b/src/interface/obsidian/versions.json @@ -77,5 +77,6 @@ "1.23.3": "0.15.0", "1.24.0": "0.15.0", "1.24.1": "0.15.0", - "1.25.0": "0.15.0" + "1.25.0": "0.15.0", + "1.26.0": "0.15.0" } diff --git a/src/interface/web/package.json b/src/interface/web/package.json index 143d372b..4be40e9e 100644 --- a/src/interface/web/package.json +++ b/src/interface/web/package.json @@ -1,6 +1,6 @@ { "name": "khoj-ai", - "version": "1.25.0", + "version": "1.26.0", "private": true, "scripts": { "dev": "next dev", diff --git a/versions.json b/versions.json index 24ed5a84..8c9fd580 100644 --- a/versions.json +++ b/versions.json @@ -77,5 +77,6 @@ "1.23.3": "0.15.0", "1.24.0": "0.15.0", "1.24.1": "0.15.0", - "1.25.0": "0.15.0" + "1.25.0": "0.15.0", + "1.26.0": "0.15.0" } From dbd9a945b062ddf6e34d1582b9c4dd8929c3420e Mon Sep 17 00:00:00 2001 From: sabaimran Date: Fri, 18 Oct 2024 09:31:56 -0700 Subject: [PATCH 26/71] Re-evaluate agent private/public filtering after authenticateddata is retrieved. Update selectedAgent check logic to reflect. --- src/interface/web/app/agents/page.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/interface/web/app/agents/page.tsx b/src/interface/web/app/agents/page.tsx index 3a7ce172..45b485fb 100644 --- a/src/interface/web/app/agents/page.tsx +++ b/src/interface/web/app/agents/page.tsx @@ -1481,7 +1481,13 @@ export default function Agents() { // Search for the agent with the slug in the URL if (agentSlug) { setAgentSlug(agentSlug); - const selectedAgent = data.find((agent) => agent.slug === agentSlug); + let selectedAgent = data.find((agent) => agent.slug === agentSlug); + + // If the agent is not found in all the returned agents, check in the public agents. The code may be running 2x after either agent data or authenticated data is retrieved. + if (!selectedAgent) { + selectedAgent = publicAgents.find((agent) => agent.slug === agentSlug); + } + if (!selectedAgent) { // See if the agent is accessible as a protected agent. fetch(`/api/agents/${agentSlug}`) @@ -1500,7 +1506,7 @@ export default function Agents() { } } } - }, [data]); + }, [data, authenticatedData]); if (error) { return ( From a4e6e1d5e89aeb23a773b21d43aec546b0c6f018 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 18 Oct 2024 01:25:16 -0700 Subject: [PATCH 27/71] Share webp images from web, desktop, obsidian app to chat with --- src/interface/desktop/main.js | 4 +++- src/interface/obsidian/src/utils.ts | 4 +++- src/interface/web/app/common/iconUtils.tsx | 1 + .../web/app/components/chatInputArea/chatInputArea.tsx | 4 ++-- src/khoj/processor/content/images/image_to_entries.py | 2 ++ src/khoj/utils/helpers.py | 2 ++ 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/interface/desktop/main.js b/src/interface/desktop/main.js index d4208449..7c7bf4fc 100644 --- a/src/interface/desktop/main.js +++ b/src/interface/desktop/main.js @@ -19,7 +19,7 @@ const textFileTypes = [ 'org', 'md', 'markdown', 'txt', 'html', 'xml', // Other valid text file extensions from https://google.github.io/magika/model/config.json 'appleplist', 'asm', 'asp', 'batch', 'c', 'cs', 'css', 'csv', 'eml', 'go', 'html', 'ini', 'internetshortcut', 'java', 'javascript', 'json', 'latex', 'lisp', 'makefile', 'markdown', 'mht', 'mum', 'pem', 'perl', 'php', 'powershell', 'python', 'rdf', 'rst', 'rtf', 'ruby', 'rust', 'scala', 'shell', 'smali', 'sql', 'svg', 'symlinktext', 'txt', 'vba', 'winregistry', 'xml', 'yaml'] -const binaryFileTypes = ['pdf', 'jpg', 'jpeg', 'png'] +const binaryFileTypes = ['pdf', 'jpg', 'jpeg', 'png', 'webp'] const validFileTypes = textFileTypes.concat(binaryFileTypes); const schema = { @@ -104,6 +104,8 @@ function filenameToMimeType (filename) { case 'jpg': case 'jpeg': return 'image/jpeg'; + case 'webp': + return 'image/webp'; case 'md': case 'markdown': return 'text/markdown'; diff --git a/src/interface/obsidian/src/utils.ts b/src/interface/obsidian/src/utils.ts index 2395c529..3cc83fb8 100644 --- a/src/interface/obsidian/src/utils.ts +++ b/src/interface/obsidian/src/utils.ts @@ -37,6 +37,8 @@ function filenameToMimeType (filename: TFile): string { case 'jpg': case 'jpeg': return 'image/jpeg'; + case 'webp': + return 'image/webp'; case 'md': case 'markdown': return 'text/markdown'; @@ -50,7 +52,7 @@ function filenameToMimeType (filename: TFile): string { export const fileTypeToExtension = { 'pdf': ['pdf'], - 'image': ['png', 'jpg', 'jpeg'], + 'image': ['png', 'jpg', 'jpeg', 'webp'], 'markdown': ['md', 'markdown'], }; export const supportedImageFilesTypes = fileTypeToExtension.image; diff --git a/src/interface/web/app/common/iconUtils.tsx b/src/interface/web/app/common/iconUtils.tsx index c84f0cc4..1a6ca0e1 100644 --- a/src/interface/web/app/common/iconUtils.tsx +++ b/src/interface/web/app/common/iconUtils.tsx @@ -241,6 +241,7 @@ function getIconFromFilename( case "jpg": case "jpeg": case "png": + case "webp": return ; default: return ; diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index 2b4975e9..d85d6a54 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -168,12 +168,12 @@ export default function ChatInputArea(props: ChatInputProps) { function uploadFiles(files: FileList) { if (!props.isLoggedIn) { - setLoginRedirectMessage("Whoa! You need to login to upload files"); + setLoginRedirectMessage("Please login to chat with your files"); setShowLoginPrompt(true); return; } // check for image file - const image_endings = ["jpg", "jpeg", "png"]; + const image_endings = ["jpg", "jpeg", "png", "webp"]; for (let i = 0; i < files.length; i++) { const file = files[i]; const file_extension = file.name.split(".").pop(); diff --git a/src/khoj/processor/content/images/image_to_entries.py b/src/khoj/processor/content/images/image_to_entries.py index 6dcd728c..87b9a009 100644 --- a/src/khoj/processor/content/images/image_to_entries.py +++ b/src/khoj/processor/content/images/image_to_entries.py @@ -64,6 +64,8 @@ class ImageToEntries(TextToEntries): tmp_file = f"tmp_image_file_{timestamp_now}.png" elif image_file.endswith(".jpg") or image_file.endswith(".jpeg"): tmp_file = f"tmp_image_file_{timestamp_now}.jpg" + elif image_file.endswith(".webp"): + tmp_file = f"tmp_image_file_{timestamp_now}.webp" with open(tmp_file, "wb") as f: bytes = image_files[image_file] f.write(bytes) diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index 7006d7d4..abc65b77 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -127,6 +127,8 @@ def get_file_type(file_type: str, file_content: bytes) -> tuple[str, str]: return "image", encoding elif file_type in ["image/png"]: return "image", encoding + elif file_type in ["image/webp"]: + return "image", encoding elif content_group in ["code", "text"]: return "plaintext", encoding else: From d55cba8627f56f950c3ed2b49f48a7f4f02cb706 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sat, 19 Oct 2024 14:05:21 -0700 Subject: [PATCH 28/71] Pass user query for chat response when document lookup fails Recent changes made Khoj try respond even when document lookup fails. This change missed handling downstream effects of a failed document lookup, as the defiltered_query was null and so the text response didn't have the user query to respond to. This code initializes defiltered_query to original user query to handle that. Also response_type wasn't being passed via send_message_to_model_wrapper_sync unlike in the async scenario --- src/khoj/routers/api_chat.py | 7 +++---- src/khoj/routers/helpers.py | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 0d367029..d57b5530 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -10,9 +10,8 @@ from urllib.parse import unquote from asgiref.sync import sync_to_async from fastapi import APIRouter, Depends, HTTPException, Request -from fastapi.requests import Request from fastapi.responses import Response, StreamingResponse -from starlette.authentication import has_required_scope, requires +from starlette.authentication import requires from khoj.app.settings import ALLOWED_HOSTS from khoj.database.adapters import ( @@ -837,7 +836,7 @@ async def chat( # Gather Context ## Extract Document References - compiled_references, inferred_queries, defiltered_query = [], [], None + compiled_references, inferred_queries, defiltered_query = [], [], q try: async for result in extract_references_and_questions( request, @@ -960,7 +959,7 @@ async def chat( ## Generate Image Output if ConversationCommand.Image in conversation_commands: async for result in text_to_image( - q, + defiltered_query, user, meta_log, location_data=location, diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index c3d997e9..12616e36 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -881,6 +881,7 @@ def send_message_to_model_wrapper_sync( messages=truncated_messages, api_key=api_key, model=chat_model, + response_type=response_type, ) else: raise HTTPException(status_code=500, detail="Invalid conversation config") From e2abc1a257b3b58e1a0355ea23aef20b86869eb6 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 23:05:43 -0700 Subject: [PATCH 29/71] Handle multiple images shared in query to chat API Previously Khoj could respond to a single shared image at a time. This changes updates the chat API to accept multiple images shared by the user and send it to the appropriate chat actors including the openai response generation chat actor for getting an image aware response --- .../conversation/google/gemini_chat.py | 3 +- src/khoj/processor/conversation/openai/gpt.py | 8 +-- src/khoj/processor/conversation/utils.py | 30 +++++---- src/khoj/processor/image/generate.py | 4 +- src/khoj/processor/tools/online_search.py | 8 +-- src/khoj/routers/api.py | 4 +- src/khoj/routers/api_chat.py | 62 +++++++++---------- src/khoj/routers/helpers.py | 52 ++++++++-------- 8 files changed, 90 insertions(+), 81 deletions(-) diff --git a/src/khoj/processor/conversation/google/gemini_chat.py b/src/khoj/processor/conversation/google/gemini_chat.py index 7359b3eb..e8848806 100644 --- a/src/khoj/processor/conversation/google/gemini_chat.py +++ b/src/khoj/processor/conversation/google/gemini_chat.py @@ -6,7 +6,7 @@ from typing import Dict, Optional from langchain.schema import ChatMessage -from khoj.database.models import Agent, KhojUser +from khoj.database.models import Agent, ChatModelOptions, KhojUser from khoj.processor.conversation import prompts from khoj.processor.conversation.google.utils import ( format_messages_for_gemini, @@ -187,6 +187,7 @@ def converse_gemini( model_name=model, max_prompt_size=max_prompt_size, tokenizer_name=tokenizer_name, + model_type=ChatModelOptions.ModelType.GOOGLE, ) messages, system_prompt = format_messages_for_gemini(messages, system_prompt) diff --git a/src/khoj/processor/conversation/openai/gpt.py b/src/khoj/processor/conversation/openai/gpt.py index ad02b10e..4a656fac 100644 --- a/src/khoj/processor/conversation/openai/gpt.py +++ b/src/khoj/processor/conversation/openai/gpt.py @@ -30,7 +30,7 @@ def extract_questions( api_base_url=None, location_data: LocationData = None, user: KhojUser = None, - uploaded_image_url: Optional[str] = None, + query_images: Optional[list[str]] = None, vision_enabled: bool = False, personality_context: Optional[str] = None, ): @@ -74,7 +74,7 @@ def extract_questions( prompt = construct_structured_message( message=prompt, - image_url=uploaded_image_url, + images=query_images, model_type=ChatModelOptions.ModelType.OPENAI, vision_enabled=vision_enabled, ) @@ -135,7 +135,7 @@ def converse( location_data: LocationData = None, user_name: str = None, agent: Agent = None, - image_url: Optional[str] = None, + query_images: Optional[list[str]] = None, vision_available: bool = False, ): """ @@ -191,7 +191,7 @@ def converse( model_name=model, max_prompt_size=max_prompt_size, tokenizer_name=tokenizer_name, - uploaded_image_url=image_url, + query_images=query_images, vision_enabled=vision_available, model_type=ChatModelOptions.ModelType.OPENAI, ) diff --git a/src/khoj/processor/conversation/utils.py b/src/khoj/processor/conversation/utils.py index e841c484..8d799745 100644 --- a/src/khoj/processor/conversation/utils.py +++ b/src/khoj/processor/conversation/utils.py @@ -109,7 +109,7 @@ def save_to_conversation_log( client_application: ClientApplication = None, conversation_id: str = None, automation_id: str = None, - uploaded_image_url: str = None, + query_images: List[str] = None, ): user_message_time = user_message_time or datetime.now().strftime("%Y-%m-%d %H:%M:%S") updated_conversation = message_to_log( @@ -117,7 +117,7 @@ def save_to_conversation_log( chat_response=chat_response, user_message_metadata={ "created": user_message_time, - "uploadedImageData": uploaded_image_url, + "images": query_images, }, khoj_message_metadata={ "context": compiled_references, @@ -145,10 +145,18 @@ Khoj: "{inferred_queries if ("text-to-image" in intent_type) else chat_response} ) -# Format user and system messages to chatml format -def construct_structured_message(message, image_url, model_type, vision_enabled): - if image_url and vision_enabled and model_type == ChatModelOptions.ModelType.OPENAI: - return [{"type": "text", "text": message}, {"type": "image_url", "image_url": {"url": image_url}}] +def construct_structured_message(message: str, images: list[str], model_type: str, vision_enabled: bool): + """ + Format messages into appropriate multimedia format for supported chat model types + """ + if not images or not vision_enabled: + return message + + if model_type == ChatModelOptions.ModelType.OPENAI: + return [ + {"type": "text", "text": message}, + *[{"type": "image_url", "image_url": {"url": image}} for image in images], + ] return message @@ -160,7 +168,7 @@ def generate_chatml_messages_with_context( loaded_model: Optional[Llama] = None, max_prompt_size=None, tokenizer_name=None, - uploaded_image_url=None, + query_images=None, vision_enabled=False, model_type="", ): @@ -183,9 +191,7 @@ def generate_chatml_messages_with_context( message_content = chat["message"] + message_notes - message_content = construct_structured_message( - message_content, chat.get("uploadedImageData"), model_type, vision_enabled - ) + message_content = construct_structured_message(message_content, chat.get("images"), model_type, vision_enabled) reconstructed_message = ChatMessage(content=message_content, role=role) @@ -198,7 +204,7 @@ def generate_chatml_messages_with_context( if not is_none_or_empty(user_message): messages.append( ChatMessage( - content=construct_structured_message(user_message, uploaded_image_url, model_type, vision_enabled), + content=construct_structured_message(user_message, query_images, model_type, vision_enabled), role="user", ) ) @@ -222,7 +228,6 @@ def truncate_messages( tokenizer_name=None, ) -> list[ChatMessage]: """Truncate messages to fit within max prompt size supported by model""" - default_tokenizer = "gpt-4o" try: @@ -252,6 +257,7 @@ def truncate_messages( system_message = messages.pop(idx) break + # TODO: Handle truncation of multi-part message.content, i.e when message.content is a list[dict] rather than a string system_message_tokens = ( len(encoder.encode(system_message.content)) if system_message and type(system_message.content) == str else 0 ) diff --git a/src/khoj/processor/image/generate.py b/src/khoj/processor/image/generate.py index 59073731..ee39bdc5 100644 --- a/src/khoj/processor/image/generate.py +++ b/src/khoj/processor/image/generate.py @@ -26,7 +26,7 @@ async def text_to_image( references: List[Dict[str, Any]], online_results: Dict[str, Any], send_status_func: Optional[Callable] = None, - uploaded_image_url: Optional[str] = None, + query_images: Optional[List[str]] = None, agent: Agent = None, ): status_code = 200 @@ -65,7 +65,7 @@ async def text_to_image( note_references=references, online_results=online_results, model_type=text_to_image_config.model_type, - uploaded_image_url=uploaded_image_url, + query_images=query_images, user=user, agent=agent, ) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index 70972eac..fdf1ba9f 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -62,7 +62,7 @@ async def search_online( user: KhojUser, send_status_func: Optional[Callable] = None, custom_filters: List[str] = [], - uploaded_image_url: str = None, + query_images: List[str] = None, agent: Agent = None, ): query += " ".join(custom_filters) @@ -73,7 +73,7 @@ async def search_online( # Breakdown the query into subqueries to get the correct answer subqueries = await generate_online_subqueries( - query, conversation_history, location, user, uploaded_image_url=uploaded_image_url, agent=agent + query, conversation_history, location, user, query_images=query_images, agent=agent ) response_dict = {} @@ -151,7 +151,7 @@ async def read_webpages( location: LocationData, user: KhojUser, send_status_func: Optional[Callable] = None, - uploaded_image_url: str = None, + query_images: List[str] = None, agent: Agent = None, ): "Infer web pages to read from the query and extract relevant information from them" @@ -159,7 +159,7 @@ async def read_webpages( if send_status_func: async for event in send_status_func(f"**Inferring web pages to read**"): yield {ChatEvent.STATUS: event} - urls = await infer_webpage_urls(query, conversation_history, location, user, uploaded_image_url) + urls = await infer_webpage_urls(query, conversation_history, location, user, query_images) logger.info(f"Reading web pages at: {urls}") if send_status_func: diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py index 59948b47..075c8c47 100644 --- a/src/khoj/routers/api.py +++ b/src/khoj/routers/api.py @@ -340,7 +340,7 @@ async def extract_references_and_questions( conversation_commands: List[ConversationCommand] = [ConversationCommand.Default], location_data: LocationData = None, send_status_func: Optional[Callable] = None, - uploaded_image_url: Optional[str] = None, + query_images: Optional[List[str]] = None, agent: Agent = None, ): user = request.user.object if request.user.is_authenticated else None @@ -431,7 +431,7 @@ async def extract_references_and_questions( conversation_log=meta_log, location_data=location_data, user=user, - uploaded_image_url=uploaded_image_url, + query_images=query_images, vision_enabled=vision_enabled, personality_context=personality_context, ) diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index d57b5530..ee84c554 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -535,7 +535,7 @@ class ChatRequestBody(BaseModel): country: Optional[str] = None country_code: Optional[str] = None timezone: Optional[str] = None - image: Optional[str] = None + images: Optional[list[str]] = None create_new: Optional[bool] = False @@ -564,9 +564,9 @@ async def chat( country = body.country or get_country_name_from_timezone(body.timezone) country_code = body.country_code or get_country_code_from_timezone(body.timezone) timezone = body.timezone - image = body.image + raw_images = body.images - async def event_generator(q: str, image: str): + async def event_generator(q: str, images: list[str]): start_time = time.perf_counter() ttft = None chat_metadata: dict = {} @@ -576,16 +576,16 @@ async def chat( q = unquote(q) nonlocal conversation_id - uploaded_image_url = None - if image: - decoded_string = unquote(image) - base64_data = decoded_string.split(",", 1)[1] - image_bytes = base64.b64decode(base64_data) - webp_image_bytes = convert_image_to_webp(image_bytes) - try: - uploaded_image_url = upload_image_to_bucket(webp_image_bytes, request.user.object.id) - except: - uploaded_image_url = None + uploaded_images: list[str] = [] + if images: + for image in images: + decoded_string = unquote(image) + base64_data = decoded_string.split(",", 1)[1] + image_bytes = base64.b64decode(base64_data) + webp_image_bytes = convert_image_to_webp(image_bytes) + uploaded_image = upload_image_to_bucket(webp_image_bytes, request.user.object.id) + if uploaded_image: + uploaded_images.append(uploaded_image) async def send_event(event_type: ChatEvent, data: str | dict): nonlocal connection_alive, ttft @@ -692,7 +692,7 @@ async def chat( meta_log, is_automated_task, user=user, - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, agent=agent, ) conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands]) @@ -701,7 +701,7 @@ async def chat( ): yield result - mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, user, uploaded_image_url, agent) + mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, user, uploaded_images, agent) async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"): yield result if mode not in conversation_commands: @@ -764,7 +764,7 @@ async def chat( q, contextual_data, conversation_history=meta_log, - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, user=user, agent=agent, ) @@ -785,7 +785,7 @@ async def chat( intent_type="summarize", client_application=request.user.client_app, conversation_id=conversation_id, - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, ) return @@ -828,7 +828,7 @@ async def chat( conversation_id=conversation_id, inferred_queries=[query_to_run], automation_id=automation.id, - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, ) async for result in send_llm_response(llm_response): yield result @@ -848,7 +848,7 @@ async def chat( conversation_commands, location, partial(send_event, ChatEvent.STATUS), - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, agent=agent, ): if isinstance(result, dict) and ChatEvent.STATUS in result: @@ -892,7 +892,7 @@ async def chat( user, partial(send_event, ChatEvent.STATUS), custom_filters, - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, agent=agent, ): if isinstance(result, dict) and ChatEvent.STATUS in result: @@ -916,7 +916,7 @@ async def chat( location, user, partial(send_event, ChatEvent.STATUS), - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, agent=agent, ): if isinstance(result, dict) and ChatEvent.STATUS in result: @@ -966,20 +966,20 @@ async def chat( references=compiled_references, online_results=online_results, send_status_func=partial(send_event, ChatEvent.STATUS), - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, agent=agent, ): if isinstance(result, dict) and ChatEvent.STATUS in result: yield result[ChatEvent.STATUS] else: - image, status_code, improved_image_prompt, intent_type = result + generated_image, status_code, improved_image_prompt, intent_type = result - if image is None or status_code != 200: + if generated_image is None or status_code != 200: content_obj = { "content-type": "application/json", "intentType": intent_type, "detail": improved_image_prompt, - "image": image, + "image": None, } async for result in send_llm_response(json.dumps(content_obj)): yield result @@ -987,7 +987,7 @@ async def chat( await sync_to_async(save_to_conversation_log)( q, - image, + generated_image, user, meta_log, user_message_time, @@ -997,12 +997,12 @@ async def chat( conversation_id=conversation_id, compiled_references=compiled_references, online_results=online_results, - uploaded_image_url=uploaded_image_url, + query_images=uploaded_images, ) content_obj = { "intentType": intent_type, "inferredQueries": [improved_image_prompt], - "image": image, + "image": generated_image, } async for result in send_llm_response(json.dumps(content_obj)): yield result @@ -1024,7 +1024,7 @@ async def chat( conversation_id, location, user_name, - uploaded_image_url, + uploaded_images, ) # Send Response @@ -1050,9 +1050,9 @@ async def chat( ## Stream Text Response if stream: - return StreamingResponse(event_generator(q, image=image), media_type="text/plain") + return StreamingResponse(event_generator(q, images=raw_images), media_type="text/plain") ## Non-Streaming Text Response else: - response_iterator = event_generator(q, image=image) + response_iterator = event_generator(q, images=raw_images) response_data = await read_chat_stream(response_iterator) return Response(content=json.dumps(response_data), media_type="application/json", status_code=200) diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index 12616e36..7ed9c72d 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -290,7 +290,7 @@ async def aget_relevant_information_sources( conversation_history: dict, is_task: bool, user: KhojUser, - uploaded_image_url: str = None, + query_images: List[str] = None, agent: Agent = None, ): """ @@ -309,8 +309,8 @@ async def aget_relevant_information_sources( chat_history = construct_chat_history(conversation_history) - if uploaded_image_url: - query = f"[placeholder for user attached image]\n{query}" + if query_images: + query = f"[placeholder for {len(query_images)} user attached images]\n{query}" personality_context = ( prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else "" @@ -367,7 +367,7 @@ async def aget_relevant_output_modes( conversation_history: dict, is_task: bool = False, user: KhojUser = None, - uploaded_image_url: str = None, + query_images: List[str] = None, agent: Agent = None, ): """ @@ -389,8 +389,8 @@ async def aget_relevant_output_modes( chat_history = construct_chat_history(conversation_history) - if uploaded_image_url: - query = f"[placeholder for user attached image]\n{query}" + if query_images: + query = f"[placeholder for {len(query_images)} user attached images]\n{query}" personality_context = ( prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else "" @@ -433,7 +433,7 @@ async def infer_webpage_urls( conversation_history: dict, location_data: LocationData, user: KhojUser, - uploaded_image_url: str = None, + query_images: List[str] = None, agent: Agent = None, ) -> List[str]: """ @@ -459,7 +459,7 @@ async def infer_webpage_urls( with timer("Chat actor: Infer webpage urls to read", logger): response = await send_message_to_model_wrapper( - online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user + online_queries_prompt, query_images=query_images, response_type="json_object", user=user ) # Validate that the response is a non-empty, JSON-serializable list of URLs @@ -479,7 +479,7 @@ async def generate_online_subqueries( conversation_history: dict, location_data: LocationData, user: KhojUser, - uploaded_image_url: str = None, + query_images: List[str] = None, agent: Agent = None, ) -> List[str]: """ @@ -505,7 +505,7 @@ async def generate_online_subqueries( with timer("Chat actor: Generate online search subqueries", logger): response = await send_message_to_model_wrapper( - online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user + online_queries_prompt, query_images=query_images, response_type="json_object", user=user ) # Validate that the response is a non-empty, JSON-serializable list @@ -524,7 +524,7 @@ async def generate_online_subqueries( async def schedule_query( - q: str, conversation_history: dict, user: KhojUser, uploaded_image_url: str = None + q: str, conversation_history: dict, user: KhojUser, query_images: List[str] = None ) -> Tuple[str, ...]: """ Schedule the date, time to run the query. Assume the server timezone is UTC. @@ -537,7 +537,7 @@ async def schedule_query( ) raw_response = await send_message_to_model_wrapper( - crontime_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user + crontime_prompt, query_images=query_images, response_type="json_object", user=user ) # Validate that the response is a non-empty, JSON-serializable list @@ -583,7 +583,7 @@ async def extract_relevant_summary( q: str, corpus: str, conversation_history: dict, - uploaded_image_url: str = None, + query_images: List[str] = None, user: KhojUser = None, agent: Agent = None, ) -> Union[str, None]: @@ -612,7 +612,7 @@ async def extract_relevant_summary( extract_relevant_information, prompts.system_prompt_extract_relevant_summary, user=user, - uploaded_image_url=uploaded_image_url, + query_images=query_images, ) return response.strip() @@ -624,7 +624,7 @@ async def generate_better_image_prompt( note_references: List[Dict[str, Any]], online_results: Optional[dict] = None, model_type: Optional[str] = None, - uploaded_image_url: Optional[str] = None, + query_images: Optional[List[str]] = None, user: KhojUser = None, agent: Agent = None, ) -> str: @@ -676,7 +676,7 @@ async def generate_better_image_prompt( ) with timer("Chat actor: Generate contextual image prompt", logger): - response = await send_message_to_model_wrapper(image_prompt, uploaded_image_url=uploaded_image_url, user=user) + response = await send_message_to_model_wrapper(image_prompt, query_images=query_images, user=user) response = response.strip() if response.startswith(('"', "'")) and response.endswith(('"', "'")): response = response[1:-1] @@ -689,11 +689,11 @@ async def send_message_to_model_wrapper( system_message: str = "", response_type: str = "text", user: KhojUser = None, - uploaded_image_url: str = None, + query_images: List[str] = None, ): conversation_config: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config(user) vision_available = conversation_config.vision_enabled - if not vision_available and uploaded_image_url: + if not vision_available and query_images: vision_enabled_config = await ConversationAdapters.aget_vision_enabled_config() if vision_enabled_config: conversation_config = vision_enabled_config @@ -746,7 +746,7 @@ async def send_message_to_model_wrapper( max_prompt_size=max_tokens, tokenizer_name=tokenizer, vision_enabled=vision_available, - uploaded_image_url=uploaded_image_url, + query_images=query_images, model_type=conversation_config.model_type, ) @@ -766,7 +766,7 @@ async def send_message_to_model_wrapper( max_prompt_size=max_tokens, tokenizer_name=tokenizer, vision_enabled=vision_available, - uploaded_image_url=uploaded_image_url, + query_images=query_images, model_type=conversation_config.model_type, ) @@ -784,7 +784,8 @@ async def send_message_to_model_wrapper( max_prompt_size=max_tokens, tokenizer_name=tokenizer, vision_enabled=vision_available, - uploaded_image_url=uploaded_image_url, + query_images=query_images, + model_type=conversation_config.model_type, ) return gemini_send_message_to_model( @@ -875,6 +876,7 @@ def send_message_to_model_wrapper_sync( model_name=chat_model, max_prompt_size=max_tokens, vision_enabled=vision_available, + model_type=conversation_config.model_type, ) return gemini_send_message_to_model( @@ -900,7 +902,7 @@ def generate_chat_response( conversation_id: str = None, location_data: LocationData = None, user_name: Optional[str] = None, - uploaded_image_url: Optional[str] = None, + query_images: Optional[List[str]] = None, ) -> Tuple[Union[ThreadedGenerator, Iterator[str]], Dict[str, str]]: # Initialize Variables chat_response = None @@ -919,12 +921,12 @@ def generate_chat_response( inferred_queries=inferred_queries, client_application=client_application, conversation_id=conversation_id, - uploaded_image_url=uploaded_image_url, + query_images=query_images, ) conversation_config = ConversationAdapters.get_valid_conversation_config(user, conversation) vision_available = conversation_config.vision_enabled - if not vision_available and uploaded_image_url: + if not vision_available and query_images: vision_enabled_config = ConversationAdapters.get_vision_enabled_config() if vision_enabled_config: conversation_config = vision_enabled_config @@ -955,7 +957,7 @@ def generate_chat_response( chat_response = converse( compiled_references, q, - image_url=uploaded_image_url, + query_images=query_images, online_results=online_results, conversation_log=meta_log, model=chat_model, From 0d6a54c10fe18efc615eb053b5966205ce0de5e5 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Thu, 17 Oct 2024 23:08:20 -0700 Subject: [PATCH 30/71] Allow sharing multiple images as part of user query from the web app Previously the web app only expected a single image to be shared by the user as part of their query. This change allows sharing multiple images from the web app. Closes #921 --- src/interface/web/app/chat/page.tsx | 39 +++++---- .../components/chatHistory/chatHistory.tsx | 3 +- .../chatInputArea/chatInputArea.tsx | 87 ++++++++++++------- .../components/chatMessage/chatMessage.tsx | 17 ++-- src/interface/web/app/page.tsx | 13 +-- src/interface/web/app/share/chat/page.tsx | 40 ++++++--- 6 files changed, 122 insertions(+), 77 deletions(-) diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index 7d87fd81..c9c38870 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -17,8 +17,6 @@ import { useIPLocationData, useIsMobileWidth, welcomeConsole } from "../common/u import ChatInputArea, { ChatOptions } from "../components/chatInputArea/chatInputArea"; import { useAuthenticatedData } from "../common/auth"; import { AgentData } from "../agents/page"; -import { DotsThreeVertical } from "@phosphor-icons/react"; -import { Button } from "@/components/ui/button"; interface ChatBodyDataProps { chatOptionsData: ChatOptions | null; @@ -29,14 +27,14 @@ interface ChatBodyDataProps { setUploadedFiles: (files: string[]) => void; isMobileWidth?: boolean; isLoggedIn: boolean; - setImage64: (image64: string) => void; + setImages: (images: string[]) => void; } function ChatBodyData(props: ChatBodyDataProps) { const searchParams = useSearchParams(); const conversationId = searchParams.get("conversationId"); const [message, setMessage] = useState(""); - const [image, setImage] = useState(null); + const [images, setImages] = useState([]); const [processingMessage, setProcessingMessage] = useState(false); const [agentMetadata, setAgentMetadata] = useState(null); @@ -44,17 +42,20 @@ function ChatBodyData(props: ChatBodyDataProps) { const onConversationIdChange = props.onConversationIdChange; useEffect(() => { - if (image) { - props.setImage64(encodeURIComponent(image)); + if (images.length > 0) { + const encodedImages = images.map((image) => encodeURIComponent(image)); + props.setImages(encodedImages); } - }, [image, props.setImage64]); + }, [images, props.setImages]); useEffect(() => { - const storedImage = localStorage.getItem("image"); - if (storedImage) { - setImage(storedImage); - props.setImage64(encodeURIComponent(storedImage)); - localStorage.removeItem("image"); + const storedImages = localStorage.getItem("images"); + if (storedImages) { + const parsedImages: string[] = JSON.parse(storedImages); + setImages(parsedImages); + const encodedImages = parsedImages.map((img: string) => encodeURIComponent(img)); + props.setImages(encodedImages); + localStorage.removeItem("images"); } const storedMessage = localStorage.getItem("message"); @@ -62,7 +63,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setProcessingMessage(true); setQueryToProcess(storedMessage); } - }, [setQueryToProcess]); + }, [setQueryToProcess, props.setImages]); useEffect(() => { if (message) { @@ -112,7 +113,7 @@ function ChatBodyData(props: ChatBodyDataProps) { agentColor={agentMetadata?.color} isLoggedIn={props.isLoggedIn} sendMessage={(message) => setMessage(message)} - sendImage={(image) => setImage(image)} + sendImage={(image) => setImages((prevImages) => [...prevImages, image])} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={conversationId} @@ -134,7 +135,7 @@ export default function Chat() { const [queryToProcess, setQueryToProcess] = useState(""); const [processQuerySignal, setProcessQuerySignal] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); - const [image64, setImage64] = useState(""); + const [images, setImages] = useState([]); const locationData = useIPLocationData() || { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -170,7 +171,7 @@ export default function Chat() { completed: false, timestamp: new Date().toISOString(), rawQuery: queryToProcess || "", - uploadedImageData: decodeURIComponent(image64), + images: images, }; setMessages((prevMessages) => [...prevMessages, newStreamMessage]); setProcessQuerySignal(true); @@ -201,7 +202,7 @@ export default function Chat() { if (done) { setQueryToProcess(""); setProcessQuerySignal(false); - setImage64(""); + setImages([]); break; } @@ -249,7 +250,7 @@ export default function Chat() { country_code: locationData.countryCode, timezone: locationData.timezone, }), - ...(image64 && { image: image64 }), + ...(images.length > 0 && { images: images }), }; const response = await fetch(chatAPI, { @@ -331,7 +332,7 @@ export default function Chat() { setUploadedFiles={setUploadedFiles} isMobileWidth={isMobileWidth} onConversationIdChange={handleConversationIdChange} - setImage64={setImage64} + setImages={setImages} /> diff --git a/src/interface/web/app/components/chatHistory/chatHistory.tsx b/src/interface/web/app/components/chatHistory/chatHistory.tsx index 1a7c90c0..fc37ba7d 100644 --- a/src/interface/web/app/components/chatHistory/chatHistory.tsx +++ b/src/interface/web/app/components/chatHistory/chatHistory.tsx @@ -298,7 +298,7 @@ export default function ChatHistory(props: ChatHistoryProps) { created: message.timestamp, by: "you", automationId: "", - uploadedImageData: message.uploadedImageData, + images: message.images, }} customClassName="fullHistory" borderLeftColor={`${data?.agent?.color}-500`} @@ -341,7 +341,6 @@ export default function ChatHistory(props: ChatHistoryProps) { created: new Date().getTime().toString(), by: "you", automationId: "", - uploadedImageData: props.pendingMessage, }} customClassName="fullHistory" borderLeftColor={`${data?.agent?.color}-500`} diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index d85d6a54..fde23a0d 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -78,10 +78,11 @@ export default function ChatInputArea(props: ChatInputProps) { const [loginRedirectMessage, setLoginRedirectMessage] = useState(null); const [showLoginPrompt, setShowLoginPrompt] = useState(false); - const [recording, setRecording] = useState(false); const [imageUploaded, setImageUploaded] = useState(false); - const [imagePath, setImagePath] = useState(""); - const [imageData, setImageData] = useState(null); + const [imagePaths, setImagePaths] = useState([]); + const [imageData, setImageData] = useState([]); + + const [recording, setRecording] = useState(false); const [mediaRecorder, setMediaRecorder] = useState(null); const [progressValue, setProgressValue] = useState(0); @@ -106,27 +107,31 @@ export default function ChatInputArea(props: ChatInputProps) { useEffect(() => { async function fetchImageData() { - if (imagePath) { - const response = await fetch(imagePath); - const blob = await response.blob(); - const reader = new FileReader(); - reader.onload = function () { - const base64data = reader.result; - setImageData(base64data as string); - }; - reader.readAsDataURL(blob); + if (imagePaths.length > 0) { + const newImageData = await Promise.all( + imagePaths.map(async (path) => { + const response = await fetch(path); + const blob = await response.blob(); + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.readAsDataURL(blob); + }); + }), + ); + setImageData(newImageData); } setUploading(false); } setUploading(true); fetchImageData(); - }, [imagePath]); + }, [imagePaths]); function onSendMessage() { if (imageUploaded) { setImageUploaded(false); - setImagePath(""); - props.sendImage(imageData || ""); + setImagePaths([]); + imageData.forEach((data) => props.sendImage(data)); } if (!message.trim()) return; @@ -172,18 +177,23 @@ export default function ChatInputArea(props: ChatInputProps) { setShowLoginPrompt(true); return; } - // check for image file + // check for image files const image_endings = ["jpg", "jpeg", "png", "webp"]; + const newImagePaths: string[] = []; for (let i = 0; i < files.length; i++) { const file = files[i]; const file_extension = file.name.split(".").pop(); if (image_endings.includes(file_extension || "")) { - setImageUploaded(true); - setImagePath(DOMPurify.sanitize(URL.createObjectURL(file))); - return; + newImagePaths.push(DOMPurify.sanitize(URL.createObjectURL(file))); } } + if (newImagePaths.length > 0) { + setImageUploaded(true); + setImagePaths((prevPaths) => [...prevPaths, ...newImagePaths]); + return; + } + uploadDataForIndexing( files, setWarning, @@ -288,9 +298,12 @@ export default function ChatInputArea(props: ChatInputProps) { setIsDragAndDropping(false); } - function removeImageUpload() { - setImageUploaded(false); - setImagePath(""); + function removeImageUpload(index: number) { + setImagePaths((prevPaths) => prevPaths.filter((_, i) => i !== index)); + setImageData((prevData) => prevData.filter((_, i) => i !== index)); + if (imagePaths.length === 1) { + setImageUploaded(false); + } } return ( @@ -413,16 +426,24 @@ export default function ChatInputArea(props: ChatInputProps) { onDrop={handleDragAndDropFiles} > {imageUploaded && ( -
-
- img -
-
- -
+
+ {imagePaths.map((path, index) => ( +
+ {`img-${index}`} + +
+ ))}
)} { if (e.key === "Enter" && !e.shiftKey) { setImageUploaded(false); - setImagePath(""); + setImagePaths([]); e.preventDefault(); onSendMessage(); } diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index 23371512..e0d0f09c 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -114,7 +114,7 @@ export interface SingleChatMessage { rawQuery?: string; intent?: Intent; agent?: AgentData; - uploadedImageData?: string; + images?: string[]; } export interface StreamMessage { @@ -126,7 +126,7 @@ export interface StreamMessage { rawQuery: string; timestamp: string; agent?: AgentData; - uploadedImageData?: string; + images?: string[]; } export interface ChatHistoryData { @@ -208,7 +208,6 @@ interface ChatMessageProps { borderLeftColor?: string; isLastMessage?: boolean; agent?: AgentData; - uploadedImageData?: string; } interface TrainOfThoughtProps { @@ -328,8 +327,14 @@ const ChatMessage = forwardRef((props, ref) => .replace(/\\\[/g, "LEFTBRACKET") .replace(/\\\]/g, "RIGHTBRACKET"); - if (props.chatMessage.uploadedImageData) { - message = `![uploaded image](${props.chatMessage.uploadedImageData})\n\n${message}`; + if (props.chatMessage.images && props.chatMessage.images.length > 0) { + const imagesInMd = props.chatMessage.images + .map( + (image) => + `![uploaded image](${image.startsWith("data%3Aimage") ? decodeURIComponent(image) : image})`, + ) + .join("\n\n"); + message = `${imagesInMd}\n\n${message}`; } if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image") { @@ -364,7 +369,7 @@ const ChatMessage = forwardRef((props, ref) => // Sanitize and set the rendered markdown setMarkdownRendered(DOMPurify.sanitize(markdownRendered)); - }, [props.chatMessage.message, props.chatMessage.intent]); + }, [props.chatMessage.message, props.chatMessage.images, props.chatMessage.intent]); useEffect(() => { if (copySuccess) { diff --git a/src/interface/web/app/page.tsx b/src/interface/web/app/page.tsx index 158b6fb7..7002a340 100644 --- a/src/interface/web/app/page.tsx +++ b/src/interface/web/app/page.tsx @@ -44,7 +44,7 @@ function FisherYatesShuffle(array: any[]) { function ChatBodyData(props: ChatBodyDataProps) { const [message, setMessage] = useState(""); - const [image, setImage] = useState(null); + const [images, setImages] = useState([]); const [processingMessage, setProcessingMessage] = useState(false); const [greeting, setGreeting] = useState(""); const [shuffledOptions, setShuffledOptions] = useState([]); @@ -140,18 +140,19 @@ function ChatBodyData(props: ChatBodyDataProps) { onConversationIdChange?.(newConversationId); window.location.href = `/chat?conversationId=${newConversationId}`; localStorage.setItem("message", message); - if (image) { - localStorage.setItem("image", image); + if (images.length > 0) { + localStorage.setItem("images", JSON.stringify(images)); } } catch (error) { console.error("Error creating new conversation:", error); setProcessingMessage(false); } setMessage(""); + setImages([]); } }; processMessage(); - if (message) { + if (message || images.length > 0) { setProcessingMessage(true); } }, [selectedAgent, message, processingMessage, onConversationIdChange]); @@ -232,7 +233,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setMessage(message)} - sendImage={(image) => setImage(image)} + sendImage={(image) => setImages((prevImages) => [...prevImages, image])} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={null} @@ -313,7 +314,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setMessage(message)} - sendImage={(image) => setImage(image)} + sendImage={(image) => setImages((prevImages) => [...prevImages, image])} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={null} diff --git a/src/interface/web/app/share/chat/page.tsx b/src/interface/web/app/share/chat/page.tsx index 9bc5f12d..b1b92034 100644 --- a/src/interface/web/app/share/chat/page.tsx +++ b/src/interface/web/app/share/chat/page.tsx @@ -28,22 +28,40 @@ interface ChatBodyDataProps { isLoggedIn: boolean; conversationId?: string; setQueryToProcess: (query: string) => void; - setImage64: (image64: string) => void; + setImages: (images: string[]) => void; } function ChatBodyData(props: ChatBodyDataProps) { const [message, setMessage] = useState(""); - const [image, setImage] = useState(null); + const [images, setImages] = useState([]); const [processingMessage, setProcessingMessage] = useState(false); const [agentMetadata, setAgentMetadata] = useState(null); const setQueryToProcess = props.setQueryToProcess; const streamedMessages = props.streamedMessages; useEffect(() => { - if (image) { - props.setImage64(encodeURIComponent(image)); + if (images.length > 0) { + const encodedImages = images.map((image) => encodeURIComponent(image)); + props.setImages(encodedImages); } - }, [image, props.setImage64]); + }, [images, props.setImages]); + + useEffect(() => { + const storedImages = localStorage.getItem("images"); + if (storedImages) { + const parsedImages: string[] = JSON.parse(storedImages); + setImages(parsedImages); + const encodedImages = parsedImages.map((img: string) => encodeURIComponent(img)); + props.setImages(encodedImages); + localStorage.removeItem("images"); + } + + const storedMessage = localStorage.getItem("message"); + if (storedMessage) { + setProcessingMessage(true); + setQueryToProcess(storedMessage); + } + }, [setQueryToProcess, props.setImages]); useEffect(() => { if (message) { @@ -86,7 +104,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setMessage(message)} - sendImage={(image) => setImage(image)} + sendImage={(image) => setImages((prevImages) => [...prevImages, image])} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={props.conversationId} @@ -109,7 +127,7 @@ export default function SharedChat() { const [processQuerySignal, setProcessQuerySignal] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); const [paramSlug, setParamSlug] = useState(undefined); - const [image64, setImage64] = useState(""); + const [images, setImages] = useState([]); const locationData = useIPLocationData() || { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -167,7 +185,7 @@ export default function SharedChat() { completed: false, timestamp: new Date().toISOString(), rawQuery: queryToProcess || "", - uploadedImageData: decodeURIComponent(image64), + images: images, }; setMessages((prevMessages) => [...prevMessages, newStreamMessage]); setProcessQuerySignal(true); @@ -194,7 +212,7 @@ export default function SharedChat() { if (done) { setQueryToProcess(""); setProcessQuerySignal(false); - setImage64(""); + setImages([]); break; } @@ -236,7 +254,7 @@ export default function SharedChat() { country_code: locationData.countryCode, timezone: locationData.timezone, }), - ...(image64 && { image: image64 }), + ...(images.length > 0 && { image: images }), }; const response = await fetch(chatAPI, { @@ -286,7 +304,7 @@ export default function SharedChat() { setTitle={setTitle} setUploadedFiles={setUploadedFiles} isMobileWidth={isMobileWidth} - setImage64={setImage64} + setImages={setImages} />
From 3e39fac455f58f390113d7fc34439c0387e159b1 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Fri, 18 Oct 2024 19:13:06 -0700 Subject: [PATCH 31/71] Add vision support for Gemini models in Khoj --- .../conversation/google/gemini_chat.py | 38 ++++++++++----- .../processor/conversation/google/utils.py | 46 ++++++++++++++----- src/khoj/processor/conversation/utils.py | 2 +- src/khoj/routers/api.py | 2 + src/khoj/routers/helpers.py | 6 ++- 5 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/khoj/processor/conversation/google/gemini_chat.py b/src/khoj/processor/conversation/google/gemini_chat.py index e8848806..10af8b4d 100644 --- a/src/khoj/processor/conversation/google/gemini_chat.py +++ b/src/khoj/processor/conversation/google/gemini_chat.py @@ -13,7 +13,10 @@ from khoj.processor.conversation.google.utils import ( gemini_chat_completion_with_backoff, gemini_completion_with_backoff, ) -from khoj.processor.conversation.utils import generate_chatml_messages_with_context +from khoj.processor.conversation.utils import ( + construct_structured_message, + generate_chatml_messages_with_context, +) from khoj.utils.helpers import ConversationCommand, is_none_or_empty from khoj.utils.rawconfig import LocationData @@ -29,6 +32,8 @@ def extract_questions_gemini( max_tokens=None, location_data: LocationData = None, user: KhojUser = None, + query_images: Optional[list[str]] = None, + vision_enabled: bool = False, personality_context: Optional[str] = None, ): """ @@ -70,17 +75,17 @@ def extract_questions_gemini( text=text, ) - messages = [ChatMessage(content=prompt, role="user")] + prompt = construct_structured_message( + message=prompt, + images=query_images, + model_type=ChatModelOptions.ModelType.GOOGLE, + vision_enabled=vision_enabled, + ) - model_kwargs = {"response_mime_type": "application/json"} + messages = [ChatMessage(content=prompt, role="user"), ChatMessage(content=system_prompt, role="system")] - response = gemini_completion_with_backoff( - messages=messages, - system_prompt=system_prompt, - model_name=model, - temperature=temperature, - api_key=api_key, - model_kwargs=model_kwargs, + response = gemini_send_message_to_model( + messages, api_key, model, response_type="json_object", temperature=temperature ) # Extract, Clean Message from Gemini's Response @@ -102,7 +107,7 @@ def extract_questions_gemini( return questions -def gemini_send_message_to_model(messages, api_key, model, response_type="text"): +def gemini_send_message_to_model(messages, api_key, model, response_type="text", temperature=0, model_kwargs=None): """ Send message to model """ @@ -114,7 +119,12 @@ def gemini_send_message_to_model(messages, api_key, model, response_type="text") # Get Response from Gemini return gemini_completion_with_backoff( - messages=messages, system_prompt=system_prompt, model_name=model, api_key=api_key, model_kwargs=model_kwargs + messages=messages, + system_prompt=system_prompt, + model_name=model, + api_key=api_key, + temperature=temperature, + model_kwargs=model_kwargs, ) @@ -133,6 +143,8 @@ def converse_gemini( location_data: LocationData = None, user_name: str = None, agent: Agent = None, + query_images: Optional[list[str]] = None, + vision_available: bool = False, ): """ Converse with user using Google's Gemini @@ -187,6 +199,8 @@ def converse_gemini( model_name=model, max_prompt_size=max_prompt_size, tokenizer_name=tokenizer_name, + query_images=query_images, + vision_enabled=vision_available, model_type=ChatModelOptions.ModelType.GOOGLE, ) diff --git a/src/khoj/processor/conversation/google/utils.py b/src/khoj/processor/conversation/google/utils.py index 5679ba4d..d19b02f2 100644 --- a/src/khoj/processor/conversation/google/utils.py +++ b/src/khoj/processor/conversation/google/utils.py @@ -1,8 +1,11 @@ import logging import random +from io import BytesIO from threading import Thread import google.generativeai as genai +import PIL.Image +import requests from google.generativeai.types.answer_types import FinishReason from google.generativeai.types.generation_types import StopCandidateException from google.generativeai.types.safety_types import ( @@ -53,14 +56,14 @@ def gemini_completion_with_backoff( }, ) - formatted_messages = [{"role": message.role, "parts": [message.content]} for message in messages] + formatted_messages = [{"role": message.role, "parts": message.content} for message in messages] # Start chat session. All messages up to the last are considered to be part of the chat history chat_session = model.start_chat(history=formatted_messages[0:-1]) try: # Generate the response. The last message is considered to be the current prompt - aggregated_response = chat_session.send_message(formatted_messages[-1]["parts"][0]) + aggregated_response = chat_session.send_message(formatted_messages[-1]["parts"]) return aggregated_response.text except StopCandidateException as e: response_message, _ = handle_gemini_response(e.args) @@ -117,11 +120,11 @@ def gemini_llm_thread(g, messages, system_prompt, model_name, temperature, api_k }, ) - formatted_messages = [{"role": message.role, "parts": [message.content]} for message in messages] + formatted_messages = [{"role": message.role, "parts": message.content} for message in messages] # all messages up to the last are considered to be part of the chat history chat_session = model.start_chat(history=formatted_messages[0:-1]) # the last message is considered to be the current prompt - for chunk in chat_session.send_message(formatted_messages[-1]["parts"][0], stream=True): + for chunk in chat_session.send_message(formatted_messages[-1]["parts"], stream=True): message, stopped = handle_gemini_response(chunk.candidates, chunk.prompt_feedback) message = message or chunk.text g.send(message) @@ -191,14 +194,6 @@ def generate_safety_response(safety_ratings): def format_messages_for_gemini(messages: list[ChatMessage], system_prompt: str = None) -> tuple[list[str], str]: - if len(messages) == 1: - messages[0].role = "user" - return messages, system_prompt - - for message in messages: - if message.role == "assistant": - message.role = "model" - # Extract system message system_prompt = system_prompt or "" for message in messages.copy(): @@ -207,4 +202,31 @@ def format_messages_for_gemini(messages: list[ChatMessage], system_prompt: str = messages.remove(message) system_prompt = None if is_none_or_empty(system_prompt) else system_prompt + for message in messages: + # Convert message content to string list from chatml dictionary list + if isinstance(message.content, list): + # Convert image_urls to PIL.Image and place them at beginning of list (better for Gemini) + message.content = [ + get_image_from_url(item["image_url"]["url"]) if item["type"] == "image_url" else item["text"] + for item in sorted(message.content, key=lambda x: 0 if x["type"] == "image_url" else 1) + ] + elif isinstance(message.content, str): + message.content = [message.content] + + if message.role == "assistant": + message.role = "model" + + if len(messages) == 1: + messages[0].role = "user" + return messages, system_prompt + + +def get_image_from_url(image_url: str) -> PIL.Image: + try: + response = requests.get(image_url) + response.raise_for_status() # Check if the request was successful + return PIL.Image.open(BytesIO(response.content)) + except requests.exceptions.RequestException as e: + logger.error(f"Failed to get image from URL {image_url}: {e}") + return None diff --git a/src/khoj/processor/conversation/utils.py b/src/khoj/processor/conversation/utils.py index 8d799745..789be3a5 100644 --- a/src/khoj/processor/conversation/utils.py +++ b/src/khoj/processor/conversation/utils.py @@ -152,7 +152,7 @@ def construct_structured_message(message: str, images: list[str], model_type: st if not images or not vision_enabled: return message - if model_type == ChatModelOptions.ModelType.OPENAI: + if model_type in [ChatModelOptions.ModelType.OPENAI, ChatModelOptions.ModelType.GOOGLE]: return [ {"type": "text", "text": message}, *[{"type": "image_url", "image_url": {"url": image}} for image in images], diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py index 075c8c47..33edd61f 100644 --- a/src/khoj/routers/api.py +++ b/src/khoj/routers/api.py @@ -452,12 +452,14 @@ async def extract_references_and_questions( chat_model = conversation_config.chat_model inferred_queries = extract_questions_gemini( defiltered_query, + query_images=query_images, model=chat_model, api_key=api_key, conversation_log=meta_log, location_data=location_data, max_tokens=conversation_config.max_prompt_size, user=user, + vision_enabled=vision_enabled, personality_context=personality_context, ) diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index 7ed9c72d..739a3ad6 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -995,8 +995,9 @@ def generate_chat_response( chat_response = converse_gemini( compiled_references, q, - online_results, - meta_log, + query_images=query_images, + online_results=online_results, + conversation_log=meta_log, model=conversation_config.chat_model, api_key=api_key, completion_func=partial_completion, @@ -1006,6 +1007,7 @@ def generate_chat_response( location_data=location_data, user_name=user_name, agent=agent, + vision_available=vision_available, ) metadata.update({"chat_model": conversation_config.chat_model}) From 58a331227dab6fcfa60cad63edef519766bcff9d Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sat, 19 Oct 2024 16:29:45 -0700 Subject: [PATCH 32/71] Display the attached images inside the chat input area on the web app - Put the attached images display div inside the same parent div as the text area - Keep the attachment, microphone/send message buttons aligned with the text area. So the attached images just show up at the top of the text area but everything else stays at the same horizontal height as before. - This improves the UX by - Ensuring that the attached images do not obscure the agents pane above the chat input area - The attached images visually look like they are inside the actual input area, rather than floating above it. So the visual aligns with the semantics --- .../chatInputArea/chatInputArea.tsx | 167 +++++++++--------- 1 file changed, 85 insertions(+), 82 deletions(-) diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index fde23a0d..35e34f99 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -420,32 +420,11 @@ export default function ChatInputArea(props: ChatInputProps) { )}
- {imageUploaded && ( -
- {imagePaths.map((path, index) => ( -
- {`img-${index}`} - -
- ))} -
- )} - -
+
+ +
+
+
+ {imageUploaded && + imagePaths.map((path, index) => ( +
+ {`img-${index}`} + +
+ ))} +