From f867504f69480cddca273804f2a2ebcaa33efa2b Mon Sep 17 00:00:00 2001 From: Evan Bonsignori Date: Wed, 5 Feb 2025 10:31:35 -0800 Subject: [PATCH 1/4] return empty string when empty, not null (#54262) --- src/events/components/experiments/experiment.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/events/components/experiments/experiment.ts b/src/events/components/experiments/experiment.ts index 404721136d9a..595a02e7d5b9 100644 --- a/src/events/components/experiments/experiment.ts +++ b/src/events/components/experiments/experiment.ts @@ -83,7 +83,7 @@ if (typeof window !== 'undefined') { export function getExperimentControlGroupFromSession( experimentKey: ExperimentNames, percentToGetExperiment = 50, -) { +): string { if (controlGroupOverride[experimentKey]) { return controlGroupOverride[experimentKey] } else if (process.env.NODE_ENV === 'development') { @@ -99,7 +99,7 @@ export function getExperimentControlGroupFromSession( return modHash < percentToGetExperiment ? TREATMENT_VARIATION : CONTROL_VARIATION } -export function getExperimentVariationForContext(locale: string) { +export function getExperimentVariationForContext(locale: string): string { const experiments = getActiveExperiments(locale) for (const experiment of experiments) { if (experiment.includeVariationInContext) { @@ -111,7 +111,7 @@ export function getExperimentVariationForContext(locale: string) { } // When no experiment has `includeVariationInContext: true` - return null + return '' } export function initializeExperiments(locale: string) { From 52aec0f08ad8c0f46f1401ca28f53c9c7e775f62 Mon Sep 17 00:00:00 2001 From: hubwriter Date: Wed, 5 Feb 2025 18:44:14 +0000 Subject: [PATCH 2/4] Add Copilot docs for the Gemini 2.0 Flash model (#54076) Co-authored-by: Sophie <29382425+sophietheking@users.noreply.github.com> Co-authored-by: Paul Loeb <90000203+thispaul@users.noreply.github.com> Co-authored-by: Melanie Yarbrough <11952755+myarb@users.noreply.github.com> --- .../help/copilot/copilot-immersive-button.png | Bin 0 -> 47888 bytes .../responsible-use-autofix-code-scanning.md | 2 +- .../about-github-copilot-free.md | 2 +- ...ot-policies-as-an-individual-subscriber.md | 9 +- ...features-for-copilot-in-your-enterprise.md | 27 ++---- ...licies-for-copilot-in-your-organization.md | 5 +- ...oting-common-issues-with-github-copilot.md | 2 +- .../changing-the-ai-model-for-copilot-chat.md | 90 ++++++++++++++++++ .../using-github-copilot/ai-models/index.md | 13 +++ .../using-claude-sonnet-in-github-copilot.md | 15 ++- .../using-gemini-flash-in-github-copilot.md | 52 ++++++++++ ...king-github-copilot-questions-in-github.md | 24 +---- ...ng-github-copilot-questions-in-your-ide.md | 26 +---- content/copilot/using-github-copilot/index.md | 2 +- ...i-models-into-your-development-workflow.md | 6 +- .../prototyping-with-ai-models.md | 2 +- data/reusables/copilot/change-the-ai-model.md | 1 + .../copilot/claude-sonnet-preview-note.md | 1 - .../copilot/copilot-chat-models-beta-note.md | 1 - ...copilot-chat-models-list-visual-studio.md} | 9 +- ...list-o3.md => copilot-chat-models-list.md} | 11 ++- data/reusables/copilot/gemini-model-info.md | 1 + ...i-models-into-your-development-workflow.md | 64 +++++++++++++ .../copilot/model-picker-enable-o1-models.md | 2 +- .../models/o1-models-preview-note.md | 1 - data/variables/copilot.yml | 1 + 26 files changed, 270 insertions(+), 99 deletions(-) create mode 100644 assets/images/help/copilot/copilot-immersive-button.png create mode 100644 content/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat.md create mode 100644 content/copilot/using-github-copilot/ai-models/index.md rename content/copilot/using-github-copilot/{ => ai-models}/using-claude-sonnet-in-github-copilot.md (80%) create mode 100644 content/copilot/using-github-copilot/ai-models/using-gemini-flash-in-github-copilot.md create mode 100644 data/reusables/copilot/change-the-ai-model.md delete mode 100644 data/reusables/copilot/claude-sonnet-preview-note.md delete mode 100644 data/reusables/copilot/copilot-chat-models-beta-note.md rename data/reusables/copilot/{copilot-chat-models-list-o1-preview.md => copilot-chat-models-list-visual-studio.md} (58%) rename data/reusables/copilot/{copilot-chat-models-list-o3.md => copilot-chat-models-list.md} (63%) create mode 100644 data/reusables/copilot/gemini-model-info.md create mode 100644 data/reusables/copilot/integrating-ai-models-into-your-development-workflow.md delete mode 100644 data/reusables/models/o1-models-preview-note.md diff --git a/assets/images/help/copilot/copilot-immersive-button.png b/assets/images/help/copilot/copilot-immersive-button.png new file mode 100644 index 0000000000000000000000000000000000000000..08cd4c8de6b8d70a07239c709ff1350d6dd3a469 GIT binary patch literal 47888 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
T&`dakV6+MZm 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 h-~U^WIpXW-8TNN8k8z&2G>E{Ts* [!NOTE] The following models are currently in {% data variables.release-phases.public_preview %} as AI models for {% data variables.product.prodname_copilot %}, and are subject to change. The [AUTOTITLE](/free-pro-team@latest/site-policy/github-terms/github-pre-release-license-terms) apply to your use of these products. -By default, {% data variables.product.prodname_copilot_chat_short %} uses the `GPT 4o` model. If you grant access to **Anthropic {% data variables.copilot.copilot_claude_sonnet %} in {% data variables.product.prodname_copilot_short %}**, members of your enterprise can choose to use this model rather than the default `GPT 4o` model. See [AUTOTITLE](/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot). +By default, {% data variables.product.prodname_copilot_chat_short %} uses the GPT 4o model. If you grant access to the alternative models, members of your enterprise can choose to use these models rather than the default GPT 4o model. The available alternative models are: -### {% data variables.product.prodname_copilot_short %} access to the o1 and o3 families of models - -{% data reusables.models.o1-models-preview-note %} - -By default, {% data variables.product.prodname_copilot_chat_short %} uses the `GPT 4o` model. If you grant access to the o1 or o3 models, members of your enterprise can select to use these models rather than the default `GPT 4o` model. - -The o1 family of models includes the following models: - -* `o1`/`o1-preview`: These models are focused on advanced reasoning and solving complex problems, in particular in math and science. They respond more slowly than the `gpt-4o` model. Each member of your enterprise can make 10 requests to each of these models per day. - -The o3 family of models includes one model: - -* `o3-mini`: This is the next generation of reasoning models, following from `o1` and `o1-mini`. The `o3-mini` model outperforms `o1` on coding benchmarks with response times that are comparable to `o1-mini`, providing improved quality at nearly the same latency. It is best suited for code generation and small context operations. Each member of your enterprise can make 50 requests to this model every 12 hours. +* **{% data variables.copilot.copilot_claude_sonnet %}**. See [AUTOTITLE](/copilot/using-github-copilot/ai-models/using-claude-sonnet-in-github-copilot). +* **{% data variables.copilot.copilot_gemini_flash %}**. See [AUTOTITLE](/copilot/using-github-copilot/ai-models/using-gemini-flash-in-github-copilot). +* **OpenAI's o1 and o3 models** + * **o1**: This model is focused on advanced reasoning and solving complex problems, in particular in math and science. It responds more slowly than the GPT 4o model. Each member of your enterprise can make 10 requests to this model per day. + * **o3-mini**: This is the next generation of reasoning models, following from o1 and o1-mini. The o3-mini model outperforms o1 on coding benchmarks with response times that are comparable to o1-mini, providing improved quality at nearly the same latency. It is best suited for code generation and small context operations. Each member of your enterprise can make 50 requests to this model every 12 hours. ### {% data variables.product.prodname_copilot_short %} Metrics API access diff --git a/content/copilot/managing-copilot/managing-github-copilot-in-your-organization/managing-policies-for-copilot-in-your-organization.md b/content/copilot/managing-copilot/managing-github-copilot-in-your-organization/managing-policies-for-copilot-in-your-organization.md index e468ef742708..c7b7100c1923 100644 --- a/content/copilot/managing-copilot/managing-github-copilot-in-your-organization/managing-policies-for-copilot-in-your-organization.md +++ b/content/copilot/managing-copilot/managing-github-copilot-in-your-organization/managing-policies-for-copilot-in-your-organization.md @@ -32,8 +32,9 @@ Organization owners can set policies to govern how {% data variables.product.pro * {% data variables.product.prodname_copilot_cli_short %} and {% data variables.product.prodname_windows_terminal %} * Suggestions matching public code * Access to alternative models for {% data variables.product.prodname_copilot_short %} - * Anthropic {% data variables.copilot.copilot_claude_sonnet %} in Copilot - * OpenAI o1 and o3 models in Copilot + * Anthropic {% data variables.copilot.copilot_claude_sonnet %} in {% data variables.product.prodname_copilot_short %} + * Google {% data variables.copilot.copilot_gemini_flash %} in {% data variables.product.prodname_copilot_short %} + * OpenAI o1 and o3 models in {% data variables.product.prodname_copilot_short %} The policy settings selected by an organization owner determine the behavior of {% data variables.product.prodname_copilot %} for all organization members that have been granted access to {% data variables.product.prodname_copilot_short %} through the organization. diff --git a/content/copilot/troubleshooting-github-copilot/troubleshooting-common-issues-with-github-copilot.md b/content/copilot/troubleshooting-github-copilot/troubleshooting-common-issues-with-github-copilot.md index 38566015daf2..fe5140115e8d 100644 --- a/content/copilot/troubleshooting-github-copilot/troubleshooting-common-issues-with-github-copilot.md +++ b/content/copilot/troubleshooting-github-copilot/troubleshooting-common-issues-with-github-copilot.md @@ -64,7 +64,7 @@ For more information, see the [{% data variables.product.prodname_copilot_cli_sh This error suggests that you have exceeded the rate limit for {% data variables.product.prodname_copilot_short %} requests. {% data variables.product.github %} uses rate limits to ensure everyone has fair access to the {% data variables.product.prodname_copilot_short %} service and to protect against abuse. -Most people see rate limiting for preview models, like OpenAI’s o1 and o1-mini, which are rate-limited due to limited capacity. +Most people see rate limiting for preview models, like OpenAI’s o1 and o3-mini, which are rate-limited due to limited capacity. Service-level request rate limits ensure high service quality for all {% data variables.product.prodname_copilot_short %} users and should not affect typical or even deeply engaged {% data variables.product.prodname_copilot_short %} usage. We are aware of some use cases that are affected by it. {% data variables.product.github %} is iterating on {% data variables.product.prodname_copilot_short %}’s rate-limiting heuristics to ensure it doesn’t block legitimate use cases. diff --git a/content/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat.md b/content/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat.md new file mode 100644 index 000000000000..cb5af5be5543 --- /dev/null +++ b/content/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat.md @@ -0,0 +1,90 @@ +--- +title: Changing the AI model for Copilot Chat +shortTitle: 'Change the AI model' +intro: 'Learn how to change the default LLM for {% data variables.product.prodname_copilot_chat_short %} to a different model.' +versions: + feature: copilot +topics: + - Copilot +--- + +By default, {% data variables.product.prodname_copilot_chat_short %} uses OpenAI's GPT 4o large language model. This is a highly proficient model that performs well for text generation tasks, such as summarization and knowledge-based chat. The model is also capable of reasoning, solving complex math problems and coding. + +However, you are not limited to using this model. You can choose from a selection of other models, each with its own particular strengths. You may have a favorite model that you like to use, or you might prefer to use a particular model for inquiring about a specific subject. + +{% data variables.product.prodname_copilot_short %} allows you to change the model during a chat and have the alternative model used to generate responses to your prompts. + +{% webui %} + +> [!NOTE] +> * Multiple model support in {% data variables.product.prodname_copilot_chat_short %} is in {% data variables.release-phases.public_preview %} and is subject to change. +> * You can only use an alternative AI model in the immersive view of {% data variables.product.prodname_copilot_chat_short %}. This is the full-page version of {% data variables.product.prodname_copilot_chat_short %} that's displayed at [https://github.com/copilot](https://github.com/copilot). The {% data variables.product.prodname_copilot_chat_short %} panel always uses the default model. + +## AI models for {% data variables.product.prodname_copilot_chat_short %} + +{% data reusables.copilot.copilot-chat-models-list %} + +### Limitations of AI models for {% data variables.product.prodname_copilot_chat_short %} + +* If you want to use the skills listed in the table above{% ifversion ghec %}, or knowledge bases{% endif %}, on the {% data variables.product.github %} website, only the GPT 4o, {% data variables.copilot.copilot_claude_sonnet %}, and {% data variables.copilot.copilot_gemini_flash %} models are supported. +* Experimental pre-release versions of the models may not interact with all filters correctly, including the duplication detection filter. + +## Changing your AI model + +These instructions are for {% data variables.product.prodname_copilot_short %} on the {% data variables.product.github %} website. For {% data variables.product.prodname_vs %} or {% data variables.product.prodname_vscode_shortname %}, click the appropriate tab at the top of this page. + +{% data reusables.copilot.model-picker-enable-o1-models %} + +> [!NOTE] If you use {% data variables.product.prodname_copilot_extensions_short %}, they may override the model you select. + +1. In the top right of any page on {% data variables.product.github %}, click the down arrow beside the **{% octicon "copilot" aria-hidden="true" %}** icon and click **Immersive** in the dropdown menu. + + ![Screenshot of the 'Immersive' button, highlighted with a dark orange outline.](/assets/images/help/copilot/copilot-immersive-button.png) + +1. At the top of the immersive view, select the **CURRENT-MODEL** {% octicon "chevron-down" aria-hidden="true" %} dropdown menu, then click the AI model of your choice. + +{% endwebui %} + +{% vscode %} + +> [!NOTE] Multiple model support in {% data variables.product.prodname_copilot_chat_short %} is in {% data variables.release-phases.public_preview %} and is subject to change. + +## AI models for {% data variables.product.prodname_copilot_chat_short %} + +{% data reusables.copilot.copilot-chat-models-list %} + +## Changing your AI model + +These instructions are for {% data variables.product.prodname_vscode_shortname %}. For {% data variables.product.prodname_vs %} or for {% data variables.product.prodname_copilot_short %} on the {% data variables.product.github %} website, click the appropriate tab at the top of this page. + +{% data reusables.copilot.model-picker-enable-o1-models %} + +{% data reusables.copilot.chat-model-limitations-ide %} + +{% data reusables.copilot.open-chat-vs-code %} +1. In the bottom right of the chat view, select the **CURRENT-MODEL** {% octicon "chevron-down" aria-hidden="true" %} dropdown menu, then click the AI model of your choice. + +{% endvscode %} + +{% visualstudio %} + +> [!NOTE] Multiple model support in {% data variables.product.prodname_copilot_chat_short %} is in {% data variables.release-phases.public_preview %} and is subject to change. + +## AI models for {% data variables.product.prodname_copilot_chat_short %} + +{% data reusables.copilot.copilot-chat-models-list-visual-studio %} + +## Changing your AI model + +These instructions are for {% data variables.product.prodname_vs %}. For {% data variables.product.prodname_vscode_shortname %} or for {% data variables.product.prodname_copilot_short %} on the {% data variables.product.github %} website, click the appropriate tab at the top of this page. + +To use multi-model {% data variables.product.prodname_copilot_chat_short %}, you must use {% data variables.product.prodname_vs %} 2022 version 17.12 or later. See the [{% data variables.product.prodname_vs %} downloads page](https://visualstudio.microsoft.com/downloads/). + +{% data reusables.copilot.model-picker-enable-o1-models %} + +{% data reusables.copilot.chat-model-limitations-ide %} + +1. In the {% data variables.product.prodname_vs %} menu bar, click **View**, then click **{% data variables.product.prodname_copilot_chat %}**. +1. In the bottom right of the chat view, select the **CURRENT-MODEL** {% octicon "triangle-down" aria-hidden="true" %} dropdown menu, then click the AI model of your choice. + +{% endvisualstudio %} diff --git a/content/copilot/using-github-copilot/ai-models/index.md b/content/copilot/using-github-copilot/ai-models/index.md new file mode 100644 index 000000000000..ca21e7692857 --- /dev/null +++ b/content/copilot/using-github-copilot/ai-models/index.md @@ -0,0 +1,13 @@ +--- +title: AI models for Copilot Chat +shortTitle: AI models +intro: "Learn how to use alternative large language models for {% data variables.product.prodname_copilot_chat %}." +versions: + feature: copilot +topics: + - Copilot +children: + - /changing-the-ai-model-for-copilot-chat + - /using-claude-sonnet-in-github-copilot + - /using-gemini-flash-in-github-copilot +--- diff --git a/content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md b/content/copilot/using-github-copilot/ai-models/using-claude-sonnet-in-github-copilot.md similarity index 80% rename from content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md rename to content/copilot/using-github-copilot/ai-models/using-claude-sonnet-in-github-copilot.md index 3d08879413ef..2fe76b7e731f 100644 --- a/content/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot.md +++ b/content/copilot/using-github-copilot/ai-models/using-claude-sonnet-in-github-copilot.md @@ -7,9 +7,11 @@ versions: feature: copilot topics: - Copilot +redirect_from: + - /copilot/using-github-copilot/using-claude-sonnet-in-github-copilot --- -{% data reusables.copilot.claude-sonnet-preview-note %} +> [!NOTE] {% data variables.copilot.copilot_claude_sonnet %} is in {% data variables.release-phases.public_preview %} and subject to change. The [AUTOTITLE](/free-pro-team@latest/site-policy/github-terms/github-pre-release-license-terms) apply to your use of this product. ## About {% data variables.copilot.copilot_claude_sonnet %} in {% data variables.product.prodname_copilot %} @@ -18,7 +20,7 @@ topics: {% data variables.copilot.copilot_claude_sonnet %} is currently available in: * {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.prodname_vscode %} -* {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.prodname_vs %} 17.12 Preview 3 or later +* {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.prodname_vs %} 2022 version 17.12 or later * Immersive mode in {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.github %} {% data variables.product.prodname_copilot %} uses {% data variables.copilot.copilot_claude_sonnet %} hosted on Amazon Web Services. When using {% data variables.copilot.copilot_claude_sonnet %}, prompts and metadata are sent to Amazon's Bedrock service, which makes the [following data commitments](https://docs.aws.amazon.com/bedrock/latest/userguide/data-protection.html): _Amazon Bedrock doesn't store or log your prompts and completions. Amazon Bedrock doesn't use your prompts and completions to train any AWS models and doesn't distribute them to third parties_. @@ -39,7 +41,7 @@ If you have a {% data variables.product.prodname_copilot_free_short %} or {% dat Clicking **Allow** enables you to use {% data variables.copilot.copilot_claude_sonnet %} and updates the policy in your personal settings on {% data variables.product.github %}. -* You can enable the model directly in your personal settings on the {% data variables.product.github %} website. See [AUTOTITLE](/copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/managing-copilot-policies-as-an-individual-subscriber#enabling-or-disabling-claude-35-sonnet). +* You can enable the model directly in your personal settings on the {% data variables.product.github %} website. See [AUTOTITLE](/copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/managing-copilot-policies-as-an-individual-subscriber#enabling-or-disabling-alternative-ai-models). {% endif %} @@ -49,11 +51,8 @@ As an {% ifversion ghec %}enterprise or{% endif %} organization owner, you can e ## Using {% data variables.copilot.copilot_claude_sonnet %} -For details of how to change the model for {% data variables.product.prodname_copilot_chat_short %}, see: - -* [AUTOTITLE](/copilot/using-github-copilot/asking-github-copilot-questions-in-githubcom#changing-your-ai-model) -* [AUTOTITLE](/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide#changing-your-ai-model) +For details of how to change the model that {% data variables.product.prodname_copilot_chat_short %} uses, see: [AUTOTITLE](/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat). ## Leaving feedback -To leave feedback about Claude 3.5 Sonnet in {% data variables.product.prodname_copilot %}, or to ask a question, see the {% data variables.product.prodname_github_community %} discussion [Claude 3.5 Sonnet is now available to all {% data variables.product.prodname_copilot_short %} users in Public Preview](https://github.com/orgs/community/discussions/143337). +To leave feedback about {% data variables.copilot.copilot_claude_sonnet %} in {% data variables.product.prodname_copilot %}, or to ask a question, see the {% data variables.product.prodname_github_community %} discussion [Claude 3.5 Sonnet is now available to all {% data variables.product.prodname_copilot_short %} users in Public Preview](https://github.com/orgs/community/discussions/143337). diff --git a/content/copilot/using-github-copilot/ai-models/using-gemini-flash-in-github-copilot.md b/content/copilot/using-github-copilot/ai-models/using-gemini-flash-in-github-copilot.md new file mode 100644 index 000000000000..03ad1a25ada9 --- /dev/null +++ b/content/copilot/using-github-copilot/ai-models/using-gemini-flash-in-github-copilot.md @@ -0,0 +1,52 @@ +--- +title: Using Gemini 2.0 Flash in GitHub Copilot +allowTitleToDifferFromFilename: true +shortTitle: 'Use {% data variables.copilot.copilot_gemini_flash %}' +intro: 'Learn how to enable {% data variables.copilot.copilot_gemini_flash %} for {% ifversion fpt %}yourself or{% endif %} your organization{% ifversion ghec %} or enterprise{% endif %}.' +versions: + feature: copilot +topics: + - Copilot +--- + +> [!NOTE] {% data variables.copilot.copilot_gemini_flash %} is in {% data variables.release-phases.public_preview %} and subject to change. The [AUTOTITLE](/free-pro-team@latest/site-policy/github-terms/github-pre-release-license-terms) apply to your use of this product. + +## About {% data variables.copilot.copilot_gemini_flash %} in {% data variables.product.prodname_copilot %} + +{% data variables.copilot.copilot_gemini_flash %} is a large language model (LLM) that you can use as an alternative to the default model used by {% data variables.product.prodname_copilot_chat_short %}. {% data variables.copilot.copilot_gemini_flash %} is a responsive LLM that can empower you to build apps faster and more easily, so you can focus on great experiences for your users. {% data reusables.copilot.gemini-model-info %} + +{% data variables.copilot.copilot_gemini_flash %} is currently available in: + +* {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.prodname_vscode %} +* {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.prodname_vs %} 2022 version 17.12 or later +* Immersive mode in {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.github %} + +{% data variables.product.prodname_copilot %} uses {% data variables.copilot.copilot_gemini_flash %} hosted on Google Cloud Platform (GCP). When using {% data variables.copilot.copilot_gemini_flash %}, prompts and metadata are sent to GCP, which makes the [following data commitment](https://cloud.google.com/gemini/docs/discover/data-governance): _Gemini doesn't use your prompts, or its responses, as data to train its models._ + +When using {% data variables.copilot.copilot_gemini_flash %}, input prompts and output completions continue to run through {% data variables.product.prodname_copilot %}'s content filters for public code matching, when applied, along with those for harmful, offensive, or off-topic content. + +## Configuring access + +You must enable access to {% data variables.copilot.copilot_gemini_flash %} before you can use the model. + +{% ifversion fpt %} + +### Setup for individual use + +If you have a {% data variables.product.prodname_copilot_free_short %} or {% data variables.product.prodname_copilot_pro_short %} subscription, you can enable {% data variables.copilot.copilot_gemini_flash %} in two ways: + +* The first time you choose to use {% data variables.copilot.copilot_gemini_flash %} with {% data variables.product.prodname_copilot_chat_short %} in {% data variables.product.prodname_vscode %}, or in the immersive view of {% data variables.product.prodname_copilot_chat_short %}, you will be prompted to allow access to the model. + + Clicking **Allow** enables you to use {% data variables.copilot.copilot_gemini_flash %} and updates the policy in your personal settings on {% data variables.product.github %}. + +* You can enable the model directly in your personal settings on the {% data variables.product.github %} website. See [AUTOTITLE](/copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/managing-copilot-policies-as-an-individual-subscriber#enabling-or-disabling-alternative-ai-models). + +{% endif %} + +### Setup for organization {% ifversion ghec %}and enterprise{% endif %} use + +As an {% ifversion ghec %}enterprise or{% endif %} organization owner, you can enable or disable {% data variables.copilot.copilot_gemini_flash %} for everyone who has been assigned a {% ifversion ghec %}{% data variables.product.prodname_copilot_enterprise_short %} or {% endif %}{% data variables.product.prodname_copilot_business_short %} seat through your {% ifversion ghec %}enterprise or {% endif %}organization. See [AUTOTITLE](/copilot/managing-copilot/managing-github-copilot-in-your-organization/setting-policies-for-copilot-in-your-organization/managing-policies-for-copilot-in-your-organization){% ifversion ghec %} and [AUTOTITLE](/copilot/managing-copilot/managing-copilot-for-your-enterprise/managing-policies-and-features-for-copilot-in-your-enterprise#copilot-access-to-alternative-ai-models){% endif %}. + +## Using {% data variables.copilot.copilot_gemini_flash %} + +For details of how to change the model that {% data variables.product.prodname_copilot_chat_short %} uses, see [AUTOTITLE](/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat). diff --git a/content/copilot/using-github-copilot/asking-github-copilot-questions-in-github.md b/content/copilot/using-github-copilot/asking-github-copilot-questions-in-github.md index 154376ee2f81..5a5c5e666987 100644 --- a/content/copilot/using-github-copilot/asking-github-copilot-questions-in-github.md +++ b/content/copilot/using-github-copilot/asking-github-copilot-questions-in-github.md @@ -35,7 +35,7 @@ On {% data variables.product.github %}, you can use {% data variables.product.pr ## Powered by skills -When using the GPT-4o and {% data variables.copilot.copilot_claude_sonnet %} models, {% data variables.product.prodname_copilot_short %} has access to a collection of skills to fetch data from {% data variables.product.github %}, which are dynamically selected based on the question you ask. You can tell which skill {% data variables.product.prodname_copilot_short %} used by clicking {% octicon "chevron-down" aria-label="the down arrow" %} to expand the status information in the chat window. +When using the GPT 4o and {% data variables.copilot.copilot_claude_sonnet %} models, {% data variables.product.prodname_copilot_short %} has access to a collection of skills to fetch data from {% data variables.product.github %}, which are dynamically selected based on the question you ask. You can tell which skill {% data variables.product.prodname_copilot_short %} used by clicking {% octicon "chevron-down" aria-label="the down arrow" %} to expand the status information in the chat window. ![Screenshot of the {% data variables.product.prodname_copilot_short %} chat panel with the status information expanded and the skill that was used highlighted with an orange outline.](/assets/images/help/copilot/chat-show-skill.png) @@ -65,27 +65,7 @@ The skills you can use in {% data variables.product.prodname_copilot_chat_dotcom ## AI models for {% data variables.product.prodname_copilot_chat_short %} -{% data reusables.copilot.copilot-chat-models-beta-note %} - -{% data reusables.copilot.copilot-chat-models-list-o3 %} - -### Limitations of AI models for {% data variables.product.prodname_copilot_chat_short %} - -* If you want to use the skills listed in the table above{% ifversion ghec %}, or knowledge bases{% endif %}, on the {% data variables.product.github %} website, only the GPT 4o and {% data variables.copilot.copilot_claude_sonnet %} models are supported. -* Experimental pre-release versions of the models may not interact with all filters correctly, including the duplication detection filter. - -### Changing your AI model - -> [!NOTE] If you use {% data variables.product.prodname_copilot_extensions_short %}, they may override the model you select. - -{% data reusables.copilot.model-picker-enable-o1-models %} -1. In the top right of any page on {% data variables.product.github %}, click the **{% octicon "copilot" aria-hidden="true" %}** {% data variables.product.prodname_copilot %} icon next to the search bar. -1. If the panel contains a previous conversation you had with {% data variables.product.prodname_copilot_short %}, in the top right of the panel, click {% octicon "plus" aria-label="New conversation" %}. - - ![Screenshot of the new conversation button, highlighted with a dark orange outline.](/assets/images/help/copilot/chat-new-conversation-button.png) - -1. In the top right of the panel, click **{% octicon "screen-full" aria-hidden="true" %} Take conversation to immersive**. Multi-model {% data variables.product.prodname_copilot_chat_short %} is currently only available in the immersive view. -1. In the top left of the immersive view, select the **CURRENT-MODEL** {% octicon "chevron-down" aria-hidden="true" %} dropdown menu, then click the AI model of your choice. +{% data reusables.copilot.change-the-ai-model %} ## Asking a general question about software development diff --git a/content/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide.md b/content/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide.md index 37c7749103c7..69b2dad44d40 100644 --- a/content/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide.md +++ b/content/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide.md @@ -151,17 +151,7 @@ You can tell {% data variables.product.prodname_copilot_short %} to answer a que ## AI models for {% data variables.product.prodname_copilot_chat_short %} -{% data reusables.copilot.copilot-chat-models-beta-note %} - -{% data reusables.copilot.copilot-chat-models-list-o3 %} - -### Changing your AI model - -{% data reusables.copilot.chat-model-limitations-ide %} - -{% data reusables.copilot.model-picker-enable-o1-models %} -{% data reusables.copilot.open-chat-vs-code %} -1. In the bottom right of the chat view, select the **CURRENT-MODEL** {% octicon "chevron-down" aria-hidden="true" %} dropdown menu, then click the AI model of your choice. +{% data reusables.copilot.change-the-ai-model %} ## Additional ways to access {% data variables.product.prodname_copilot_chat_short %} @@ -306,19 +296,7 @@ You can tell {% data variables.product.prodname_copilot_short %} to answer a que ## AI models for {% data variables.product.prodname_copilot_chat_short %} -{% data reusables.copilot.copilot-chat-models-beta-note %} - -{% data reusables.copilot.copilot-chat-models-list-o1-preview %} - -### Changing your AI model - -To use multi-model {% data variables.product.prodname_copilot_chat_short %}, you must use {% data variables.product.prodname_vs %} 17.12 Preview 3 or later. See [{% data variables.product.prodname_vs %} 2022 Preview](https://visualstudio.microsoft.com/vs/preview/#download-preview) in the {% data variables.product.prodname_vs %} documentation. - -{% data reusables.copilot.chat-model-limitations-ide %} - -{% data reusables.copilot.model-picker-enable-o1-models %} -1. In the {% data variables.product.prodname_vs %} menu bar, click **View**, then click **{% data variables.product.prodname_copilot_chat %}**. -1. In the bottom right of the chat view, select the **CURRENT-MODEL** {% octicon "triangle-down" aria-hidden="true" %} dropdown menu, then click the AI model of your choice. +{% data reusables.copilot.change-the-ai-model %} ## Additional ways to access {% data variables.product.prodname_copilot_chat_short %} diff --git a/content/copilot/using-github-copilot/index.md b/content/copilot/using-github-copilot/index.md index 4f68a6a465ca..3dacf0b5bec3 100644 --- a/content/copilot/using-github-copilot/index.md +++ b/content/copilot/using-github-copilot/index.md @@ -19,7 +19,7 @@ children: - /using-github-copilot-in-the-command-line - /prompt-engineering-for-github-copilot - /using-extensions-to-integrate-external-tools-with-copilot-chat - - /using-claude-sonnet-in-github-copilot + - /ai-models - /finding-public-code-that-matches-github-copilot-suggestions - /using-github-copilot-for-pull-requests - /guides-on-using-github-copilot diff --git a/content/github-models/integrating-ai-models-into-your-development-workflow.md b/content/github-models/integrating-ai-models-into-your-development-workflow.md index fea6f6a40109..d1ac910660b3 100644 --- a/content/github-models/integrating-ai-models-into-your-development-workflow.md +++ b/content/github-models/integrating-ai-models-into-your-development-workflow.md @@ -12,7 +12,7 @@ With {% data variables.product.prodname_github_models %} extensions, you can cal If you have a {% data variables.product.prodname_copilot_short %} subscription, you can work with AI models in {% data variables.product.prodname_copilot_chat_short %} in two different ways: * Using the {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %}. With this extension, you can ask for model recommendations based on certain criteria and chat with specific models. See [Using the {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %}](#using-the-github-models-copilot-extension). -* Using multiple model support in {% data variables.product.prodname_copilot_chat_short %}. With multi-model {% data variables.product.prodname_copilot_chat_short %}, you can choose a specific model to use for a conversation, then prompt {% data variables.product.prodname_copilot_chat_short %} as usual. See [AUTOTITLE](/copilot/using-github-copilot/asking-github-copilot-questions-in-githubcom#ai-models-for-copilot-chat) and [AUTOTITLE](/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide#ai-models-for-copilot-chat). +* Using multiple model support in {% data variables.product.prodname_copilot_chat_short %}. With multi-model {% data variables.product.prodname_copilot_chat_short %}, you can choose a specific model to use for a conversation, then prompt {% data variables.product.prodname_copilot_chat_short %} as usual. See [AUTOTITLE](/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat). ### Using the {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %} @@ -60,5 +60,5 @@ To see a list of all available commands, run `gh models`. There are a few key ways you can use the extension: * **To ask a model multiple questions using a chat experience**, run `gh models run`. Select your model from the listed models, then send your prompts. - * **To ask a model a single question**, run `gh models run MODEL-NAME "QUESTION"` in your terminal. For example, to ask the `gpt-4o` model why the sky is blue, you can run `gh models run gpt-4o "why is the sky blue?"`. - * **To provide the output of a command as context when you call a model**, you can join a separate command and the call to the model with the pipe character (`|`). For example, to summarize the README file in the current directory using the `gpt-4o` model, you can run `cat README.md | gh models run gpt-4o "summarize this text"`. + * **To ask a model a single question**, run `gh models run MODEL-NAME "QUESTION"` in your terminal. For example, to ask the GPT 4o model why the sky is blue, you can run `gh models run gpt-4o "why is the sky blue?"`. + * **To provide the output of a command as context when you call a model**, you can join a separate command and the call to the model with the pipe character (`|`). For example, to summarize the README file in the current directory using the GPT 4o model, you can run `cat README.md | gh models run gpt-4o "summarize this text"`. diff --git a/content/github-models/prototyping-with-ai-models.md b/content/github-models/prototyping-with-ai-models.md index 90d77647576d..3d2933986369 100644 --- a/content/github-models/prototyping-with-ai-models.md +++ b/content/github-models/prototyping-with-ai-models.md @@ -18,7 +18,7 @@ To find an AI model: The model is opened in the model playground. Details of the model are displayed in the sidebar on the right. If the sidebar is not displayed, expand it by clicking the **{% octicon "sidebar-expand" aria-label="Show parameters setting" %}** icon at the right of the playground. -{% data reusables.models.o1-models-preview-note %} +> [!NOTE] Access to OpenAI's models is in {% data variables.release-phases.public_preview %} and subject to change. ## Experimenting with AI models in the playground diff --git a/data/reusables/copilot/change-the-ai-model.md b/data/reusables/copilot/change-the-ai-model.md new file mode 100644 index 000000000000..24f2c695ddbc --- /dev/null +++ b/data/reusables/copilot/change-the-ai-model.md @@ -0,0 +1 @@ +You can change the large language model that {% data variables.product.prodname_copilot_short %} uses to generate responses to chat prompts. You may find that different models perform better, or provide more useful responses, depending on the type of questions you ask. For more information see [AUTOTITLE](/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat). diff --git a/data/reusables/copilot/claude-sonnet-preview-note.md b/data/reusables/copilot/claude-sonnet-preview-note.md deleted file mode 100644 index f1a8e80cf033..000000000000 --- a/data/reusables/copilot/claude-sonnet-preview-note.md +++ /dev/null @@ -1 +0,0 @@ -> [!NOTE] {% data variables.copilot.copilot_claude_sonnet %} is in {% data variables.release-phases.public_preview %} and subject to change. The [AUTOTITLE](/free-pro-team@latest/site-policy/github-terms/github-pre-release-license-terms) apply to your use of this product. diff --git a/data/reusables/copilot/copilot-chat-models-beta-note.md b/data/reusables/copilot/copilot-chat-models-beta-note.md deleted file mode 100644 index ac3d185c0ea4..000000000000 --- a/data/reusables/copilot/copilot-chat-models-beta-note.md +++ /dev/null @@ -1 +0,0 @@ -> [!NOTE] Multiple model support in {% data variables.product.prodname_copilot_chat_short %} is in {% data variables.release-phases.public_preview %} and subject to change. diff --git a/data/reusables/copilot/copilot-chat-models-list-o1-preview.md b/data/reusables/copilot/copilot-chat-models-list-visual-studio.md similarity index 58% rename from data/reusables/copilot/copilot-chat-models-list-o1-preview.md rename to data/reusables/copilot/copilot-chat-models-list-visual-studio.md index 44a39a26aa6d..7c81451ca633 100644 --- a/data/reusables/copilot/copilot-chat-models-list-o1-preview.md +++ b/data/reusables/copilot/copilot-chat-models-list-visual-studio.md @@ -1,12 +1,9 @@ The following models are currently available through multi-model {% data variables.product.prodname_copilot_chat_short %}: -* **GPT 4o:** This is the default {% data variables.product.prodname_copilot_chat_short %} model. It is a versatile, multimodal model that excels in both text and image processing and is designed to provide fast, reliable responses. It also has superior performance in non-English languages. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/gpt-4o) and review the [model card](https://openai.com/index/gpt-4o-system-card/). Gpt-4o is hosted on Azure. +* **GPT 4o:** This is the default {% data variables.product.prodname_copilot_chat_short %} model. It is a versatile, multimodal model that excels in both text and image processing and is designed to provide fast, reliable responses. It also has superior performance in non-English languages. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/gpt-4o) and review the [model card](https://openai.com/index/gpt-4o-system-card/). GPT 4o is hosted on Azure. * **{% data variables.copilot.copilot_claude_sonnet %}:** This model excels at coding tasks across the entire software development lifecycle, from initial design to bug fixes, maintenance to optimizations. Learn more about the [model's capabilities](https://www.anthropic.com/claude/sonnet) or read the [model card](https://assets.anthropic.com/m/61e7d27f8c8f5919/original/Claude-3-Model-Card.pdf). {% data variables.product.prodname_copilot %} uses {% data variables.copilot.copilot_claude_sonnet %} hosted on Amazon Web Services. -* **o1-preview:** This model is focused on advanced reasoning and solving complex problems, in particular in math and science. It responds more slowly than the `gpt-4o` model. You can make 10 requests to this model per day. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/o1) and review the [model card](https://openai.com/index/openai-o1-system-card/). o1-preview is hosted on Azure. -* **o1-mini:** This is the faster version of the `o1-preview` model, balancing the use of complex reasoning with the need for faster responses. It is best suited for code generation and small context operations. You can make 50 requests to this model per day. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/o1) and review the [model card](https://openai.com/index/openai-o1-system-card/). o1-mini is hosted on Azure. - -> [!NOTE] -> Support for the `o1` model, replacing `o1-preview`, is coming soon to {% data variables.product.prodname_vs %}. +* **o1:** This model is focused on advanced reasoning and solving complex problems, in particular in math and science. It responds more slowly than the GPT 4o model. You can make 10 requests to this model per day. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/o1) and review the [model card](https://openai.com/index/openai-o1-system-card/). o1 is hosted on Azure. +* **o1-mini:** This is the faster version of the o1 model, balancing the use of complex reasoning with the need for faster responses. It is best suited for code generation and small context operations. You can make 50 requests to this model per day. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/o1) and review the [model card](https://openai.com/index/openai-o1-system-card/). o1-mini is hosted on Azure. For more information about the o1 models, see [Models](https://platform.openai.com/docs/models/models) in the OpenAI Platform documentation. diff --git a/data/reusables/copilot/copilot-chat-models-list-o3.md b/data/reusables/copilot/copilot-chat-models-list.md similarity index 63% rename from data/reusables/copilot/copilot-chat-models-list-o3.md rename to data/reusables/copilot/copilot-chat-models-list.md index de2f6665199f..009294d99dd0 100644 --- a/data/reusables/copilot/copilot-chat-models-list-o3.md +++ b/data/reusables/copilot/copilot-chat-models-list.md @@ -1,10 +1,13 @@ The following models are currently available through multi-model {% data variables.product.prodname_copilot_chat_short %}: -* **GPT 4o:** This is the default {% data variables.product.prodname_copilot_chat_short %} model. It is a versatile, multimodal model that excels in both text and image processing and is designed to provide fast, reliable responses. It also has superior performance in non-English languages. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/gpt-4o) and review the [model card](https://openai.com/index/gpt-4o-system-card/). Gpt-4o is hosted on Azure. +* **GPT 4o:** This is the default {% data variables.product.prodname_copilot_chat_short %} model. It is a versatile, multimodal model that excels in both text and image processing and is designed to provide fast, reliable responses. It also has superior performance in non-English languages. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/gpt-4o) and review the [model card](https://openai.com/index/gpt-4o-system-card/). GPT 4o is hosted on Azure. * **{% data variables.copilot.copilot_claude_sonnet %}:** This model excels at coding tasks across the entire software development lifecycle, from initial design to bug fixes, maintenance to optimizations. Learn more about the [model's capabilities](https://www.anthropic.com/claude/sonnet) or read the [model card](https://assets.anthropic.com/m/61e7d27f8c8f5919/original/Claude-3-Model-Card.pdf). {% data variables.product.prodname_copilot %} uses {% data variables.copilot.copilot_claude_sonnet %} hosted on Amazon Web Services. -* **o1:** This model is focused on advanced reasoning and solving complex problems, in particular in math and science. It responds more slowly than the `gpt-4o` model. You can make 10 requests to this model per day. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/o1) and review the [model card](https://openai.com/index/openai-o1-system-card/). o1 is hosted on Azure. +* **{% data variables.copilot.copilot_gemini_flash %}:** This model has strong coding, math, and reasoning capabilities that makes it well suited to assist with software development. {% data reusables.copilot.gemini-model-info %} +* **o1:** This model is focused on advanced reasoning and solving complex problems, in particular in math and science. It responds more slowly than the GPT 4o model. You can make 10 requests to this model per day. Learn more about the [model's capabilities](https://platform.openai.com/docs/models/o1) and review the [model card](https://openai.com/index/openai-o1-system-card/). o1 is hosted on Azure. * **o3-mini:** This model is the next generation of reasoning models, following from o1 and o1-mini. The o3-mini model outperforms o1 on coding benchmarks with response times that are comparable to o1-mini, providing improved quality at nearly the same latency. It is best suited for code generation and small context operations. You can make 50 requests to this model every 12 hours. Learn more about the [model's capabilities](https://platform.openai.com/docs/models#o3-mini) and review the [model card](https://openai.com/index/o3-mini-system-card/). o3-mini is hosted on Azure. -For more information about the o1 and o3 models, see [Models](https://platform.openai.com/docs/models/models) in the OpenAI Platform documentation. +For more information about these models, see: -For more information about the {% data variables.copilot.copilot_claude_sonnet %} model from Anthropic, see [AUTOTITLE](/copilot/using-github-copilot/using-claude-sonnet-in-github-copilot). +* **OpenAI's GPT 4o, o1, and o3-mini models**: [Models](https://platform.openai.com/docs/models/models) in the OpenAI Platform documentation. +* **Anthropic's {% data variables.copilot.copilot_claude_sonnet %} model**: [AUTOTITLE](/copilot/using-github-copilot/ai-models/using-claude-sonnet-in-github-copilot). +* **Google's {% data variables.copilot.copilot_gemini_flash %} model**: [AUTOTITLE](/copilot/using-github-copilot/ai-models/using-gemini-flash-in-github-copilot). diff --git a/data/reusables/copilot/gemini-model-info.md b/data/reusables/copilot/gemini-model-info.md new file mode 100644 index 000000000000..ab736a008f40 --- /dev/null +++ b/data/reusables/copilot/gemini-model-info.md @@ -0,0 +1 @@ +For information about the capabilities of {% data variables.copilot.copilot_gemini_flash %}, see the [Google for developers blog](https://developers.googleblog.com/en/the-next-chapter-of-the-gemini-era-for-developers/) and the [Google Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash). For details of Google's data handling policy, see [Generative AI and data governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance#prediction) on the Google website. diff --git a/data/reusables/copilot/integrating-ai-models-into-your-development-workflow.md b/data/reusables/copilot/integrating-ai-models-into-your-development-workflow.md new file mode 100644 index 000000000000..28e5d811cca8 --- /dev/null +++ b/data/reusables/copilot/integrating-ai-models-into-your-development-workflow.md @@ -0,0 +1,64 @@ +--- +title: Integrating AI models into your development workflow +intro: 'Call AI models in the tools you use every day.' +versions: + feature: github-models +shortTitle: Integrate AI models +--- + +With {% data variables.product.prodname_github_models %} extensions, you can call specific AI models from both {% data variables.product.prodname_copilot_chat_short %} and {% data variables.product.prodname_cli %}. These extensions integrate directly into your development workflow, allowing you to prompt models without context switching. + +## Using AI models in {% data variables.product.prodname_copilot_chat_short %} + +If you have a {% data variables.product.prodname_copilot_short %} subscription, you can work with AI models in {% data variables.product.prodname_copilot_chat_short %} in two different ways: +* Using the {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %}. With this extension, you can ask for model recommendations based on certain criteria and chat with specific models. See [Using the {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %}](#using-the-github-models-copilot-extension). +* Using multiple model support in {% data variables.product.prodname_copilot_chat_short %}. With multi-model {% data variables.product.prodname_copilot_chat_short %}, you can choose a specific model to use for a conversation, then prompt {% data variables.product.prodname_copilot_chat_short %} as usual. See [AUTOTITLE](/copilot/using-github-copilot/ai-models/changing-the-ai-model-for-copilot-chat). + +### Using the {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %} + +> [!NOTE] The {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %} is in {% data variables.release-phases.public_preview %} and is subject to change. + +1. Install the [{% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %}](https://github.com/marketplace/models-github). + * If you have a {% data variables.product.prodname_copilot_pro_short %} subscription, you can install the extension on your personal account. + * If you have access to {% data variables.product.prodname_copilot_short %} through a {% data variables.product.prodname_copilot_business_short %} or {% data variables.product.prodname_copilot_enterprise_short %} subscription: + * An organization owner or enterprise owner needs to enable the {% data variables.product.prodname_copilot_extensions_short %} policy for your organization or enterprise. + * An organization owner needs to install the extension for your organization. + +1. Open any implementation of {% data variables.product.prodname_copilot_chat_short %} that supports {% data variables.product.prodname_copilot_extensions %}. For a list of supported {% data variables.product.prodname_copilot_chat_short %} implementations, see [AUTOTITLE](/copilot/using-github-copilot/using-extensions-to-integrate-external-tools-with-copilot-chat#supported-clients-and-ides). +1. In the chat window, type `@models YOUR-PROMPT`, then send your prompt. There are several use cases for the {% data variables.product.prodname_github_models %} {% data variables.product.prodname_copilot_extension_short %}, including: + * Recommending a particular model based on context and criteria you provide. For example, you can ask for a low-cost OpenAI model that supports function calling. + * Executing prompts using a particular model. This is especially useful when you want to use a model that is not currently available in multi-model {% data variables.product.prodname_copilot_chat_short %}. + * Listing models currently available through {% data variables.product.prodname_github_models %} + +## Using AI models from the command line + +> [!NOTE] The {% data variables.product.prodname_github_models %} extension for {% data variables.product.prodname_cli %} is in {% data variables.release-phases.public_preview %} and is subject to change. + +You can use the {% data variables.product.prodname_github_models %} extension for {% data variables.product.prodname_cli %} to prompt AI models from the command line, and even pipe in the output of a command as context. + +### Prerequisites + +To use the {% data variables.product.prodname_github_models %} CLI extension, you need to have {% data variables.product.prodname_cli %} installed. {% data reusables.cli.cli-installation %} + +### Installing the extension + +1. If you have not already authenticated to the {% data variables.product.prodname_cli %}, run the following command in your terminal. + + ```shell copy + gh auth login + ``` + +1. To install the {% data variables.product.prodname_github_models %} extension, run the following command. + + ```shell copy + gh extension install https://github.com/github/gh-models + ``` + +### Using the extension + +To see a list of all available commands, run `gh models`. + +There are a few key ways you can use the extension: + * **To ask a model multiple questions using a chat experience**, run `gh models run`. Select your model from the listed models, then send your prompts. + * **To ask a model a single question**, run `gh models run MODEL-NAME "QUESTION"` in your terminal. For example, to ask the GPT 4o model why the sky is blue, you can run `gh models run GPT-4o "why is the sky blue?"`. + * **To provide the output of a command as context when you call a model**, you can join a separate command and the call to the model with the pipe character (`|`). For example, to summarize the README file in the current directory using the GPT 4o model, you can run `cat README.md | gh models run GPT-4o "summarize this text"`. diff --git a/data/reusables/copilot/model-picker-enable-o1-models.md b/data/reusables/copilot/model-picker-enable-o1-models.md index cd177ba95ea7..479c1d45a16d 100644 --- a/data/reusables/copilot/model-picker-enable-o1-models.md +++ b/data/reusables/copilot/model-picker-enable-o1-models.md @@ -1 +1 @@ -1. If you access {% data variables.product.prodname_copilot_chat_short %} through a {% data variables.product.prodname_copilot_business_short %}{% ifversion ghec %} or {% data variables.product.prodname_copilot_enterprise_short %}{% endif %} subscription, your organization{% ifversion ghec %} or enterprise{% endif %} must grant members the ability to switch to a different model. See [AUTOTITLE](/copilot/managing-copilot/managing-github-copilot-in-your-organization/setting-policies-for-copilot-in-your-organization/managing-policies-for-copilot-in-your-organization){% ifversion ghec %} or [AUTOTITLE](/copilot/managing-copilot/managing-copilot-for-your-enterprise/managing-policies-and-features-for-copilot-in-your-enterprise){% endif %}. +If you access {% data variables.product.prodname_copilot_chat_short %} through a {% data variables.product.prodname_copilot_business_short %}{% ifversion ghec %} or {% data variables.product.prodname_copilot_enterprise_short %}{% endif %} subscription, your organization{% ifversion ghec %} or enterprise{% endif %} must grant members the ability to switch to a different model. See [AUTOTITLE](/copilot/managing-copilot/managing-github-copilot-in-your-organization/setting-policies-for-copilot-in-your-organization/managing-policies-for-copilot-in-your-organization){% ifversion ghec %} or [AUTOTITLE](/copilot/managing-copilot/managing-copilot-for-your-enterprise/managing-policies-and-features-for-copilot-in-your-enterprise#copilot-access-to-alternative-ai-models){% endif %}. diff --git a/data/reusables/models/o1-models-preview-note.md b/data/reusables/models/o1-models-preview-note.md deleted file mode 100644 index 8091d2544dbc..000000000000 --- a/data/reusables/models/o1-models-preview-note.md +++ /dev/null @@ -1 +0,0 @@ -> [!NOTE] Access to OpenAI's `o1` and `o3` models is in {% data variables.release-phases.public_preview %} and subject to change. diff --git a/data/variables/copilot.yml b/data/variables/copilot.yml index 0a7c029e0444..3ea736125ed7 100644 --- a/data/variables/copilot.yml +++ b/data/variables/copilot.yml @@ -29,3 +29,4 @@ copilot_code-review_short: 'Copilot code review' ## LLM models for Copilot copilot_claude_sonnet: 'Claude 3.5 Sonnet' +copilot_gemini_flash: 'Gemini 2.0 Flash' From b099e4a9e32a0ee1f8267d4598555cd52739c1e2 Mon Sep 17 00:00:00 2001 From: Evan Bonsignori Date: Wed, 5 Feb 2025 11:46:58 -0800 Subject: [PATCH 3/4] Ai search UI (#53026) Co-authored-by: Kevin Heis Co-authored-by: Ashish Keshan --- data/ui.yml | 32 + package-lock.json | 4545 ++++++++--------- package.json | 6 +- src/events/components/event-groups.ts | 3 + src/events/components/events.ts | 18 +- .../components/experiments/experiments.ts | 8 + src/events/lib/schema.ts | 47 +- src/events/types.ts | 10 + src/fixtures/fixtures/data/ui.yml | 32 + .../tests/playwright-rendering.spec.ts | 84 + src/frame/components/context/MainContext.tsx | 1 + src/frame/components/hooks/useQueryParam.ts | 92 + .../components/page-header/Header.module.scss | 37 - src/frame/components/page-header/Header.tsx | 229 +- .../page-header/HeaderSearchAndWidgets.tsx | 149 + .../OldHeaderSearchAndWidgets.module.scss | 39 + .../page-header/OldHeaderSearchAndWidgets.tsx | 186 + .../page-header/hooks/useInnerWindowWidth.ts | 27 + src/frame/components/sidebar/SidebarNav.tsx | 2 +- .../UnrenderedMarkdownContent.tsx | 49 + src/pages/search.tsx | 2 +- src/search/components/Loading.tsx | 38 - .../helpers/ai-search-links-json.ts | 76 + .../helpers/execute-search-actions.ts | 123 + .../helpers/fix-incomplete-markdown.ts | 153 + .../hooks/useAISearchAutocomplete.ts | 152 + .../components/{ => hooks}/useBreakpoint.ts | 0 .../components/hooks/useLocalStorageCache.ts | 152 + .../components/{ => hooks}/useMediaQuery.ts | 0 .../{ => hooks}/useNumberFormatter.ts | 0 src/search/components/{ => hooks}/usePage.ts | 0 src/search/components/{ => hooks}/useQuery.ts | 2 +- .../components/input/AskAIResults.module.scss | 76 + src/search/components/input/AskAIResults.tsx | 297 ++ .../{Search.tsx => input/OldSearchInput.tsx} | 10 +- src/search/components/input/README.md | 10 + .../input/SearchBarButton.module.scss | 168 + .../components/input/SearchBarButton.tsx | 107 + .../input/SearchOverlay.module.scss | 99 + src/search/components/input/SearchOverlay.tsx | 609 +++ src/search/components/input/variables.scss | 11 + .../components/{ => results}/Aggregations.tsx | 0 .../components/{ => results}/NoQuery.tsx | 2 +- src/search/components/results/README.md | 6 + .../{ => results}/SearchResults.module.scss | 0 .../{ => results}/SearchResults.tsx | 2 + .../{ => results}/SidebarSearchAggregates.tsx | 2 +- .../{ => results}/ValidationErrors.tsx | 2 +- src/search/components/{ => results}/index.tsx | 8 +- src/search/lib/ai-search-proxy.ts | 37 +- .../ai-search-autocomplete.ts | 49 +- .../general-autocomplete.ts | 57 +- .../lib/get-elasticsearch-results/types.ts | 1 + .../lib/helpers/cse-copilot-docs-versions.ts | 6 +- .../lib/routes/combined-autocomplete-route.ts | 120 + src/search/middleware/search-routes.ts | 45 +- .../pages/{search.tsx => search-results.tsx} | 2 +- .../tests/api-ai-search-autocomplete.ts | 15 +- src/search/tests/api-combined-search.ts | 170 + src/search/tests/fix-incomplete-markdown.ts | 147 + src/search/types.ts | 8 +- .../handle-invalid-query-strings.ts | 6 +- 62 files changed, 5702 insertions(+), 2664 deletions(-) create mode 100644 src/events/components/event-groups.ts create mode 100644 src/frame/components/hooks/useQueryParam.ts create mode 100644 src/frame/components/page-header/HeaderSearchAndWidgets.tsx create mode 100644 src/frame/components/page-header/OldHeaderSearchAndWidgets.module.scss create mode 100644 src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx create mode 100644 src/frame/components/page-header/hooks/useInnerWindowWidth.ts create mode 100644 src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx delete mode 100644 src/search/components/Loading.tsx create mode 100644 src/search/components/helpers/ai-search-links-json.ts create mode 100644 src/search/components/helpers/execute-search-actions.ts create mode 100644 src/search/components/helpers/fix-incomplete-markdown.ts create mode 100644 src/search/components/hooks/useAISearchAutocomplete.ts rename src/search/components/{ => hooks}/useBreakpoint.ts (100%) create mode 100644 src/search/components/hooks/useLocalStorageCache.ts rename src/search/components/{ => hooks}/useMediaQuery.ts (100%) rename src/search/components/{ => hooks}/useNumberFormatter.ts (100%) rename src/search/components/{ => hooks}/usePage.ts (100%) rename src/search/components/{ => hooks}/useQuery.ts (91%) create mode 100644 src/search/components/input/AskAIResults.module.scss create mode 100644 src/search/components/input/AskAIResults.tsx rename src/search/components/{Search.tsx => input/OldSearchInput.tsx} (90%) create mode 100644 src/search/components/input/README.md create mode 100644 src/search/components/input/SearchBarButton.module.scss create mode 100644 src/search/components/input/SearchBarButton.tsx create mode 100644 src/search/components/input/SearchOverlay.module.scss create mode 100644 src/search/components/input/SearchOverlay.tsx create mode 100644 src/search/components/input/variables.scss rename src/search/components/{ => results}/Aggregations.tsx (100%) rename src/search/components/{ => results}/NoQuery.tsx (93%) create mode 100644 src/search/components/results/README.md rename src/search/components/{ => results}/SearchResults.module.scss (100%) rename src/search/components/{ => results}/SearchResults.tsx (97%) rename src/search/components/{ => results}/SidebarSearchAggregates.tsx (85%) rename src/search/components/{ => results}/ValidationErrors.tsx (90%) rename src/search/components/{ => results}/index.tsx (88%) create mode 100644 src/search/lib/routes/combined-autocomplete-route.ts rename src/search/pages/{search.tsx => search-results.tsx} (97%) create mode 100644 src/search/tests/api-combined-search.ts create mode 100644 src/search/tests/fix-incomplete-markdown.ts diff --git a/data/ui.yml b/data/ui.yml index 4af49d6989d2..3295fb5e1049 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -15,6 +15,7 @@ header: ghes_release_notes_upgrade_patch_and_release: 📣 This is not the latest patch release of this release series, and this is not the latest release of Enterprise Server. sign_up_cta: Sign up menu: Menu + open_menu_label: Open menu go_home: Home picker: language_picker_label: Language @@ -23,6 +24,37 @@ picker: release_notes: banner_text: GitHub began rolling these changes out to enterprises on search: + input: + experimental_tag: Experimental + aria_label: Open search overlay + placeholder: Search or ask AI assistant + overlay: + input_aria_label: Search or ask AI assistant + suggestions_list_aria_label: Search suggestions + ai_suggestions_list_aria_label: AI search suggestions + general_suggestions_list_aria_label: Docs search suggestions + general_autocomplete_list_heading: Search docs + ai_autocomplete_list_heading: Ask AI + give_feedback: Give feedback + beta_tag: Beta + return_to_search: Return to search + clear_search_query: Clear + ai: + disclaimer: This is an experimental generative AI response. All information should be verified prior to use. + references: References from these articles + loading_status_message: Loading AI response... + done_loading_status_message: Done loading AI response + unable_to_answer: Sorry, I'm unable to answer that question. Please try a different query. + copy_answer: Copy answer + copied_announcement: Copied! + thumbs_up: This answer was helpful + thumbs_down: This answer was not helpful + thumbs_announcement: Thank you for your feedback! + failure: + autocomplete_title: There was an error loading autocomplete results. + ai_title: There was an error loading the AI assistant. + description: You can still use this field to search our docs. +old_search: description: Enter a search term to find it in the GitHub Docs. placeholder: Search GitHub Docs label: Search GitHub Docs diff --git a/package-lock.json b/package-lock.json index bac3df34a230..70a79ee3e52c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,8 @@ "@primer/behaviors": "^1.7.0", "@primer/css": "^21.3.1", "@primer/live-region-element": "^0.7.0", - "@primer/octicons": "^19.11.0", - "@primer/octicons-react": "^19.11.0", + "@primer/octicons": "^19.14.0", + "@primer/octicons-react": "^19.14.0", "@primer/react": "36.27.0", "accept-language-parser": "^1.5.0", "ajv": "^8.17.1", @@ -74,6 +74,7 @@ "quick-lru": "7.0.0", "react": "18.3.1", "react-dom": "18.3.1", + "react-markdown": "^9.0.1", "rehype-highlight": "^7.0.0", "rehype-raw": "^7.0.0", "rehype-slug": "^6.0.0", @@ -126,6 +127,7 @@ "@types/react": "18.3.3", "@types/react-dom": "^18.3.0", "@types/semver": "^7.5.8", + "@types/styled-components": "^5.1.34", "@types/tcp-port-used": "1.0.4", "@types/website-scraper": "^1.2.10", "@typescript-eslint/eslint-plugin": "^8.7.0", @@ -2724,17 +2726,19 @@ } }, "node_modules/@primer/octicons": { - "version": "19.11.0", - "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.11.0.tgz", - "integrity": "sha512-dJfi3I7zF4JyqHyHpxaUliUa//w4AmTYAo0i5KgKbz92ZQ0IizRU1rlW+eVdYk5nitAebpUX7gnKceZBDGW3XQ==", + "version": "19.14.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.14.0.tgz", + "integrity": "sha512-9Ovw/xcUFHC/zbsNhr/Hkp1+m9XnNeQvnGHDHrI5vhlf6PRZVzSsdMnesV2xCzQh7jXP3EVRcaeXsUGlsZrfcA==", + "license": "MIT", "dependencies": { "object-assign": "^4.1.1" } }, "node_modules/@primer/octicons-react": { - "version": "19.11.0", - "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.11.0.tgz", - "integrity": "sha512-8PpCz7cpYY2GCGnJ/G9UARh9PH4C290l31YjriQHZ+WsagE37ntKXhFwq+s4OWoRqZ7fA7HeU81zGDFHQi0VKg==", + "version": "19.14.0", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.14.0.tgz", + "integrity": "sha512-EKeavGV7s2HYac3ybb+6vfyqHGMUeG+OlZAus5ORfEjzXlorDAIjZ59fszVJj9DI6ArfFK/Cvg8V4JRfeUHjcw==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -2817,149 +2821,857 @@ "resolved": "https://registry.npmjs.org/@oddbird/popover-polyfill/-/popover-polyfill-0.3.7.tgz", "integrity": "sha512-WNthEIPPXnFQkumLby6yVxhyOcA/GtMnlByHwEglMO9WZckoaqidnpLp2JFzAh2RDOZxn+Xt3ffSMKId9cPjOQ==" }, - "node_modules/@primer/view-components": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@primer/view-components/-/view-components-0.22.0.tgz", - "integrity": "sha512-38S4GgybG9iviiULHLbWxLn4G8DDOE4E9XgQARmanGyju+GBZjcOS5AKglVT+JziQPq6o3dgcwWFl+FxKZDCnA==", + "node_modules/@primer/react/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", "dependencies": { - "@github/auto-check-element": "^5.2.0", - "@github/auto-complete-element": "^3.6.2", - "@github/catalyst": "^1.6.0", - "@github/clipboard-copy-element": "^1.3.0", - "@github/details-menu-element": "^1.0.12", - "@github/image-crop-element": "^5.0.0", - "@github/include-fragment-element": "^6.1.1", - "@github/relative-time-element": "^4.0.0", - "@github/tab-container-element": "^3.1.2", - "@oddbird/popover-polyfill": "^0.4.0", - "@primer/behaviors": "^1.3.4" + "@types/unist": "^2" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz", - "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@primer/react/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@types/unist": "^2" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz", - "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@primer/react/node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz", - "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@primer/react/node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "license": "MIT" }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz", - "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@primer/react/node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz", - "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@primer/react/node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz", - "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@primer/react/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz", - "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==", - "cpu": [ - "arm" + "node_modules/@primer/react/node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz", - "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==", - "cpu": [ - "arm" + "node_modules/@primer/react/node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz", - "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==", - "cpu": [ - "arm64" + "node_modules/@primer/react/node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/@primer/react/node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/@primer/react/node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/@primer/react/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/@primer/react/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/@primer/react/node_modules/react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@primer/react/node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/@primer/react/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/react/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@primer/view-components": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@primer/view-components/-/view-components-0.22.0.tgz", + "integrity": "sha512-38S4GgybG9iviiULHLbWxLn4G8DDOE4E9XgQARmanGyju+GBZjcOS5AKglVT+JziQPq6o3dgcwWFl+FxKZDCnA==", + "dependencies": { + "@github/auto-check-element": "^5.2.0", + "@github/auto-complete-element": "^3.6.2", + "@github/catalyst": "^1.6.0", + "@github/clipboard-copy-element": "^1.3.0", + "@github/details-menu-element": "^1.0.12", + "@github/image-crop-element": "^5.0.0", + "@github/include-fragment-element": "^6.1.1", + "@github/relative-time-element": "^4.0.0", + "@github/tab-container-element": "^3.1.2", + "@oddbird/popover-polyfill": "^0.4.0", + "@primer/behaviors": "^1.3.4" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz", + "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz", + "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz", + "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz", + "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz", + "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz", + "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz", + "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz", + "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz", + "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.31.0", @@ -3406,9 +4118,17 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/event-to-promise": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@types/event-to-promise/-/event-to-promise-0.7.5.tgz", @@ -3443,7 +4163,9 @@ } }, "node_modules/@types/hast": { - "version": "2.3.4", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -3451,9 +4173,8 @@ }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -3654,10 +4375,11 @@ } }, "node_modules/@types/styled-components": { - "version": "5.1.11", + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "@types/hoist-non-react-statics": "*", "@types/react": "*", @@ -4755,6 +5477,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -5528,9 +6260,10 @@ } }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -6783,6 +7516,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -7866,14 +8609,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-from-parse5/node_modules/@types/hast": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.0.tgz", - "integrity": "sha512-SoytUJRuf68HXYqcXicQIhCrLQjqeYU2anikr4G3p3Iz+OZO5QDQpDj++gv+RenHsnUBwNZ2dumBArF8VLSk2Q==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-from-parse5/node_modules/@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", @@ -7895,45 +8630,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-from-parse5/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5/node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5/node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/hast-util-heading-rank": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", @@ -7946,14 +8642,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-heading-rank/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-is-element": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", @@ -7966,14 +8654,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-is-element/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -7986,14 +8666,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-parse-selector/node_modules/@types/hast": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.0.tgz", - "integrity": "sha512-SoytUJRuf68HXYqcXicQIhCrLQjqeYU2anikr4G3p3Iz+OZO5QDQpDj++gv+RenHsnUBwNZ2dumBArF8VLSk2Q==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-raw": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.1.tgz", @@ -8018,58 +8690,11 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-raw/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-raw/node_modules/@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" }, - "node_modules/hast-util-raw/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw/node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw/node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/hast-util-to-html": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.0.tgz", @@ -8093,31 +8718,44 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-html/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-to-html/node_modules/@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" }, - "node_modules/hast-util-to-html/node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", @@ -8136,14 +8774,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-parse5/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-to-string": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", @@ -8156,14 +8786,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-string/node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-to-text": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.0.tgz", @@ -8179,22 +8801,19 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-text/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/hast-util-to-text/node_modules/@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" }, "node_modules/hast-util-whitespace": { - "version": "2.0.0", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -8300,6 +8919,16 @@ } ] }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -8554,7 +9183,9 @@ "license": "ISC" }, "node_modules/inline-style-parser": { - "version": "0.1.1", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, "node_modules/internal-slot": { @@ -8585,6 +9216,30 @@ "node": ">= 0.10" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -8673,6 +9328,8 @@ }, "node_modules/is-buffer": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", "funding": [ { "type": "github", @@ -8760,6 +9417,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extendable": { "version": "0.1.1", "license": "MIT", @@ -8818,6 +9485,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -9273,6 +9950,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -9831,14 +10509,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/lowlight/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/lru-cache": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", @@ -10005,6 +10675,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "license": "MIT", "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", @@ -10016,9 +10687,10 @@ } }, "node_modules/mdast-util-definitions/node_modules/@types/mdast": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", - "integrity": "sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", "dependencies": { "@types/unist": "^2" } @@ -10027,6 +10699,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", @@ -10041,6 +10714,7 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" @@ -10110,18 +10784,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" }, - "node_modules/mdast-util-from-markdown/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-gfm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", @@ -10186,27 +10848,93 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "license": "MIT", "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", @@ -10267,58 +10995,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-to-hast/node_modules/@types/hast": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.0.tgz", - "integrity": "sha512-SoytUJRuf68HXYqcXicQIhCrLQjqeYU2anikr4G3p3Iz+OZO5QDQpDj++gv+RenHsnUBwNZ2dumBArF8VLSk2Q==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/mdast-util-to-hast/node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" - }, - "node_modules/mdast-util-to-hast/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast/node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast/node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-to-markdown": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", @@ -10921,547 +11597,288 @@ }, "node_modules/micromark-util-types": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", - "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-response": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mockdate": { - "version": "3.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/morgan": { - "version": "1.10.0", - "license": "MIT", - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + ] + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=8.6" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/negotiator": { - "version": "0.6.3", + "node_modules/mime-db": { + "version": "1.52.0", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/next": { - "version": "15.1.5", - "resolved": "https://registry.npmjs.org/next/-/next-15.1.5.tgz", - "integrity": "sha512-Cf/TEegnt01hn3Hoywh6N8fvkhbOuChO4wFje24+a86wKOubgVaWkDqxGVgoWlz2Hp9luMJ9zw3epftujdnUOg==", + "node_modules/mime-types": { + "version": "2.1.35", "license": "MIT", "dependencies": { - "@next/env": "15.1.5", - "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.15", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" + "mime-db": "1.52.0" }, "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "15.1.5", - "@next/swc-darwin-x64": "15.1.5", - "@next/swc-linux-arm64-gnu": "15.1.5", - "@next/swc-linux-arm64-musl": "15.1.5", - "@next/swc-linux-x64-gnu": "15.1.5", - "@next/swc-linux-x64-musl": "15.1.5", - "@next/swc-win32-arm64-msvc": "15.1.5", - "@next/swc-win32-x64-msvc": "15.1.5", - "sharp": "^0.33.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/next/node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "node_modules/mimic-fn": { + "version": "2.1.0", "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "engines": { + "node": ">=6" } }, - "node_modules/next/node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/next/node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/mimic-response": { + "version": "4.0.0", + "license": "MIT", "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/next/node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/next/node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/next/node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/next/node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], "funding": { - "url": "https://opencollective.com/libvips" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/next/node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, "funding": { - "url": "https://opencollective.com/libvips" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/next/node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", "funding": { - "url": "https://opencollective.com/libvips" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/next/node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "node_modules/next/node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/mkdirp": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/libvips" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mockdate": { + "version": "3.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/morgan": { + "version": "1.10.0", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/next/node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" + "node": ">=4" } }, - "node_modules/next/node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/next/node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 0.6" + } + }, + "node_modules/next": { + "version": "15.1.5", + "resolved": "https://registry.npmjs.org/next/-/next-15.1.5.tgz", + "integrity": "sha512-Cf/TEegnt01hn3Hoywh6N8fvkhbOuChO4wFje24+a86wKOubgVaWkDqxGVgoWlz2Hp9luMJ9zw3epftujdnUOg==", + "license": "MIT", + "dependencies": { + "@next/env": "15.1.5", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" }, - "funding": { - "url": "https://opencollective.com/libvips" + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" + "@next/swc-darwin-arm64": "15.1.5", + "@next/swc-darwin-x64": "15.1.5", + "@next/swc-linux-arm64-gnu": "15.1.5", + "@next/swc-linux-arm64-musl": "15.1.5", + "@next/swc-linux-x64-gnu": "15.1.5", + "@next/swc-linux-x64-musl": "15.1.5", + "@next/swc-win32-arm64-msvc": "15.1.5", + "@next/swc-win32-x64-msvc": "15.1.5", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } } }, - "node_modules/next/node_modules/@img/sharp-linuxmusl-arm64": { + "node_modules/next/node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/next/node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", "cpu": [ "arm64" ], "license": "Apache-2.0", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -11470,20 +11887,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + "@img/sharp-libvips-darwin-arm64": "1.0.4" } }, - "node_modules/next/node_modules/@img/sharp-linuxmusl-x64": { + "node_modules/next/node_modules/@img/sharp-darwin-x64": { "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", "cpu": [ "x64" ], "license": "Apache-2.0", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -11492,1596 +11909,1298 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + "@img/sharp-libvips-darwin-x64": "1.0.4" } }, - "node_modules/next/node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "node_modules/next/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", "cpu": [ - "wasm32" + "arm64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "LGPL-3.0-or-later", "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, + "os": [ + "darwin" + ], "funding": { "url": "https://opencollective.com/libvips" } }, - "node_modules/next/node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "node_modules/next/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", "cpu": [ - "ia32" + "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "darwin" ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, - "node_modules/next/node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "node_modules/next/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", "cpu": [ - "x64" + "arm" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "linux" ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, "funding": { "url": "https://opencollective.com/libvips" } }, - "node_modules/next/node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, - "license": "Apache-2.0", + "node_modules/next/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, + "os": [ + "linux" + ], "funding": { "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" } }, - "node_modules/nock": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0.tgz", - "integrity": "sha512-3Z2ZoZoYTR/y2I+NI16+6IzfZFKBX7MrADtoBAm7v/QKqxQUhKw+Dh+847PPS1j/FDutjfIXfrh3CJF74yITWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mswjs/interceptors": "^0.37.3", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" - }, - "node_modules/nodemon": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz", - "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, + "node_modules/next/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" + "url": "https://opencollective.com/libvips" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node_modules/next/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "engines": { - "node": ">=14.16" - }, + "node_modules/next/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" } }, - "node_modules/npm-merge-driver-install": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "bin": { - "npm-merge-driver-install": "src/install.js", - "npm-merge-driver-is-installed": "src/is-installed.js", - "npm-merge-driver-merge": "src/merge.js", - "npm-merge-driver-uninstall": "src/uninstall.js" + "node_modules/next/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, + "node_modules/next/node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "license": "MIT", + "node_modules/next/node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" } }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, + "node_modules/next/node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", + "node_modules/next/node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" } }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, + "node_modules/next/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" } }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, + "node_modules/next/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">= 0.4" + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" } }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dev": true, - "license": "MIT", + "node_modules/next/node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "@emnapi/runtime": "^1.2.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/libvips" } }, - "node_modules/on-finished": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, + "node_modules/next/node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "license": "MIT", + "node_modules/next/node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/onetime": { - "version": "5.1.2", - "license": "MIT", + "node_modules/next/node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "mimic-fn": "^2.1.0" + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" }, "engines": { - "node": ">=6" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">= 0.8.0" + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" } }, - "node_modules/ora": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz", - "integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==", + "node_modules/nock": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.0.tgz", + "integrity": "sha512-3Z2ZoZoYTR/y2I+NI16+6IzfZFKBX7MrADtoBAm7v/QKqxQUhKw+Dh+847PPS1j/FDutjfIXfrh3CJF74yITWg==", + "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "@mswjs/interceptors": "^0.37.3", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, - "node_modules/ora/node_modules/string-width": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", - "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", + "node_modules/nodemon": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz", + "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==", + "dev": true, "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/p-cancelable": { + "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=12.20" + "node": ">=4" } }, - "node_modules/p-limit": { - "version": "3.1.0", + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "yocto-queue": "^0.1.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, - "node_modules/p-queue": { - "version": "7.3.4", + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", "dev": true, "license": "MIT", "dependencies": { - "eventemitter3": "^4.0.7", - "p-timeout": "^5.0.2" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/p-timeout": { - "version": "5.1.0", + "node_modules/nopt": { + "version": "1.0.10", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "abbrev": "1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "nopt": "bin/nopt.js" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "dev": true, + "node_modules/normalize-path": { + "version": "3.0.0", + "devOptional": true, "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/parse5": { - "version": "7.1.2", - "license": "MIT", - "dependencies": { - "entities": "^4.4.0" + "node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "engines": { + "node": ">=14.16" }, "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", + "node_modules/npm-merge-driver-install": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "npm-merge-driver-install": "src/install.js", + "npm-merge-driver-is-installed": "src/is-installed.js", + "npm-merge-driver-merge": "src/merge.js", + "npm-merge-driver-uninstall": "src/uninstall.js" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, "license": "MIT", "dependencies": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/parse5/node_modules/entities": { - "version": "4.4.0", + "node_modules/nth-check": { + "version": "2.1.1", "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" + "dependencies": { + "boolbase": "^1.0.0" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", + "node_modules/object-keys": { + "version": "1.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/path-key": { - "version": "3.1.1", - "license": "MIT", + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-parse": { - "version": "1.0.7", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "20 || >=22" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">= 14.16" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "dev": true, - "license": [ - "MIT", - "Apache2" - ], + "node_modules/on-finished": { + "version": "2.3.0", + "license": "MIT", "dependencies": { - "through": "~2.3" + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/peek-readable": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", - "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==", + "node_modules/on-headers": { + "version": "1.0.2", "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "node": ">= 0.8" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } }, - "node_modules/picomatch": { - "version": "2.3.1", + "node_modules/onetime": { + "version": "5.1.2", "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, "engines": { - "node": ">=8.6" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pidtree": { - "version": "0.6.0", + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" }, "engines": { - "node": ">=0.10" + "node": ">= 0.8.0" } }, - "node_modules/playwright": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz", - "integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==", - "devOptional": true, - "license": "Apache-2.0", + "node_modules/ora": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz", + "integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==", "dependencies": { - "playwright-core": "1.50.0" - }, - "bin": { - "playwright": "cli.js" + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { "node": ">=18" }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz", - "integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", + "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-value-parser": { - "version": "4.1.0", + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, "license": "MIT" }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/p-cancelable": { + "version": "3.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=12.20" } }, - "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "node_modules/p-limit": { + "version": "3.1.0", "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=14" + "node": ">=10" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "node_modules/p-queue": { + "version": "7.3.4", "dev": true, + "license": "MIT", "dependencies": { - "fast-diff": "^1.1.2" + "eventemitter3": "^4.0.7", + "p-timeout": "^5.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prop-types": { - "version": "15.7.2", + "node_modules/p-timeout": { + "version": "5.1.0", + "dev": true, "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, - "node_modules/propagate": { - "version": "2.0.1", + "node_modules/parent-module": { + "version": "1.0.1", "dev": true, "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { - "node": ">= 8" + "node": ">=6" } }, - "node_modules/property-information": { - "version": "6.0.1", + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", + "node_modules/parse5": { + "version": "7.1.2", "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "entities": "^4.4.0" }, - "engines": { - "node": ">= 0.10" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/ps-tree": { - "version": "1.2.0", - "dev": true, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", "license": "MIT", "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "dev": true, - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, + "node_modules/parse5/node_modules/entities": { + "version": "4.4.0", + "license": "BSD-2-Clause", "engines": { - "node": ">=0.6" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz", - "integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, - "peerDependencies": { - "react": "^18.3.1" + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/react-intersection-observer": { - "version": "9.4.3", + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "license": "MIT", - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" }, - "node_modules/react-markdown": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", - "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/prop-types": "^15.0.0", - "@types/unist": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^2.0.0", - "prop-types": "^15.0.0", - "property-information": "^6.0.0", - "react-is": "^18.0.0", - "remark-parse": "^10.0.0", - "remark-rehype": "^10.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.4.0", - "unified": "^10.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" } }, - "node_modules/react-markdown/node_modules/@types/mdast": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", - "integrity": "sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==", + "node_modules/pause-stream": { + "version": "0.0.11", + "dev": true, + "license": [ + "MIT", + "Apache2" + ], "dependencies": { - "@types/unist": "^2" + "through": "~2.3" } }, - "node_modules/react-markdown/node_modules/mdast-util-from-markdown": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" + "node_modules/peek-readable": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", + "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==", + "license": "MIT", + "engines": { + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/react-markdown/node_modules/mdast-util-to-hast": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", - "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-definitions": "^5.0.0", - "micromark-util-sanitize-uri": "^1.1.0", - "trim-lines": "^3.0.0", - "unist-util-generated": "^2.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0" + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/react-markdown/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", - "dependencies": { - "@types/mdast": "^3.0.0" + "node_modules/pidtree": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10" } }, - "node_modules/react-markdown/node_modules/micromark": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/playwright": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz", + "integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" + "playwright-core": "1.50.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" } }, - "node_modules/react-markdown/node_modules/micromark-core-commonmark": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" + "node_modules/playwright-core": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz", + "integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" } }, - "node_modules/react-markdown/node_modules/micromark-factory-destination": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" } }, - "node_modules/react-markdown/node_modules/micromark-factory-label": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/react-markdown/node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/react-markdown/node_modules/micromark-factory-title": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } + "node_modules/postcss-value-parser": { + "version": "4.1.0", + "license": "MIT" }, - "node_modules/react-markdown/node_modules/micromark-factory-whitespace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/react-markdown/node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/react-markdown/node_modules/micromark-util-chunked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, "dependencies": { - "micromark-util-symbol": "^1.0.0" + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/react-markdown/node_modules/micromark-util-classify-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "node_modules/react-markdown/node_modules/micromark-util-combine-extensions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/propagate": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" } }, - "node_modules/react-markdown/node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", - "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^1.0.0" + "node_modules/property-information": { + "version": "6.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/react-markdown/node_modules/micromark-util-decode-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", - "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/react-markdown/node_modules/micromark-util-encode": { + "node_modules/proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", - "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, - "node_modules/react-markdown/node_modules/micromark-util-html-tag-name": { + "node_modules/ps-tree": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", - "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/react-markdown/node_modules/micromark-util-normalize-identifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", - "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "dev": true, + "license": "MIT", "dependencies": { - "micromark-util-symbol": "^1.0.0" + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/react-markdown/node_modules/micromark-util-resolve-all": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", - "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-types": "^1.0.0" + "node_modules/pstree.remy": { + "version": "1.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" } }, - "node_modules/react-markdown/node_modules/micromark-util-sanitize-uri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", - "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" } }, - "node_modules/react-markdown/node_modules/micromark-util-subtokenize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", - "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react-markdown/node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, "funding": [ { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "github", + "url": "https://github.com/sponsors/feross" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/react-markdown/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "patreon", + "url": "https://www.patreon.com/feross" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "type": "consulting", + "url": "https://feross.org/support" } - ] - }, - "node_modules/react-markdown/node_modules/remark-parse": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", - "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + ], + "license": "MIT" }, - "node_modules/react-markdown/node_modules/remark-rehype": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", - "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-to-hast": "^12.1.0", - "unified": "^10.0.0" + "node_modules/quick-lru": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz", + "integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==", + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-markdown/node_modules/style-to-object": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.2.tgz", - "integrity": "sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==", - "dependencies": { - "inline-style-parser": "0.1.1" + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" } }, - "node_modules/react-markdown/node_modules/unified": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", - "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { - "@types/unist": "^2.0.0", - "bail": "^2.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^5.0.0" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 0.8" } }, - "node_modules/react-markdown/node_modules/unist-util-position": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", - "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { - "@types/unist": "^2.0.0" + "loose-envify": "^1.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/react-markdown/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "peerDependencies": { + "react": "^18.3.1" } }, - "node_modules/react-markdown/node_modules/unist-util-visit-parents": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "node_modules/react-intersection-observer": { + "version": "9.4.3", + "license": "MIT", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "license": "MIT", "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" } }, "node_modules/readdirp": { @@ -13156,58 +13275,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-highlight/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/rehype-highlight/node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" - }, - "node_modules/rehype-highlight/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-highlight/node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-highlight/node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", @@ -13222,58 +13289,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-raw/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/rehype-raw/node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" - }, - "node_modules/rehype-raw/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-raw/node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-raw/node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/rehype-slug": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", @@ -13290,14 +13305,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-slug/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/rehype-stringify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz", @@ -13312,14 +13319,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-stringify/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/remark-gemoji-to-emoji": { "version": "1.1.0", "license": "MIT", @@ -13394,58 +13393,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remark-rehype/node_modules/@types/hast": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.1.tgz", - "integrity": "sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/remark-rehype/node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" - }, - "node_modules/remark-rehype/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype/node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype/node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-remove-comments": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/remark-remove-comments/-/remark-remove-comments-1.0.1.tgz", @@ -13696,6 +13643,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", "dependencies": { "mri": "^1.1.0" }, @@ -14457,6 +14405,15 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/styled-components": { "version": "5.3.5", "hasInstallScript": true, @@ -15146,6 +15103,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -15211,16 +15169,24 @@ } }, "node_modules/unist-util-stringify-position": { - "version": "3.0.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { - "@types/unist": "^2.0.0" + "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-stringify-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/unist-util-visit": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", @@ -15384,6 +15350,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "license": "MIT", "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", @@ -15451,13 +15418,13 @@ } }, "node_modules/vfile": { - "version": "5.1.1", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", @@ -15477,41 +15444,11 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-location/node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location/node_modules/unist-util-stringify-position/node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" - }, - "node_modules/vfile-location/node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location/node_modules/vfile-message": { + "node_modules/vfile-message": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" @@ -15521,27 +15458,17 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-location/node_modules/vfile-message/node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" - }, - "node_modules/vfile-location/node_modules/vfile/node_modules/@types/unist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", - "integrity": "sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==" + "node_modules/vfile-message/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" }, - "node_modules/vfile-message": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/vfile/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" }, "node_modules/vite": { "version": "6.0.11", diff --git a/package.json b/package.json index 91e3f1c81bfe..17063980f5ed 100644 --- a/package.json +++ b/package.json @@ -246,8 +246,8 @@ "@primer/behaviors": "^1.7.0", "@primer/css": "^21.3.1", "@primer/live-region-element": "^0.7.0", - "@primer/octicons": "^19.11.0", - "@primer/octicons-react": "^19.11.0", + "@primer/octicons": "^19.14.0", + "@primer/octicons-react": "^19.14.0", "@primer/react": "36.27.0", "accept-language-parser": "^1.5.0", "ajv": "^8.17.1", @@ -304,6 +304,7 @@ "quick-lru": "7.0.0", "react": "18.3.1", "react-dom": "18.3.1", + "react-markdown": "^9.0.1", "rehype-highlight": "^7.0.0", "rehype-raw": "^7.0.0", "rehype-slug": "^6.0.0", @@ -356,6 +357,7 @@ "@types/react": "18.3.3", "@types/react-dom": "^18.3.0", "@types/semver": "^7.5.8", + "@types/styled-components": "^5.1.34", "@types/tcp-port-used": "1.0.4", "@types/website-scraper": "^1.2.10", "@typescript-eslint/eslint-plugin": "^8.7.0", diff --git a/src/events/components/event-groups.ts b/src/events/components/event-groups.ts new file mode 100644 index 000000000000..fb3d9b5a522c --- /dev/null +++ b/src/events/components/event-groups.ts @@ -0,0 +1,3 @@ +export const ASK_AI_EVENT_GROUP = 'ask-ai' +export const SEARCH_OVERLAY_EVENT_GROUP = 'search-overlay' +export const GENERAL_SEARCH_RESULTS = 'general-search-results' diff --git a/src/events/components/events.ts b/src/events/components/events.ts index 39996f6cd828..5059fd48c138 100644 --- a/src/events/components/events.ts +++ b/src/events/components/events.ts @@ -36,7 +36,7 @@ function resetPageParams() { // Temporary polyfill for crypto.randomUUID() // Necessary for localhost development (doesn't have https://) -function uuidv4(): string { +export function uuidv4(): string { try { return crypto.randomUUID() } catch { @@ -64,10 +64,14 @@ function getMetaContent(name: string) { export function sendEvent({ type, version = '1.0.0', + eventGroupKey, + eventGroupId, ...props }: { type: T version?: string + eventGroupKey?: string + eventGroupId?: string } & EventPropsByType[T]) { const body = { type, @@ -113,6 +117,10 @@ export function sendEvent({ code_display_preference: Cookies.get('annotate-mode'), experiment_variation: getExperimentVariationForContext(getMetaContent('path-language')), + + // Event grouping + event_group_key: eventGroupKey, + event_group_id: eventGroupId, }, ...props, @@ -295,6 +303,7 @@ function initCopyButtonEvent() { const target = evt.target as HTMLElement const button = target.closest('.js-btn-copy') as HTMLButtonElement if (!button) return + sendEvent({ type: EventType.clipboard, clipboard_operation: 'copy', @@ -310,12 +319,19 @@ function initLinkEvent() { if (!link) return const sameSite = link.origin === location.origin const container = target.closest(`[data-container]`) as HTMLElement | null + + // We can attach `data-group-key` and `data-group-id` to any anchor element to include them in the event + const eventGroupKey = link?.dataset?.groupKey || undefined + const eventGroupId = link?.dataset?.groupId || undefined + sendEvent({ type: EventType.link, link_url: link.href, link_samesite: sameSite, link_samepage: sameSite && link.pathname === location.pathname, link_container: container?.dataset.container, + eventGroupKey, + eventGroupId, }) }) diff --git a/src/events/components/experiments/experiments.ts b/src/events/components/experiments/experiments.ts index 48498b2e6d41..b5b1695c20f8 100644 --- a/src/events/components/experiments/experiments.ts +++ b/src/events/components/experiments/experiments.ts @@ -16,6 +16,14 @@ type Experiment = { export type ExperimentNames = 'ai_search_experiment' export const EXPERIMENTS = { + ai_search_experiment: { + key: 'ai_search_experiment', + isActive: true, // Set to false when the experiment is over + percentOfUsersToGetExperiment: 0, // 10% of users will get the experiment + includeVariationInContext: true, // All events will include the `experiment_variation` of the `ai_search_experiment` + limitToLanguages: ['en'], // Only users with the `en` language will be included in the experiment + alwaysShowForStaff: true, // When set to true, staff will always see the experiment (determined by the `staffonly` cookie) + }, /* Add new experiments here, example: 'example_experiment': { key: 'example_experiment', diff --git a/src/events/lib/schema.ts b/src/events/lib/schema.ts index 5771fc456879..d1bedfd8bbea 100644 --- a/src/events/lib/schema.ts +++ b/src/events/lib/schema.ts @@ -182,6 +182,17 @@ const context = { type: 'string', description: 'The variation this user we bucketed in is in, such as control or treatment.', }, + + // Event Grouping. The comination of key + id should be unique + event_group_key: { + type: 'string', + description: 'A enum indentifier (e.g. "ask-ai") used to put events into a specific group.', + }, + event_group_id: { + type: 'string', + description: + 'A unique id (uuid) that can be used to identify a group of events made by a user during the same session.', + }, }, } @@ -380,6 +391,38 @@ const searchResult = { }, } +const aiSearchResult = { + type: 'object', + additionalProperties: false, + required: [ + 'type', + 'context', + 'ai_search_result_query', + 'ai_search_result_response', + 'ai_search_result_links_json', + ], + properties: { + context, + type: { + type: 'string', + pattern: '^aiSearchResult$', + }, + ai_search_result_query: { + type: 'string', + description: 'The query the user searched for.', + }, + ai_search_result_response: { + type: 'number', + description: "The GPT's response to the query.", + }, + ai_search_result_links_json: { + type: 'number', + description: + 'Dynamic JSON string of an array of "link" objects in the form: [{ "type": "reference" | "inline", "url": "https://..", "product": "issues" | "pages" | ... }, ...]', + }, + }, +} + const survey = { type: 'object', additionalProperties: false, @@ -432,7 +475,7 @@ const experiment = { }, experiment_variation: { type: 'string', - description: 'The variation this user we bucketed in, such as control or treatment.', + description: 'The variation this user we bucketed in is in, such as control or treatment.', }, experiment_success: { type: 'boolean', @@ -545,6 +588,7 @@ export const schemas = { hover, search, searchResult, + aiSearchResult, survey, experiment, clipboard, @@ -560,6 +604,7 @@ export const hydroNames = { hover: 'docs.v0.HoverEvent', search: 'docs.v0.SearchEvent', searchResult: 'docs.v0.SearchResultEvent', + aiSearchResult: 'docs.v0.AISearchResultsEvent', survey: 'docs.v0.SurveyEvent', experiment: 'docs.v0.ExperimentEvent', clipboard: 'docs.v0.ClipboardEvent', diff --git a/src/events/types.ts b/src/events/types.ts index 081373022668..bbb1863a79ce 100644 --- a/src/events/types.ts +++ b/src/events/types.ts @@ -1,4 +1,5 @@ export enum EventType { + aiSearchResult = 'aiSearchResult', page = 'page', exit = 'exit', link = 'link', @@ -46,10 +47,19 @@ export type EventProps = { color_mode_preference: string os_preference: string code_display_preference: string + event_group_key?: string + event_group_id?: string } } export type EventPropsByType = { + [EventType.aiSearchResult]: { + ai_search_result_query: string + ai_search_result_response: string + // Dynamic JSON string of an array of "link" objects in the form: + // [{ "type": "reference" | "inline", "url": "https://..", "product": "issues" | "pages" | ... }, ...] + ai_search_result_links_json: string + } [EventType.clipboard]: { clipboard_operation: string clipboard_target?: string diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml index 4af49d6989d2..3295fb5e1049 100644 --- a/src/fixtures/fixtures/data/ui.yml +++ b/src/fixtures/fixtures/data/ui.yml @@ -15,6 +15,7 @@ header: ghes_release_notes_upgrade_patch_and_release: 📣 This is not the latest patch release of this release series, and this is not the latest release of Enterprise Server. sign_up_cta: Sign up menu: Menu + open_menu_label: Open menu go_home: Home picker: language_picker_label: Language @@ -23,6 +24,37 @@ picker: release_notes: banner_text: GitHub began rolling these changes out to enterprises on search: + input: + experimental_tag: Experimental + aria_label: Open search overlay + placeholder: Search or ask AI assistant + overlay: + input_aria_label: Search or ask AI assistant + suggestions_list_aria_label: Search suggestions + ai_suggestions_list_aria_label: AI search suggestions + general_suggestions_list_aria_label: Docs search suggestions + general_autocomplete_list_heading: Search docs + ai_autocomplete_list_heading: Ask AI + give_feedback: Give feedback + beta_tag: Beta + return_to_search: Return to search + clear_search_query: Clear + ai: + disclaimer: This is an experimental generative AI response. All information should be verified prior to use. + references: References from these articles + loading_status_message: Loading AI response... + done_loading_status_message: Done loading AI response + unable_to_answer: Sorry, I'm unable to answer that question. Please try a different query. + copy_answer: Copy answer + copied_announcement: Copied! + thumbs_up: This answer was helpful + thumbs_down: This answer was not helpful + thumbs_announcement: Thank you for your feedback! + failure: + autocomplete_title: There was an error loading autocomplete results. + ai_title: There was an error loading the AI assistant. + description: You can still use this field to search our docs. +old_search: description: Enter a search term to find it in the GitHub Docs. placeholder: Search GitHub Docs label: Search GitHub Docs diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index 37f834c1c749..64d14387c867 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -68,6 +68,90 @@ test('do a search from home page and click on "Foo" page', async ({ page }) => { await expect(page).toHaveTitle(/For Playwright/) }) +test('open new search, and perform a general search', async ({ page }) => { + test.skip(!SEARCH_TESTS, 'No local Elasticsearch, no tests involving search') + + await page.goto('/') + + // Enable the AI search experiment by overriding the control group + await page.evaluate(() => { + // @ts-expect-error overrideControlGroup is a custom function added to the window object + window.overrideControlGroup('ai_search_experiment', 'treatment') + }) + + await page.getByTestId('search').click() + + await page.getByTestId('overlay-search-input').fill('serve playwright') + // Let new suggestions load + await page.waitForTimeout(1000) + // Navigate to general search item, "serve playwright" + await page.keyboard.press('ArrowDown') + // Select the general search item, "serve playwright" + await page.keyboard.press('Enter') + + await expect(page).toHaveURL(/\/search\?query=serve\+playwright/) + await expect(page).toHaveTitle(/\d Search results for "serve playwright"/) + + await page.getByRole('link', { name: 'For Playwright' }).click() + + await expect(page).toHaveURL(/\/get-started\/foo\/for-playwright$/) + await expect(page).toHaveTitle(/For Playwright/) +}) + +test('open new search, and get auto-complete results', async ({ page }) => { + test.skip(!SEARCH_TESTS, 'No local Elasticsearch, no tests involving search') + + await page.goto('/') + + // Enable the AI search experiment by overriding the control group + await page.evaluate(() => { + // @ts-expect-error overrideControlGroup is a custom function added to the window object + window.overrideControlGroup('ai_search_experiment', 'treatment') + }) + + await page.getByTestId('search').click() + + let listGroup = page.getByTestId('ai-autocomplete-suggestions') + + await expect(listGroup).toBeVisible() + let listItems = listGroup.locator('li') + await expect(listItems).toHaveCount(5) + + // Top 5 queries from queries.json fixture's 'topQueries' + let expectedTexts = [ + 'What is GitHub and how do I get started?', + 'What is GitHub Copilot and how do I get started?', + 'How do I connect to GitHub with SSH?', + 'How do I generate a personal access token?', + 'How do I clone a repository?', + ] + for (let i = 0; i < expectedTexts.length; i++) { + await expect(listItems.nth(i)).toHaveText(expectedTexts[i]) + } + + const searchInput = await page.getByTestId('overlay-search-input') + + await expect(searchInput).toBeVisible() + await expect(searchInput).toBeEnabled() + + // Type the text "rest" into the search input + await searchInput.fill('rest') + + // Ask AI suggestions + listGroup = page.getByTestId('ai-autocomplete-suggestions') + listItems = listGroup.locator('li') + await expect(listItems).toHaveCount(3) + await expect(listGroup).toBeVisible() + expectedTexts = [ + 'rest', + 'How do I manage OAuth app access restrictions for my organization?', + 'How do I test my SSH connection to GitHub?', + ] + for (let i = 0; i < expectedTexts.length; i++) { + await expect(listItems.nth(i)).toHaveText(expectedTexts[i]) + } +}) + test('search from enterprise-cloud and filter by top-level Fooing', async ({ page }) => { test.skip(!SEARCH_TESTS, 'No local Elasticsearch, no tests involving search') diff --git a/src/frame/components/context/MainContext.tsx b/src/frame/components/context/MainContext.tsx index af84301735ea..159703d82ead 100644 --- a/src/frame/components/context/MainContext.tsx +++ b/src/frame/components/context/MainContext.tsx @@ -138,6 +138,7 @@ const DEFAULT_UI_NAMESPACES = [ 'alerts', 'header', 'search', + 'old_search', 'survey', 'toc', 'meta', diff --git a/src/frame/components/hooks/useQueryParam.ts b/src/frame/components/hooks/useQueryParam.ts new file mode 100644 index 000000000000..ff43ec42acfd --- /dev/null +++ b/src/frame/components/hooks/useQueryParam.ts @@ -0,0 +1,92 @@ +// A generic hook for getting and setting a query parameter without reloading the page +// The `queryParam` variable returned from this method are stateful and will be set to the query param on page load + +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import { parseDebug } from '@/search/components/hooks/useQuery' + +type UseQueryParamReturn = { + debug: boolean + queryParam: T + setQueryParam: (value: T) => void +} + +// Overloads so we can use this for a boolean or string query param +// eslint-disable-next-line no-redeclare +export function useQueryParam(queryParamKey: string, isBoolean: true): UseQueryParamReturn +// eslint-disable-next-line no-redeclare +export function useQueryParam(queryParamKey: string, isBoolean?: false): UseQueryParamReturn +// eslint-disable-next-line no-redeclare +export function useQueryParam( + queryParamKey: string, + isBoolean?: boolean, +): UseQueryParamReturn { + const router = useRouter() + + // Determine the initial value of the query param + let initialQueryParam = '' + const paramValue = router.query[queryParamKey] + + if (paramValue) { + if (Array.isArray(paramValue)) { + initialQueryParam = paramValue[0] + } else { + initialQueryParam = paramValue + } + } + + const debugValue = parseDebug(router.query.debug) + + // Return type will be set based on overloads + const [queryParamString, setQueryParamState] = useState(initialQueryParam) + const [debug] = useState(debugValue) + + // If the query param changes in the URL, update the state + useEffect(() => { + console.log('updating state') + const paramValue = router.query[queryParamKey] + + if (paramValue) { + if (Array.isArray(paramValue)) { + setQueryParamState(paramValue[0]) + } else { + setQueryParamState(paramValue) + } + } + }, [router.query, queryParamKey]) + + // Determine the type of queryParam based on isBoolean + const queryParam: string | boolean = isBoolean ? queryParamString === 'true' : queryParamString + + const setQueryParam = (value: string | boolean) => { + const { pathname, hash, search } = window.location + + let newValue: string = value as string + + // If it's a false boolean or empty string, just remove the query param + if (!value) { + newValue = '' + } else if (typeof value === 'boolean') { + newValue = 'true' + } + + const params = new URLSearchParams(search) + if (newValue) { + params.set(queryParamKey, newValue) + } else { + params.delete(queryParamKey) + } + + const newSearch = params.toString() + const newUrl = newSearch ? `${pathname}?${newSearch}${hash}` : `${pathname}${hash}` + + window.history.replaceState({}, '', newUrl) + setQueryParamState(newValue) + } + + return { + debug, + queryParam: queryParam as any, // Type will be set based on overloads + setQueryParam: setQueryParam as any, + } +} diff --git a/src/frame/components/page-header/Header.module.scss b/src/frame/components/page-header/Header.module.scss index 2897d50eca62..2624600b0994 100644 --- a/src/frame/components/page-header/Header.module.scss +++ b/src/frame/components/page-header/Header.module.scss @@ -10,43 +10,6 @@ z-index: 3 !important; } -// Contains the search input, language picker, and sign-up button. When the -// search input is open and up to sm (where the language picker and sign-up -// button are hidden) we need to take up almost all the header width but then at -// md and above we don't want the search input to take up the header width. -.widgetsContainer { - width: 100%; - - @include breakpoint(md) { - width: auto; - } -} - -// Contains the search input and used when the smaller width search input UI is -// closed to hide the full width input, but as the width increases to md and -// above we show the search input along the other UI widgets (the menu button, -// the language picker, etc.) -.searchContainerWithClosedSearch { - display: none; - - @include breakpoint(md) { - display: block; - } -} - -// Contains the search input and used when the smaller width search input UI is -// open and we set it full width but as the browser width increases to md and -// above we don't take up the whole width anymore since we now show other UI -// widgets. -.searchContainerWithOpenSearch { - width: 100%; - margin-right: -1px; - - @include breakpoint(md) { - width: auto; - } -} - // Contains the logo and version picker and used when the smaller width search // input UI is closed. .logoWithClosedSearch { diff --git a/src/frame/components/page-header/Header.tsx b/src/frame/components/page-header/Header.tsx index b3a4493ad59e..8d1a0efa2dd9 100644 --- a/src/frame/components/page-header/Header.tsx +++ b/src/frame/components/page-header/Header.tsx @@ -1,32 +1,28 @@ import { Suspense, useCallback, useEffect, useRef, useState } from 'react' import cx from 'classnames' import { useRouter } from 'next/router' -import { ActionList, ActionMenu, Dialog, IconButton } from '@primer/react' -import { - KebabHorizontalIcon, - LinkExternalIcon, - MarkGithubIcon, - SearchIcon, - ThreeBarsIcon, - XIcon, -} from '@primer/octicons-react' +import { Dialog, IconButton } from '@primer/react' +import { MarkGithubIcon, ThreeBarsIcon } from '@primer/octicons-react' import dynamic from 'next/dynamic' import { DEFAULT_VERSION, useVersion } from 'src/versions/components/useVersion' import { Link } from 'src/frame/components/Link' import { useMainContext } from 'src/frame/components/context/MainContext' -import { useHasAccount } from 'src/frame/components/hooks/useHasAccount' -import { LanguagePicker } from 'src/languages/components/LanguagePicker' import { HeaderNotifications } from 'src/frame/components/page-header/HeaderNotifications' import { ApiVersionPicker } from 'src/rest/components/ApiVersionPicker' import { useTranslation } from 'src/languages/components/useTranslation' -import { Search } from 'src/search/components/Search' import { Breadcrumbs } from 'src/frame/components/page-header/Breadcrumbs' import { VersionPicker } from 'src/versions/components/VersionPicker' import { SidebarNav } from 'src/frame/components/sidebar/SidebarNav' import { AllProductsLink } from 'src/frame/components/sidebar/AllProductsLink' import styles from './Header.module.scss' +import { OldHeaderSearchAndWidgets } from './OldHeaderSearchAndWidgets' +import { HeaderSearchAndWidgets } from './HeaderSearchAndWidgets' +import { useInnerWindowWidth } from './hooks/useInnerWindowWidth' +import { EXPERIMENTS } from '@/events/components/experiments/experiments' +import { useShouldShowExperiment } from '@/events/components/experiments/useShouldShowExperiment' +import { useQueryParam } from '@/frame/components/hooks/useQueryParam' const DomainNameEdit = dynamic(() => import('src/links/components/DomainNameEdit'), { ssr: false, @@ -39,9 +35,11 @@ export const Header = () => { const { currentVersion } = useVersion() const { t } = useTranslation(['header']) const isRestPage = currentProduct && currentProduct.id === 'rest' - const [isSearchOpen, setIsSearchOpen] = useState(false) + const { queryParam: isSearchOpen, setQueryParam: setIsSearchOpen } = useQueryParam( + 'search-overlay-open', + true, + ) const [scroll, setScroll] = useState(false) - const { hasAccount } = useHasAccount() const [isSidebarOpen, setIsSidebarOpen] = useState(false) const openSidebar = useCallback(() => setIsSidebarOpen(true), [isSidebarOpen]) const closeSidebar = useCallback(() => setIsSidebarOpen(false), [isSidebarOpen]) @@ -50,12 +48,14 @@ export const Header = () => { const { asPath } = useRouter() const isSearchResultsPage = router.route === '/search' const isEarlyAccessPage = currentProduct && currentProduct.id === 'early-access' - const signupCTAVisible = - hasAccount === false && // don't show if `null` - (currentVersion === DEFAULT_VERSION || currentVersion === 'enterprise-cloud@latest') - const { width } = useWidth() + const { width } = useInnerWindowWidth() const returnFocusRef = useRef(null) + const showNewSearch = useShouldShowExperiment( + EXPERIMENTS.ai_search_experiment, + router.locale as string, + ) + useEffect(() => { function onScroll() { setScroll(window.scrollY > 10) @@ -120,32 +120,6 @@ export const Header = () => { } }, []) - function useWidth() { - const hasWindow = typeof window !== 'undefined' - - function getWidth() { - const width = hasWindow ? window.innerWidth : null - return { - width, - } - } - - const [width, setWidth] = useState(getWidth()) - - useEffect(() => { - if (hasWindow) { - const handleResize = function () { - setWidth(getWidth()) - } - - window.addEventListener('resize', handleResize) - return () => window.removeEventListener('resize', handleResize) - } - }, [hasWindow]) - - return width - } - let homeURL = `/${router.locale}` if (currentVersion !== DEFAULT_VERSION) { homeURL += `/${currentVersion}` @@ -172,6 +146,10 @@ export const Header = () => { >
    {
    )}
    - -
    - {/* */} - {error !== '404' && ( -
    - -
    - )} - -
    - -
    - - {signupCTAVisible && ( - - )} - - setIsSearchOpen(!isSearchOpen)} - aria-label="Open Search Bar" - aria-expanded={isSearchOpen ? 'true' : 'false'} - icon={SearchIcon} + {showNewSearch ? ( + - setIsSearchOpen(!isSearchOpen)} - aria-label="Close Search Bar" - aria-expanded={isSearchOpen ? 'true' : 'false'} - icon={XIcon} - sx={ - isSearchOpen - ? { - // The x button to close the small width search UI when search is open, as the - // browser width increases to md and above we no longer show that search UI so - // the close search button is hidden as well. - // breakpoint(md) - '@media (min-width: 768px)': { - display: 'none', - }, - } - : { - display: 'none', - } - } + ) : ( + - - {/* The ... navigation menu at medium and smaller widths */} -
    - - - - - - - - {width && width > 544 ? ( - - ) : ( - - )} - - {width && width < 545 && ( - <> - - - {showDomainNameEdit && ( - <> - - - - - - )} - - )} - {signupCTAVisible && ( - - {t`sign_up_cta`} - - - )}{' '} - - - - -
    -
    + )} {!isHomepageVersion && !isSearchResultsPage && (
    diff --git a/src/frame/components/page-header/HeaderSearchAndWidgets.tsx b/src/frame/components/page-header/HeaderSearchAndWidgets.tsx new file mode 100644 index 000000000000..2f12548de6ac --- /dev/null +++ b/src/frame/components/page-header/HeaderSearchAndWidgets.tsx @@ -0,0 +1,149 @@ +import { Suspense } from 'react' +import cx from 'classnames' +import { KebabHorizontalIcon, LinkExternalIcon } from '@primer/octicons-react' +import { IconButton, ActionMenu, ActionList } from '@primer/react' + +import { LanguagePicker } from '@/languages/components/LanguagePicker' +import { useTranslation } from '@/languages/components/useTranslation' +import DomainNameEdit from '@/links/components/DomainNameEdit' +import { VersionPicker } from '@/versions/components/VersionPicker' +import { DEFAULT_VERSION, useVersion } from '@/versions/components/useVersion' +import { useHasAccount } from '../hooks/useHasAccount' + +import { SearchBarButton } from '@/search/components/input/SearchBarButton' +import { useBreakpoint } from '@/search/components/hooks/useBreakpoint' + +type Props = { + isSearchOpen: boolean + setIsSearchOpen: (value: boolean) => void + width: number | null +} + +export function HeaderSearchAndWidgets({ isSearchOpen, setIsSearchOpen, width }: Props) { + const { currentVersion } = useVersion() + const { t } = useTranslation(['header']) + const isLarge = useBreakpoint('large') + const { hasAccount } = useHasAccount() + const signupCTAVisible = + hasAccount === false && // don't show if `null` + (currentVersion === DEFAULT_VERSION || currentVersion === 'enterprise-cloud@latest') + + const showDomainNameEdit = currentVersion.startsWith('enterprise-server@') + + const SearchButton = ( + + ) + + return ( + <> + {/* At larger & up widths we show the search as an input. This doesn't need to be grouped with the widgets */} + {isLarge ? SearchButton : null} +
    +
    + +
    + + {signupCTAVisible && ( + + )} + + {/* Below large widths we show the search as a button which needs to be grouped with the widgets */} + {!isLarge ? SearchButton : null} + + {/* The ... navigation menu at medium and smaller widths */} +
    + + + + + + + + {width && width > 544 ? ( + + ) : ( + + )} + + {width && width < 545 && ( + <> + + + {showDomainNameEdit && ( + <> + + + + + + )} + + )} + {signupCTAVisible && ( + + {t`sign_up_cta`} + + + )}{' '} + + + + +
    +
    + + ) +} diff --git a/src/frame/components/page-header/OldHeaderSearchAndWidgets.module.scss b/src/frame/components/page-header/OldHeaderSearchAndWidgets.module.scss new file mode 100644 index 000000000000..87a6e495e4de --- /dev/null +++ b/src/frame/components/page-header/OldHeaderSearchAndWidgets.module.scss @@ -0,0 +1,39 @@ +@import "@primer/css/support/variables/layout.scss"; +@import "@primer/css/support/mixins/layout.scss"; + +// Contains the search input, language picker, and sign-up button. When the +// search input is open and up to sm (where the language picker and sign-up +// button are hidden) we need to take up almost all the header width but then at +// md and above we don't want the search input to take up the header width. +.widgetsContainer { + width: 100%; + + @include breakpoint(md) { + width: auto; + } +} + +// Contains the search input and used when the smaller width search input UI is +// closed to hide the full width input, but as the width increases to md and +// above we show the search input along the other UI widgets (the menu button, +// the language picker, etc.) +.searchContainerWithClosedSearch { + display: none; + + @include breakpoint(md) { + display: block; + } +} + +// Contains the search input and used when the smaller width search input UI is +// open and we set it full width but as the browser width increases to md and +// above we don't take up the whole width anymore since we now show other UI +// widgets. +.searchContainerWithOpenSearch { + width: 100%; + margin-right: -1px; + + @include breakpoint(md) { + width: auto; + } +} diff --git a/src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx b/src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx new file mode 100644 index 000000000000..3f8a2c049593 --- /dev/null +++ b/src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx @@ -0,0 +1,186 @@ +import { Suspense } from 'react' +import cx from 'classnames' +import { SearchIcon, XIcon, KebabHorizontalIcon, LinkExternalIcon } from '@primer/octicons-react' +import { IconButton, ActionMenu, ActionList } from '@primer/react' + +import { LanguagePicker } from '@/languages/components/LanguagePicker' +import { useTranslation } from '@/languages/components/useTranslation' +import DomainNameEdit from '@/links/components/DomainNameEdit' +import { OldSearchInput } from '@/search/components/input/OldSearchInput' +import { VersionPicker } from '@/versions/components/VersionPicker' +import { DEFAULT_VERSION, useVersion } from '@/versions/components/useVersion' +import { useHasAccount } from '../hooks/useHasAccount' +import { useMainContext } from '../context/MainContext' + +import styles from './OldHeaderSearchAndWidgets.module.scss' + +type Props = { + isSearchOpen: boolean + setIsSearchOpen: (value: boolean) => void + width: number | null +} + +export function OldHeaderSearchAndWidgets({ isSearchOpen, setIsSearchOpen, width }: Props) { + const { error } = useMainContext() + const { currentVersion } = useVersion() + const { t } = useTranslation(['header']) + const { hasAccount } = useHasAccount() + const signupCTAVisible = + hasAccount === false && // don't show if `null` + (currentVersion === DEFAULT_VERSION || currentVersion === 'enterprise-cloud@latest') + + const showDomainNameEdit = currentVersion.startsWith('enterprise-server@') + + return ( +
    + {/* */} + {error !== '404' && ( +
    + +
    + )} + +
    + +
    + + {signupCTAVisible && ( + + )} + + setIsSearchOpen(!isSearchOpen)} + aria-label="Open Search Bar" + aria-expanded={isSearchOpen ? 'true' : 'false'} + icon={SearchIcon} + /> + setIsSearchOpen(!isSearchOpen)} + aria-label="Close Search Bar" + aria-expanded={isSearchOpen ? 'true' : 'false'} + icon={XIcon} + sx={ + isSearchOpen + ? { + // The x button to close the small width search UI when search is open, as the + // browser width increases to md and above we no longer show that search UI so + // the close search button is hidden as well. + // breakpoint(md) + '@media (min-width: 768px)': { + display: 'none', + }, + } + : { + display: 'none', + } + } + /> + + {/* The ... navigation menu at medium and smaller widths */} +
    + + + + + + + + {width && width > 544 ? ( + + ) : ( + + )} + + {width && width < 545 && ( + <> + + + {showDomainNameEdit && ( + <> + + + + + + )} + + )} + {signupCTAVisible && ( + + {t`sign_up_cta`} + + + )}{' '} + + + + +
    +
    + ) +} diff --git a/src/frame/components/page-header/hooks/useInnerWindowWidth.ts b/src/frame/components/page-header/hooks/useInnerWindowWidth.ts new file mode 100644 index 000000000000..bb4c4d425508 --- /dev/null +++ b/src/frame/components/page-header/hooks/useInnerWindowWidth.ts @@ -0,0 +1,27 @@ +import { useState, useEffect } from 'react' + +export function useInnerWindowWidth() { + const hasWindow = typeof window !== 'undefined' + + function getWidth() { + const width = hasWindow ? window.innerWidth : null + return { + width, + } + } + + const [width, setWidth] = useState(getWidth()) + + useEffect(() => { + if (hasWindow) { + const handleResize = function () { + setWidth(getWidth()) + } + + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + } + }, [hasWindow]) + + return width +} diff --git a/src/frame/components/sidebar/SidebarNav.tsx b/src/frame/components/sidebar/SidebarNav.tsx index a19f279a6574..7ac2f196dfed 100644 --- a/src/frame/components/sidebar/SidebarNav.tsx +++ b/src/frame/components/sidebar/SidebarNav.tsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router' import { useMainContext } from 'src/frame/components/context/MainContext' import { SidebarProduct } from 'src/landings/components/SidebarProduct' -import { SidebarSearchAggregates } from 'src/search/components/SidebarSearchAggregates' +import { SidebarSearchAggregates } from 'src/search/components/results/SidebarSearchAggregates' import { AllProductsLink } from './AllProductsLink' import { ApiVersionPicker } from 'src/rest/components/ApiVersionPicker' import { Link } from 'src/frame/components/Link' diff --git a/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx b/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx new file mode 100644 index 000000000000..ad554bbad285 --- /dev/null +++ b/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx @@ -0,0 +1,49 @@ +import ReactMarkdown from 'react-markdown' +import type { Components } from 'react-markdown' +import cx from 'classnames' +import remarkGfm from 'remark-gfm' + +import styles from './MarkdownContent.module.scss' + +export type MarkdownContentPropsT = { + children: string + className?: string + openLinksInNewTab?: boolean + eventGroupKey?: string + eventGroupId?: string + as?: keyof JSX.IntrinsicElements + tabIndex?: number +} + +// For content that comes in a Markdown string +// e.g. a GPT Response + +export const UnrenderedMarkdownContent = ({ + children, + className, + openLinksInNewTab = true, + eventGroupKey = '', + eventGroupId = '', + ...restProps +}: MarkdownContentPropsT) => { + // Overrides for ReactMarkdown components + const components = {} as Components + if (openLinksInNewTab) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + components.a = ({ node, ...props }) => ( + + {props.children} + + ) + } + return ( + + {children} + + ) +} diff --git a/src/pages/search.tsx b/src/pages/search.tsx index 31d992817132..644b5dfac792 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -1 +1 @@ -export { default, getServerSideProps } from 'src/search/pages/search' +export { default, getServerSideProps } from 'src/search/pages/search-results' diff --git a/src/search/components/Loading.tsx b/src/search/components/Loading.tsx deleted file mode 100644 index a142d7934174..000000000000 --- a/src/search/components/Loading.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Spinner } from '@primer/react' -import { useEffect, useState } from 'react' - -export function Loading() { - const [showLoading, setShowLoading] = useState(false) - useEffect(() => { - let mounted = true - setTimeout(() => { - if (mounted) { - setShowLoading(true) - } - }, 1000) - - return () => { - mounted = false - } - }, []) - return showLoading ? : -} - -function ShowSpinner() { - return ( -
    - -
    - ) -} - -function ShowNothing() { - return ( - // The min heigh is based on inspecting what the height became when it - // does render. Making this match makes the footer to not flicker - // up or down when it goes from showing nothing to something. -
    - {/* Deliberately empty */} -
    - ) -} diff --git a/src/search/components/helpers/ai-search-links-json.ts b/src/search/components/helpers/ai-search-links-json.ts new file mode 100644 index 000000000000..00a6fea51702 --- /dev/null +++ b/src/search/components/helpers/ai-search-links-json.ts @@ -0,0 +1,76 @@ +type LinksJSON = Array<{ + type: 'reference' | 'inline' + url: string + product: string +}> + +// We use this to generate a JSON string that includes all of the links: +// 1. Included in the AI response (inline) +// 2. Used to generate the AI response via an embedding (reference) +// +// We include the JSON string in our analytics events so we can see the +// most popular sourced references, among other things. +export function generateAiSearchLinksJson( + sourcesBuffer: Array<{ url: string }>, + aiResponse: string, +): string { + const linksJson = [] as LinksJSON + const inlineLinks = extractMarkdownLinks(aiResponse) + for (const link of inlineLinks) { + const product = extractProductFromDocsUrl(link) + linksJson.push({ + type: 'inline', + url: link, + product, + }) + } + for (const source of sourcesBuffer) { + const product = extractProductFromDocsUrl(source.url) + linksJson.push({ + type: 'reference', + url: source.url, + product, + }) + } + + return JSON.stringify(linksJson) +} + +// Get all links in a markdown text +function extractMarkdownLinks(markdownResponse: string) { + // This regex matches markdown links of the form [text](url) + // Explanation: + // \[([^\]]+)\] : Matches the link text inside square brackets (one or more non-']' characters). + // \( : Matches the opening parenthesis. + // ([^)]+) : Captures the URL (one or more characters that are not a closing parenthesis). + // \) : Matches the closing parenthesis. + const regex = /\[([^\]]+)\]\(([^)]+)\)/g + + const urls = [] + let match + + while ((match = regex.exec(markdownResponse)) !== null) { + urls.push(match[2]) + } + + return urls +} + +// Given a Docs URL, extract the product name +function extractProductFromDocsUrl(url: string): string { + const pathname = new URL(url).pathname + + const segments = pathname.split('/').filter((segment) => segment) + + // If the first segment is a language code (2 characters), then product is the next segment. + // Otherwise, assume the first segment is the product. + if (segments.length === 0) { + return '' + } + + if (segments[0].length === 2) { + return segments[1] || '' + } + + return segments[0] +} diff --git a/src/search/components/helpers/execute-search-actions.ts b/src/search/components/helpers/execute-search-actions.ts new file mode 100644 index 000000000000..e07e97a87bf0 --- /dev/null +++ b/src/search/components/helpers/execute-search-actions.ts @@ -0,0 +1,123 @@ +import { EventType } from '@/events/types' +import { AutocompleteSearchResponse } from '@/search/types' +import { DEFAULT_VERSION } from '@/versions/components/useVersion' +import { NextRouter } from 'next/router' +import { sendEvent } from 'src/events/components/events' +import { ASK_AI_EVENT_GROUP, SEARCH_OVERLAY_EVENT_GROUP } from '@/events/components/event-groups' + +// Search context values for identifying each search event +export const GENERAL_SEARCH_CONTEXT = 'general-search' +export const AI_SEARCH_CONTEXT = 'ai-search' +export const AI_AUTOCOMPLETE_SEARCH_CONTEXT = 'ai-search-autocomplete' + +// The logic that redirects to the /search page with the proper query params +// The query params will be consumed in the general search middleware +export function executeGeneralSearch( + router: NextRouter, + currentVersion: string, + localQuery: string, + debug = false, + eventGroupId?: string, +) { + sendEvent({ + type: EventType.search, + search_query: localQuery, + search_context: GENERAL_SEARCH_CONTEXT, + eventGroupKey: SEARCH_OVERLAY_EVENT_GROUP, + eventGroupId, + }) + + let asPath = `/${router.locale}` + if (currentVersion !== DEFAULT_VERSION) { + asPath += `/${currentVersion}` + } + asPath += '/search' + const params = new URLSearchParams({ query: localQuery }) + if (debug) { + params.set('debug', '1') + } + asPath += `?${params}` + router.push(asPath) +} + +export async function executeAISearch( + router: NextRouter, + version: string, + query: string, + debug = false, + eventGroupId?: string, +) { + sendEvent({ + type: EventType.search, + // TODO: Remove PII so we can include the actual query + search_query: 'REDACTED', + search_context: AI_SEARCH_CONTEXT, + eventGroupKey: ASK_AI_EVENT_GROUP, + eventGroupId, + }) + + let language = router.locale || 'en' + + const body = { + query, + version, + language, + ...(debug && { debug: '1' }), + } + + const response = await fetch(`/api/ai-search/v1`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }) + + return response +} + +// The AJAX request logic that fetches the autocomplete options for AI autocomplete sugggestions +export async function executeAIAutocompleteSearch( + router: NextRouter, + version: string, + query: string, + debug = false, + abortSignal?: AbortSignal, + eventGroupId?: string, +) { + sendEvent({ + type: EventType.search, + // TODO: Remove PII so we can include the actual query + search_query: 'REDACTED', + search_context: AI_AUTOCOMPLETE_SEARCH_CONTEXT, + eventGroupKey: SEARCH_OVERLAY_EVENT_GROUP, + eventGroupId: eventGroupId, + }) + + let language = router.locale || 'en' + + const params = new URLSearchParams({ query: query, version, language }) + if (debug) { + params.set('debug', '1') + } + + // Always fetch 5 results for autocomplete + params.set('size', '5') + + const response = await fetch(`/api/search/ai-search-autocomplete/v1?${params}`, { + headers: { + 'Content-Type': 'application/json', + }, + // Allow the caller to pass in an AbortSignal to cancel the request + signal: abortSignal || undefined, + }) + if (!response.ok) { + throw new Error( + `Failed to fetch ai autocomplete search results.\nStatus ${response.status}\n${response.statusText}`, + ) + } + const results = (await response.json()) as AutocompleteSearchResponse + return { + aiAutocompleteOptions: results?.hits || [], + } +} diff --git a/src/search/components/helpers/fix-incomplete-markdown.ts b/src/search/components/helpers/fix-incomplete-markdown.ts new file mode 100644 index 000000000000..c4b1fcde4930 --- /dev/null +++ b/src/search/components/helpers/fix-incomplete-markdown.ts @@ -0,0 +1,153 @@ +// When streaming markdown response, e.g., from a GPT, the response will come in chunks that may have opening tags but no closing tags. +// This function seeks to fix the partial markdown by closing the tags it detects. +export function fixIncompleteMarkdown(content: string): string { + // First, fix code blocks + content = fixCodeBlocks(content) + + // Then, fix inline code + content = fixInlineCode(content) + + // Then, fix links + content = fixLinks(content) + + // Then, fix images + content = fixImages(content) + + // Then, fix emphasis (bold, italic, strikethrough) + content = fixEmphasis(content) + + // Then, fix tables + content = fixTables(content) + + return content +} + +function fixCodeBlocks(content: string): string { + const codeBlockRegex = /```/g + const matches = content.match(codeBlockRegex) + const count = matches ? matches.length : 0 + if (count % 2 !== 0) { + content += '\n```' + } + return content +} + +function fixInlineCode(content: string): string { + const inlineCodeRegex = /`/g + const matches = content.match(inlineCodeRegex) + const count = matches ? matches.length : 0 + if (count % 2 !== 0) { + content += '`' + } + return content +} + +function fixLinks(content: string): string { + // Handle unclosed link text '[' + const linkTextRegex = /\[([^\]]*)$/ + if (linkTextRegex.test(content)) { + content += ']' + } + + // Handle unclosed link URL '(' + const linkURLRegex = /\]\(([^)]*)$/ + if (linkURLRegex.test(content)) { + content += ')' + } + + return content +} + +function fixImages(content: string): string { + // Handle unclosed image alt text '![' + const imageAltTextRegex = /!\[([^\]]*)$/ + if (imageAltTextRegex.test(content)) { + content += ']' + } + + // Handle unclosed image URL '(' + const imageURLRegex = /!\[[^\]]*\]\(([^)]*)$/ + if (imageURLRegex.test(content)) { + content += ')' + } + + return content +} + +function fixEmphasis(content: string): string { + const tokens = ['***', '**', '__', '*', '_', '~~', '~'] + const stack: { token: string; index: number }[] = [] + + let i = 0 + while (i < content.length) { + let matched = false + for (const token of tokens) { + if (content.substr(i, token.length) === token) { + if (stack.length > 0 && stack[stack.length - 1].token === token) { + // Closing token found + stack.pop() + } else { + // Opening token found + stack.push({ token, index: i }) + } + i += token.length + matched = true + break + } + } + if (!matched) { + i++ + } + } + + // Close any remaining tokens in reverse order + while (stack.length > 0) { + const { token } = stack.pop()! + content += token + } + + return content +} + +function fixTables(content: string): string { + const lines = content.split('\n') + let inTable = false + let headerPipeCount = 0 + let i = 0 + + while (i < lines.length) { + const line = lines[i] + if (/^\s*\|.*$/.test(line)) { + // Line starts with '|', possible table line + if (!inTable) { + // Potential start of table + if (i + 1 < lines.length && /^\s*\|[-\s|:]*$/.test(lines[i + 1])) { + // Next line is separator, confirm table header + inTable = true + // Count number of '|' in header line + headerPipeCount = (lines[i].match(/\|/g) || []).length + i += 1 // Move to separator line + } else { + // Not a table, continue + i += 1 + continue + } + } else { + // In table body + const linePipeCount = (line.match(/\|/g) || []).length + if (linePipeCount < headerPipeCount) { + // Calculate missing pipes + const missingPipes = headerPipeCount - linePipeCount + // Append missing ' |' to match header columns + lines[i] = line.trimEnd() + ' |'.repeat(missingPipes) + } + } + } else { + // Exiting table + inTable = false + headerPipeCount = 0 + } + i += 1 + } + return lines.join('\n') +} diff --git a/src/search/components/hooks/useAISearchAutocomplete.ts b/src/search/components/hooks/useAISearchAutocomplete.ts new file mode 100644 index 000000000000..192ad669b1bc --- /dev/null +++ b/src/search/components/hooks/useAISearchAutocomplete.ts @@ -0,0 +1,152 @@ +import { useState, useRef, useCallback, useEffect } from 'react' +import debounce from 'lodash/debounce' +import { NextRouter } from 'next/router' +import { AutocompleteSearchHit } from '@/search/types' +import { executeAIAutocompleteSearch } from '@/search/components/helpers/execute-search-actions' + +type AutocompleteOptions = { + aiAutocompleteOptions: AutocompleteSearchHit[] +} + +type UseAutocompleteProps = { + router: NextRouter + currentVersion: string + debug: boolean + eventGroupIdRef: React.MutableRefObject +} + +type UseAutocompleteReturn = { + autoCompleteOptions: AutocompleteOptions + searchLoading: boolean + searchError: boolean + updateAutocompleteResults: (query: string) => void + clearAutocompleteResults: () => void +} + +const DEBOUNCE_TIME = 300 // In milliseconds + +// Results are only cached for the current session +// We cache results so if a user presses backspace, we can show the results immediately without burdening the API +let sessionCache = {} as Record + +// Helpers surrounding the ai-search-autocomplete request to lessen the # of requests made to our API +// There are 3 methods for reducing the # of requests: +// 1. Debouncing the request to prevent multiple requests while the user is typing +// 2. Caching the results of the request so if the user presses backspace, we can show the results immediately without burdening the API +// 3. Aborting in-flight requests if the user types again before the previous request has completed +export function useAISearchAutocomplete({ + router, + currentVersion, + debug, + eventGroupIdRef, +}: UseAutocompleteProps): UseAutocompleteReturn { + const [autoCompleteOptions, setAutoCompleteOptions] = useState({ + aiAutocompleteOptions: [], + }) + const [searchLoading, setSearchLoading] = useState(true) + const [searchError, setSearchError] = useState(false) + + // Support for aborting in-flight requests (e.g. user starts typing while a request is still pending) + const abortControllerRef = useRef(null) + + // Debounce to prevent requests while user is (quickly) typing + const debouncedFetchRef = useRef | null>(null) + + useEffect(() => { + debouncedFetchRef.current = debounce((value: string) => { + fetchAutocompleteResults(value) + }, DEBOUNCE_TIME) // 300ms debounce + + return () => { + debouncedFetchRef.current?.cancel() + } + }, []) + + const fetchAutocompleteResults = useCallback( + async (queryValue: string) => { + // Cancel any ongoing request + if (abortControllerRef.current) { + abortControllerRef.current.abort() + } + + // Check if the result is in cache + if (sessionCache[queryValue]) { + setAutoCompleteOptions(sessionCache[queryValue]) + setSearchLoading(false) + return + } + + setSearchLoading(true) + + // Create a new AbortController for the new request + const controller = new AbortController() + abortControllerRef.current = controller + + try { + const { aiAutocompleteOptions } = await executeAIAutocompleteSearch( + router, + currentVersion, + queryValue, + debug, + controller.signal, // Pass in the signal to allow the request to be aborted + eventGroupIdRef.current, + ) + + const results: AutocompleteOptions = { + aiAutocompleteOptions, + } + + // Update cache + sessionCache[queryValue] = results + + // Update state with fetched results + setAutoCompleteOptions(results) + setSearchLoading(false) + } catch (error: any) { + if (error.name === 'AbortError') { + return + } + console.error(error) + setSearchError(true) + setSearchLoading(false) + } + }, + [router, currentVersion, debug], + ) + + // Entry function called when the user types in the search input + const updateAutocompleteResults = useCallback((queryValue: string) => { + // When the input is empty, don't debounce the request + // We want to immediately show the autocomplete options (that may be cached) + if (queryValue === '') { + debouncedFetchRef.current?.cancel() + fetchAutocompleteResults('') + return + } else { + debouncedFetchRef.current?.(queryValue) + } + }, []) + + const clearAutocompleteResults = useCallback(() => { + setAutoCompleteOptions({ + aiAutocompleteOptions: [], + }) + setSearchLoading(false) + setSearchError(false) + }, []) + + // Cleanup function to cancel any ongoing requests when unmounting + useEffect(() => { + return () => { + abortControllerRef.current?.abort() + } + }, []) + + return { + autoCompleteOptions, + searchLoading, + searchError, + updateAutocompleteResults, + clearAutocompleteResults, + } +} diff --git a/src/search/components/useBreakpoint.ts b/src/search/components/hooks/useBreakpoint.ts similarity index 100% rename from src/search/components/useBreakpoint.ts rename to src/search/components/hooks/useBreakpoint.ts diff --git a/src/search/components/hooks/useLocalStorageCache.ts b/src/search/components/hooks/useLocalStorageCache.ts new file mode 100644 index 000000000000..02353d3f4877 --- /dev/null +++ b/src/search/components/hooks/useLocalStorageCache.ts @@ -0,0 +1,152 @@ +import { useCallback } from 'react' + +interface CachedItem { + data: T + timestamp: number +} + +interface CacheIndexEntry { + key: string + timestamp: number +} + +/** + * Custom hook for managing a localStorage cache + * The cache uses an index to track the keys of cached items, and a separate item in localStorage + * This allows the cache to be updated without having to read a single large entry into memory and parse it each time a key is accessed + * + * Cached items are cached under a prefix, for a fixed number of days, and the cache is limited to a fixed number of entries set by the following: + * @param cacheKeyPrefix - Prefix for cache keys in localStorage. + * @param maxEntries - Maximum number of entries that can be stored in the cache. + * @param expirationDays - Number of days before a cache entry expires. + * @returns An object containing getItem and setItem functions. + */ +function useLocalStorageCache( + cacheKeyPrefix: string = 'ai-query-cache', + maxEntries: number = 1000, + expirationDays: number = 30, +) { + const cacheIndexKey = `${cacheKeyPrefix}-index` + + /** + * Generates a unique key based on the query string. + * @param query - The query string to generate the key from. + * @returns A unique string key. + */ + const generateKey = (query: string): string => { + query = query.trim().toLowerCase() + // Simple hash function to generate a unique key from the query + let hash = 0 + for (let i = 0; i < query.length; i++) { + const char = query.charCodeAt(i) + hash = (hash << 5) - hash + char + hash |= 0 // Convert to 32bit integer + } + return `${cacheKeyPrefix}-${Math.abs(hash)}` + } + + /** + * Retrieves an item from the cache. + * @param query - The query string associated with the cached data. + * @returns The cached data if valid, otherwise null. + */ + const getItem = useCallback( + (query: string): T | null => { + const key = generateKey(query) + const itemStr = localStorage.getItem(key) + if (!itemStr) return null + + let cachedItem: CachedItem + try { + cachedItem = JSON.parse(itemStr) + } catch (e) { + console.error('Failed to parse cached item from localStorage', e) + localStorage.removeItem(key) + return null + } + + const now = Date.now() + const expirationTime = cachedItem.timestamp + expirationDays * 24 * 60 * 60 * 1000 + if (now < expirationTime) { + // Item is still valid + return cachedItem.data + } else { + // Item expired, remove it + localStorage.removeItem(key) + updateCacheIndex((index) => index.filter((entry) => entry.key !== key)) + return null + } + }, + [cacheKeyPrefix, expirationDays], + ) + + /** + * Stores an item in the cache. + * @param query - The query string associated with the data. + * @param data - The data to cache. + */ + const setItem = useCallback( + (query: string, data: T): void => { + const key = generateKey(query) + const now = Date.now() + const cachedItem: CachedItem = { data, timestamp: now } + + // Store the item + localStorage.setItem(key, JSON.stringify(cachedItem)) + + // Update index + const indexStr = localStorage.getItem(cacheIndexKey) + let index: CacheIndexEntry[] = [] + if (indexStr) { + try { + index = JSON.parse(indexStr) + } catch (e) { + console.error('Failed to parse cache index from localStorage', e) + } + } + + // Remove existing entry for this key if any + index = index.filter((entry) => entry.key !== key) + index.push({ key, timestamp: now }) + + // If cache exceeds max entries, remove oldest entries + if (index.length > maxEntries) { + // Sort entries by timestamp + index.sort((a, b) => a.timestamp - b.timestamp) + const excess = index.length - maxEntries + const entriesToRemove = index.slice(0, excess) + entriesToRemove.forEach((entry) => { + localStorage.removeItem(entry.key) + }) + index = index.slice(excess) + } + + // Store updated index + localStorage.setItem(cacheIndexKey, JSON.stringify(index)) + }, + [cacheKeyPrefix, maxEntries], + ) + + /** + * Updates the cache index using a provided updater function. + * @param updateFn - A function that takes the current index and returns the updated index. + */ + const updateCacheIndex = (updateFn: (index: CacheIndexEntry[]) => CacheIndexEntry[]): void => { + const indexStr = localStorage.getItem(cacheIndexKey) + let index: CacheIndexEntry[] = [] + if (indexStr) { + try { + index = JSON.parse(indexStr) + } catch (e) { + console.error('Failed to parse cache index from localStorage', e) + } + } + + index = updateFn(index) + localStorage.setItem(cacheIndexKey, JSON.stringify(index)) + } + + return { getItem, setItem } +} + +export default useLocalStorageCache diff --git a/src/search/components/useMediaQuery.ts b/src/search/components/hooks/useMediaQuery.ts similarity index 100% rename from src/search/components/useMediaQuery.ts rename to src/search/components/hooks/useMediaQuery.ts diff --git a/src/search/components/useNumberFormatter.ts b/src/search/components/hooks/useNumberFormatter.ts similarity index 100% rename from src/search/components/useNumberFormatter.ts rename to src/search/components/hooks/useNumberFormatter.ts diff --git a/src/search/components/usePage.ts b/src/search/components/hooks/usePage.ts similarity index 100% rename from src/search/components/usePage.ts rename to src/search/components/hooks/usePage.ts diff --git a/src/search/components/useQuery.ts b/src/search/components/hooks/useQuery.ts similarity index 91% rename from src/search/components/useQuery.ts rename to src/search/components/hooks/useQuery.ts index 0f17b4b51966..7e7a1631adaf 100644 --- a/src/search/components/useQuery.ts +++ b/src/search/components/hooks/useQuery.ts @@ -19,7 +19,7 @@ export const useQuery = (): QueryInfo => { } } -function parseDebug(debug: string | Array | undefined) { +export function parseDebug(debug: string | Array | undefined) { if (debug === '') { // E.g. `?query=foo&debug` should be treated as truthy return true diff --git a/src/search/components/input/AskAIResults.module.scss b/src/search/components/input/AskAIResults.module.scss new file mode 100644 index 000000000000..f798e65600dd --- /dev/null +++ b/src/search/components/input/AskAIResults.module.scss @@ -0,0 +1,76 @@ +@import "@primer/css/support/variables/layout.scss"; + +$bodyPadding: 0 16px 0px 16px; +$mutedTextColor: var(--fgColor-muted, var(--color-fg-muted, #656d76)); + +.container { + max-height: 95vh; + overflow-y: auto; + overflow-x: hidden; + max-width: 100%; + width: 100%; +} + +.disclaimerText { + display: block; + font-size: small !important; + font-weight: var(--base-text-weight-normal, 400) !important; + color: $mutedTextColor; + margin: 8px 0px 8px 0px; + padding: $bodyPadding; +} + +.markdownBodyOverrides { + font-size: small; + margin-top: 4px; + margin-bottom: 16px; + padding: $bodyPadding; +} + +.referencesTitle { + font-size: small !important; + font-weight: var(--base-text-weight-normal, 400) !important; + margin: 0; + color: $mutedTextColor; + padding-left: 0 !important; +} + +.referencesList { + padding: 0 !important; + padding-left: 16px !important; + li { + padding: $bodyPadding; + margin-left: 0 !important; + padding-left: 0 !important; + a { + color: var(--color-accent-emphasis); + } + } +} + +.loadingContainer { + display: flex; + width: 100%; + align-items: center; + justify-content: center; + min-height: 200px; + height: 200px; +} + +.displayForScreenReader { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.postAnswerWidgets { + display: flex; + flex-direction: row; + padding-left: 12px !important; +} diff --git a/src/search/components/input/AskAIResults.tsx b/src/search/components/input/AskAIResults.tsx new file mode 100644 index 000000000000..1c343830dac4 --- /dev/null +++ b/src/search/components/input/AskAIResults.tsx @@ -0,0 +1,297 @@ +import { useEffect, useRef, useState } from 'react' +import { executeAISearch } from '../helpers/execute-search-actions' +import { useRouter } from 'next/router' +import { useTranslation } from '@/languages/components/useTranslation' +import { ActionList, IconButton, Spinner } from '@primer/react' +import { BookIcon, CheckIcon, CopyIcon, ThumbsdownIcon, ThumbsupIcon } from '@primer/octicons-react' +import { announce } from '@primer/live-region-element' +import useLocalStorageCache from '../hooks/useLocalStorageCache' +import { UnrenderedMarkdownContent } from '@/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent' +import styles from './AskAIResults.module.scss' +import { fixIncompleteMarkdown } from '../helpers/fix-incomplete-markdown' +import useClipboard from '@/rest/components/useClipboard' +import { sendEvent, uuidv4 } from '@/events/components/events' +import { EventType } from '@/events/types' +import { generateAiSearchLinksJson } from '../helpers/ai-search-links-json' +import { ASK_AI_EVENT_GROUP } from '@/events/components/event-groups' + +type AIQueryResultsProps = { + query: string + version: string + debug: boolean + setAISearchError: () => void +} + +type Source = { + url: string + title: string + index: string +} + +export function AskAIResults({ query, version, debug, setAISearchError }: AIQueryResultsProps) { + const router = useRouter() + const { t } = useTranslation('search') + const [message, setMessage] = useState('') + const [sources, setSources] = useState([] as Source[]) + const [initialLoading, setInitialLoading] = useState(true) + const [responseLoading, setResponseLoading] = useState(false) + const eventGroupId = useRef('') + const disclaimerRef = useRef(null) + // We cache up to 1000 queries, and expire them after 30 days + const { getItem, setItem } = useLocalStorageCache<{ + query: string + message: string + sources: Source[] + }>('ai-query-cache', 1000, 30) + + const [isCopied, setCopied] = useClipboard(message, { successDuration: 1400 }) + const [feedbackSelected, setFeedbackSelected] = useState(null) + + // On query change, fetch the new results + useEffect(() => { + let isCancelled = false + setMessage('') + setSources([]) + setInitialLoading(true) + setResponseLoading(true) + eventGroupId.current = uuidv4() + disclaimerRef.current?.focus() + + const cachedData = getItem(query) + if (cachedData) { + setMessage(cachedData.message) + setSources(cachedData.sources) + setInitialLoading(false) + setResponseLoading(false) + sendAISearchResultEvent(cachedData.sources, cachedData.message, eventGroupId.current) + return + } + + // Handler for streamed response from GPT + async function fetchData() { + let messageBuffer = '' + let sourcesBuffer: Source[] = [] + try { + const response = await executeAISearch(router, version, query, debug, eventGroupId.current) + // Serve canned response. A question that cannot be answered was asked + if (response.status === 400) { + setInitialLoading(false) + setResponseLoading(false) + const cannedResponse = t('search.ai.unable_to_answer') + setItem(query, { query, message: cannedResponse, sources: [] }) + return setMessage(cannedResponse) + } + if (!response.ok) { + console.error( + `Failed to fetch search results.\nStatus ${response.status}\n${response.statusText}`, + ) + return setAISearchError() + } + if (!response.body) { + console.error(`ReadableStream not supported in this browser`) + return setAISearchError() + } + + const decoder = new TextDecoder('utf-8') + const reader = response.body.getReader() + let done = false + setInitialLoading(false) + while (!done && !isCancelled) { + const { value, done: readerDone } = await reader.read() + done = readerDone + if (value) { + const chunkStr = decoder.decode(value, { stream: true }) + const chunkLines = chunkStr.split('\n').filter((line) => line.trim() !== '') + for (const line of chunkLines) { + let parsedLine + try { + parsedLine = JSON.parse(line) + } catch (e) { + console.error('Failed to parse JSON:', e, 'Line:', line) + continue + } + + if (parsedLine.chunkType === 'SOURCES') { + if (!isCancelled) { + sourcesBuffer = sourcesBuffer.concat(parsedLine.sources) + setSources(parsedLine.sources) + } + } else if (parsedLine.chunkType === 'MESSAGE_CHUNK') { + if (!isCancelled) { + messageBuffer += parsedLine.text + setMessage(messageBuffer) + } + } + } + } + } + } catch (error: any) { + if (!isCancelled) { + console.error('Failed to fetch search results:', error) + setAISearchError() + } + } finally { + if (!isCancelled && messageBuffer) { + setItem(query, { query, message: messageBuffer, sources: sourcesBuffer }) + setInitialLoading(false) + setResponseLoading(false) + sendAISearchResultEvent(sourcesBuffer, messageBuffer, eventGroupId.current) + } + } + } + + fetchData() + + return () => { + isCancelled = true + } + }, [query]) + + return ( +
    + {/* Hidden status message for screen readers */} + + {initialLoading || responseLoading + ? t('search.ai.loading_status_message') + : t('search.ai.done_loading_status_message')} + + {initialLoading ? ( +
    + +
    + ) : ( +
    + + {t('search.ai.disclaimer')} + + + {responseLoading ? fixIncompleteMarkdown(message) : message} + +
    + )} + {!responseLoading ? ( +
    + { + setFeedbackSelected('up') + announce(t('ai.thumbs_announcement')) + sendEvent({ + type: EventType.survey, + survey_vote: true, + eventGroupKey: ASK_AI_EVENT_GROUP, + eventGroupId: eventGroupId.current, + }) + }} + > + { + setFeedbackSelected('down') + announce(t('ai.thumbs_announcement')) + sendEvent({ + type: EventType.survey, + survey_vote: false, + eventGroupKey: ASK_AI_EVENT_GROUP, + eventGroupId: eventGroupId.current, + }) + }} + > + { + setCopied() + announce(t('ai.copied_announcement')) + sendEvent({ + type: EventType.clipboard, + clipboard_operation: 'copy', + eventGroupKey: ASK_AI_EVENT_GROUP, + eventGroupId: eventGroupId.current, + }) + }} + > +
    + ) : null} + {sources && sources.length > 0 ? ( + <> +
    + ) +} + +function sendAISearchResultEvent( + sources: Array<{ url: string }>, + message: string, + eventGroupId: string, +) { + let searchResultLinksJson = '[]' + try { + searchResultLinksJson = generateAiSearchLinksJson(sources, message) + } catch (e) { + console.error('Failed to generate search result links JSON:', e) + } + sendEvent({ + type: EventType.aiSearchResult, + // TODO: Remove PII so we can include the actual data + ai_search_result_query: 'REDACTED', + ai_search_result_response: 'REDACTED', + ai_search_result_links_json: searchResultLinksJson, + eventGroupKey: ASK_AI_EVENT_GROUP, + eventGroupId: eventGroupId, + }) +} diff --git a/src/search/components/Search.tsx b/src/search/components/input/OldSearchInput.tsx similarity index 90% rename from src/search/components/Search.tsx rename to src/search/components/input/OldSearchInput.tsx index a983c4e5b6a1..578ef9211efe 100644 --- a/src/search/components/Search.tsx +++ b/src/search/components/input/OldSearchInput.tsx @@ -5,18 +5,19 @@ import { SearchIcon } from '@primer/octicons-react' import { useTranslation } from 'src/languages/components/useTranslation' import { DEFAULT_VERSION, useVersion } from 'src/versions/components/useVersion' -import { useQuery } from 'src/search/components/useQuery' -import { useBreakpoint } from 'src/search/components/useBreakpoint' +import { useQuery } from 'src/search/components/hooks/useQuery' +import { useBreakpoint } from 'src/search/components/hooks/useBreakpoint' import { sendEvent } from 'src/events/components/events' import { EventType } from 'src/events/types' +import { GENERAL_SEARCH_CONTEXT } from '../helpers/execute-search-actions' type Props = { isSearchOpen: boolean } -export function Search({ isSearchOpen }: Props) { +export function OldSearchInput({ isSearchOpen }: Props) { const router = useRouter() const { query, debug } = useQuery() const [localQuery, setLocalQuery] = useState(query) - const { t } = useTranslation('search') + const { t } = useTranslation('old_search') const { currentVersion } = useVersion() const atMediumViewport = useBreakpoint('medium') @@ -56,6 +57,7 @@ export function Search({ isSearchOpen }: Props) { sendEvent({ type: EventType.search, search_query: localQuery, + search_context: GENERAL_SEARCH_CONTEXT, }) redirectSearch() diff --git a/src/search/components/input/README.md b/src/search/components/input/README.md new file mode 100644 index 000000000000..aa826cb4c067 --- /dev/null +++ b/src/search/components/input/README.md @@ -0,0 +1,10 @@ +# Search Input + +This directory contains the view logic (React components) for: + +- The search button (that looks like an input) +- The search overlay (that pops up when you press the search button) + - The search overlay shows autocomplete suggestions as the user types + - If the user selects a general search option, we use [../results](../results) to render a new page with results + - If the user selects an "Ask AI" search option, we show AI Results +- AI Results: This component "takes over" a large part of the search overlay after a user asks AI a question. \ No newline at end of file diff --git a/src/search/components/input/SearchBarButton.module.scss b/src/search/components/input/SearchBarButton.module.scss new file mode 100644 index 000000000000..f6b95a9d3e1b --- /dev/null +++ b/src/search/components/input/SearchBarButton.module.scss @@ -0,0 +1,168 @@ +@use "./variables.scss" as searchVariables; + +@import "@primer/css/support/variables/layout.scss"; +@import "@primer/css/support/mixins/layout.scss"; + +// Shown at smaller widths, just a button +.searchIconButton { + display: flex !important; + align-items: center; + + @include breakpoint(sm) { + display: flex !important; + } + + @include breakpoint(md) { + display: flex !important; + } + + @include breakpoint(lg) { + display: none !important; + } + + @include breakpoint(xl) { + display: none !important; + } +} + +// Only shown at larger widths, a button that looks like an input +.searchInputButton { + position: relative; + z-index: 2; + align-items: center; + + border-radius: 6px; + border: none; + padding: 0; + background-color: var( + --bgColor-default, + var(--color-canvas-default, #ffffff) + ) !important; + + display: none; + width: searchVariables.$smHeaderSearchInputWidth !important; + + @include breakpoint(sm) { + display: none; + } + + @include breakpoint(md) { + display: none; + } + + @include breakpoint(lg) { + display: flex; + width: searchVariables.$lgHeaderSearchInputWidth !important; + } + + @include breakpoint(xl) { + display: flex; + width: searchVariables.$xlHeaderSearchInputWidth !important; + } +} + +.searchInputContainer { + align-items: center; + width: 100%; + height: 2rem; + + pointer-events: none; + user-select: none; + + background-color: var( + --bgColor-default, + var(--color-canvas-default, #ffffff) + ) !important; + + border: 1px solid + var( + --control-borderColor-rest, + var(--borderColor-default, var(--color-border-default, #d0d7de)) + ); + + border-radius: 6px; + border-bottom-right-radius: unset; + border-top-right-radius: unset; + border-right: none; +} + +.searchInputContainer svg { + margin-left: 12px; + overflow: visible !important; + + width: 16; + height: 16; + + fill: currentColor; + color: var(--fgColor-muted, var(--color-fg-muted, #656d76)); +} + +.queryText { + line-height: 2rem; + margin-left: var(--base-size-8, 8px) !important; + font-size: var(--h5-size, 14px) !important; + + /* Hide overflow */ + overflow: hidden; + flex: 1; + justify-self: start; + min-width: 0; /* Essential for proper truncation in some browsers */ + text-overflow: ellipsis; + text-align: left; + white-space: nowrap; +} + +.placeholder { + color: var(--fgColor-muted, var(--color-fg-muted, #656d76)); + font-weight: var(--base-text-weight-normal, 400) !important; +} + +.searchIconContainer { + display: flex; + align-items: center; + justify-content: center; + + // Should be "flat" next to search bar + border: 1px solid; + border-radius: 6px; + border-top-left-radius: unset; + border-bottom-left-radius: unset; + + min-width: 32px; + width: 32px; + height: 32px; + + pointer-events: none; + user-select: none; + + color: var(--fgColor-muted, var(--color-fg-muted, #656d76)); + border-color: var( + --button-default-borderColor-rest, + var( + --button-default-borderColor-rest, + var(--color-btn-border, rgba(31, 35, 40, 0.15)) + ) + ); + background-color: var( + --button-default-bgColor-rest, + var(--color-btn-bg, #f6f8fa) + ); + box-shadow: var( + --button-default-shadow-resting, + var(--color-btn-shadow, 0 1px 0 rgba(31, 35, 40, 0.04)) + ), + var( + --button-default-shadow-inset, + var(--color-btn-inset-shadow, inset 0 1px 0 rgba(255, 255, 255, 0.25)) + ); +} + +.searchIconContainer svg { + overflow: visible !important; + + width: 16; + height: 16; + + fill: currentColor; + color: var(--fgColor-muted, var(--color-fg-muted, #656d76)); +} diff --git a/src/search/components/input/SearchBarButton.tsx b/src/search/components/input/SearchBarButton.tsx new file mode 100644 index 000000000000..e1e4164a0f01 --- /dev/null +++ b/src/search/components/input/SearchBarButton.tsx @@ -0,0 +1,107 @@ +import { useRef } from 'react' +import cx from 'classnames' +import { IconButton, Token } from '@primer/react' +import { SearchIcon, SparklesFillIcon } from '@primer/octicons-react' + +import { useTranslation } from 'src/languages/components/useTranslation' +import { SearchOverlay } from './SearchOverlay' + +import styles from './SearchBarButton.module.scss' +import { useQueryParam } from '@/frame/components/hooks/useQueryParam' + +type Props = { + isSearchOpen: boolean + setIsSearchOpen: (value: boolean) => void +} + +export function SearchBarButton({ isSearchOpen, setIsSearchOpen }: Props) { + const { t } = useTranslation('search') + const { + debug, + queryParam: urlSearchInputQuery, + setQueryParam: setUrlSearchInputQuery, + } = useQueryParam('search-overlay-input') + const { queryParam: isAskAIState, setQueryParam: setIsAskAIState } = useQueryParam( + 'search-overlay-ask-ai', + true, + ) + const buttonRef = useRef(null) + + // Handle click events + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + setIsSearchOpen(true) + } + + // Handle key down events + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' || event.key === 'Space') { + event.preventDefault() + setIsSearchOpen(true) + } else if (event.key === 'Escape') { + event.preventDefault() + setIsSearchOpen(false) + } + } + + return ( + <> + {/* We don't want to show the input when overlay is open */} + {!isSearchOpen ? ( + <> + {/* On mobile only the IconButton is shown */} + + {/* On large and up the SearchBarButton is shown */} + + + ) : ( + { + setIsSearchOpen(false) + }} + /> + )} + + ) +} diff --git a/src/search/components/input/SearchOverlay.module.scss b/src/search/components/input/SearchOverlay.module.scss new file mode 100644 index 000000000000..211744537e14 --- /dev/null +++ b/src/search/components/input/SearchOverlay.module.scss @@ -0,0 +1,99 @@ +@use "./variables.scss" as searchVariables; + +@import "@primer/css/support/variables/layout.scss"; +@import "@primer/css/support/mixins/layout.scss"; + +.overlayBackdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var( + --overlay-backdrop-bgColor, + var(--color-primer-fg-canvas-backdrop, rgba(31, 35, 40, 0.5)) + ); + z-index: 1000; /* Ensure it's above other content other than overlay */ +} + +.overlayContainer { + z-index: 1001; /* Above the backdrop */ + top: 0; + left: 0; + width: searchVariables.$smSearchOverlayWidth !important; + max-width: 100%; + height: auto; + max-height: 90vh; + overflow-y: auto; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + @include breakpoint(sm) { + top: 0 !important; + left: 0 !important; + width: searchVariables.$smSearchOverlayWidth !important; + } + + @include breakpoint(md) { + top: 0 !important; + left: 0 !important; + width: searchVariables.$mdSearchOverlayWidth !important; + } + + @include breakpoint(lg) { + // Using header padding: 8px (p-2 padding) x2 + top: 16px !important; + left: calc(50vw - searchVariables.$lgSearchOverlayWidth / 2) !important; + width: searchVariables.$lgSearchOverlayWidth !important; + } + + @include breakpoint(xl) { + top: 16px !important; + left: calc(50vw - searchVariables.$xlSearchOverlayWidth / 2) !important; + width: searchVariables.$xlSearchOverlayWidth !important; + } +} + +.header { + padding: 12px 14px 0px 14px !important; + width: 100%; + background-color: var(--overlay-bgColor) !important; +} + +.footer { + padding: 5px 16px 13px 16px; + width: 100%; + background-color: var(--overlay-bgColor); +} + +.betaToken { + color: var(--fgColor-success, var(--fgColor-open, green)) !important; + background-color: var(--overlay-bgColor); + margin-right: 1em; +} + +.loadingContainer { + display: flex; + width: 100%; + align-items: center; + justify-content: center; +} + +.suggestionsList { + width: 100%; + max-width: 100%; + // In the rare viewport case where the results are taller than the viewport, scroll + max-height: 95vh; + overflow-y: auto; + overflow-x: hidden; + padding: 4px 0 4px 0 !important; +} + +.errorBanner { + width: 100%; + margin-top: 8px; + margin-bottom: 4px; +} diff --git a/src/search/components/input/SearchOverlay.tsx b/src/search/components/input/SearchOverlay.tsx new file mode 100644 index 000000000000..64f4eec82059 --- /dev/null +++ b/src/search/components/input/SearchOverlay.tsx @@ -0,0 +1,609 @@ +import React, { + useState, + useRef, + RefObject, + useEffect, + KeyboardEvent, + SetStateAction, + useMemo, +} from 'react' +import cx from 'classnames' +import { useRouter } from 'next/router' +import { + ActionList, + Box, + Header, + Link, + Overlay, + Spinner, + Stack, + TextInput, + Token, +} from '@primer/react' +import { + ArrowRightIcon, + SearchIcon, + XCircleFillIcon, + SparklesFillIcon, + ChevronLeftIcon, +} from '@primer/octicons-react' + +import { useTranslation } from 'src/languages/components/useTranslation' +import { useVersion } from 'src/versions/components/useVersion' +import { executeGeneralSearch } from '../helpers/execute-search-actions' + +import styles from './SearchOverlay.module.scss' +import { Banner } from '@primer/react/drafts' +import { AutocompleteSearchHit } from '@/search/types' +import { useAISearchAutocomplete } from '@/search/components/hooks/useAISearchAutocomplete' +import { AskAIResults } from './AskAIResults' +import { uuidv4 } from '@/events/components/events' + +type Props = { + searchOverlayOpen: boolean + parentRef: RefObject + debug: boolean + urlSearchInputQuery: string + setUrlSearchInputQuery: (value: string) => void + isAskAIState: boolean + setIsAskAIState: (value: boolean) => void + onClose: () => void +} + +// Upon clicking the SearchInput component this overlay will be displayed +export function SearchOverlay({ + searchOverlayOpen, + parentRef, + debug, + urlSearchInputQuery, + setUrlSearchInputQuery, + isAskAIState, + setIsAskAIState, + onClose, +}: Props) { + const { t } = useTranslation('search') + const { currentVersion } = useVersion() + const router = useRouter() + + const inputRef = useRef(null) + const suggestionsListHeightRef = useRef(null) + // We need an array of refs to the list elements so we can focus them when the user uses the arrow keys + const listElementsRef = React.useRef>([]) + + const [selectedIndex, setSelectedIndex] = useState(0) + const [aiQuery, setAiQuery] = useState(urlSearchInputQuery) + const [aiSearchError, setAISearchError] = useState(false) + + // Group all events between open / close of the overlay together + const searchEventGroupId = useRef('') + useEffect(() => { + searchEventGroupId.current = uuidv4() + }, [searchOverlayOpen]) + + const { + autoCompleteOptions, + searchLoading, + searchError: autoCompleteSearchError, + updateAutocompleteResults, + clearAutocompleteResults, + } = useAISearchAutocomplete({ + router, + currentVersion, + debug, + eventGroupIdRef: searchEventGroupId, + }) + + const { aiAutocompleteOptions } = autoCompleteOptions + + // Filter out any options that match the local query and replace them with a custom user query option that include isUserQuery: true + const filteredAiOptions = aiAutocompleteOptions.filter( + (option) => option.term !== urlSearchInputQuery, + ) + + // Create new arrays that prepend the user input + const userInputOptions = + urlSearchInputQuery.trim() !== '' + ? [{ term: urlSearchInputQuery, highlights: [], isUserQuery: true }] + : [] + const generalOptionsWithUserInput = [...userInputOptions] + const aiOptionsWithUserInput = [...userInputOptions, ...filteredAiOptions] + + // Combine options for key navigation + const combinedOptions = [] as Array<{ + group: 'general' | 'ai' | string + option: AutocompleteSearchHitWithUserQuery + }> + // On AI Error, don't include AI suggestions, only user input + if (!aiSearchError) { + combinedOptions.push(...aiOptionsWithUserInput.map((option) => ({ group: 'ai', option }))) + } + // NOTE: Order of combinedOptions is important, since 'selectedIndex' is used to navigate the combinedOptions array + // Add general options after AI options + combinedOptions.push( + ...generalOptionsWithUserInput.map((option) => ({ group: 'general', option })), + ) + + // Fetch initial search results on open + useEffect(() => { + if (searchOverlayOpen && !isAskAIState) { + searchEventGroupId.current = uuidv4() + updateAutocompleteResults(urlSearchInputQuery) + } + return () => { + clearAutocompleteResults() + } + // We need to update when isAskAIState changes, because we might start a session in the "Ask AI" state, and then switch to the "Search" state + // In this scenario we don't have pre-existing autocomplete results to show, so we need to fetch them + // Additionally, the query may change in the "Ask AI" state, so we need to update the results when we switch back to the "Search" state + }, [searchOverlayOpen, updateAutocompleteResults, clearAutocompleteResults, isAskAIState]) + + // For keyboard controls, we need to use a ref for the list elements that updates when the options change + useEffect(() => { + listElementsRef.current = listElementsRef.current.slice( + 0, + generalOptionsWithUserInput.length + aiOptionsWithUserInput.length, + ) + }, [generalOptionsWithUserInput, aiOptionsWithUserInput]) + + // When loading, capture the last height of the suggestions list so we can use it for the loading div + const previousSuggestionsListHeight = useMemo(() => { + if (suggestionsListHeightRef.current?.clientHeight) { + return suggestionsListHeightRef.current.clientHeight + } else { + return '250' // Default height that looks very close to 5 suggestions (in px) + } + }, [searchLoading]) + + // When the user types in the search input, update the local query and fetch autocomplete results + const handleSearchQueryChange = (event: React.ChangeEvent) => { + const newQuery = event.target.value + setUrlSearchInputQuery(newQuery) + setSelectedIndex(0) // Reset selected index when query changes + // We don't need to fetch autocomplete results when asking the AI + if (!isAskAIState) { + updateAutocompleteResults(newQuery) + } + } + + // When a general option is selected, execute the search and close the overlay since general search results are in a new page + const generalSearchOptionOnSelect = (selectedOption: AutocompleteSearchHit) => { + if (selectedOption.term) { + executeGeneralSearch( + router, + currentVersion, + selectedOption.term, + debug, + searchEventGroupId.current, + ) + onClose() + } + } + + // When an AI option is selected, set the AI query and focus the input since ask AI results replace the suggestions + const aiSearchOptionOnSelect = (selectedOption: AutocompleteSearchHit) => { + if (selectedOption.term) { + setIsAskAIState(true) + setUrlSearchInputQuery(selectedOption.term) + setAiQuery(selectedOption.term) + inputRef.current?.focus() + } + } + + // On keydown can be called from the input or a list item + // In either case, we want to deal with both focus & selection when up, down, or enter are pressed + const handleKeyDown = ( + event: React.KeyboardEvent, + // Passed when called from a list item's handler + manuallyPassedIndex?: number, + ) => { + if (event.key === 'ArrowDown') { + event.preventDefault() + if (combinedOptions.length > 0) { + let newIndex = 0 + if (typeof manuallyPassedIndex !== 'undefined') { + newIndex = (manuallyPassedIndex + 1) % combinedOptions.length + } else { + newIndex = (selectedIndex + 1) % combinedOptions.length + } + setSelectedIndex(newIndex) + if (listElementsRef.current?.[newIndex]) { + listElementsRef.current?.[newIndex]?.focus() + } + } + } else if (event.key === 'ArrowUp') { + event.preventDefault() + if (manuallyPassedIndex === 0 || selectedIndex === 0) { + // Focus the input when the first item is selected + inputRef.current?.focus() + } else if (combinedOptions.length > 0) { + let newIndex = 0 + if (typeof manuallyPassedIndex !== 'undefined') { + newIndex = (manuallyPassedIndex - 1 + combinedOptions.length) % combinedOptions.length + } else { + newIndex = (selectedIndex - 1 + combinedOptions.length) % combinedOptions.length + } + setSelectedIndex(newIndex) + if (listElementsRef.current?.[newIndex]) { + listElementsRef.current?.[newIndex]?.focus() + } + } else { + inputRef.current?.focus() + } + } else if (event.key === 'Enter') { + // When AI Search is already open, ask subsequent queries + if (isAskAIState && !aiSearchError) { + if (isAskAIState && urlSearchInputQuery === aiQuery) { + // User has typed same query and pressed enter. Do nothing + return + } else { + event.preventDefault() + return aiSearchOptionOnSelect({ term: urlSearchInputQuery } as AutocompleteSearchHit) + } + } + + event.preventDefault() + + if ( + combinedOptions.length > 0 && + selectedIndex >= 0 && + selectedIndex < combinedOptions.length + ) { + const selectedItem = combinedOptions[selectedIndex] + if (selectedItem.group === 'general') { + generalSearchOptionOnSelect(selectedItem.option) + } else if (selectedItem.group === 'ai') { + aiSearchOptionOnSelect(selectedItem.option) + } + } + } else if (event.key === 'Escape') { + event.preventDefault() + onClose() // Close the input overlay when Escape is pressed + } + } + + // We display different content in the overlay based: + // 1. If either search (autocomplete results or ask AI) has an error + // 2. The user has selected an AI query and we are showing the ask AI results + // 3. The search is loading + // 4. Otherwise, we show the autocomplete suggestions + let OverlayContents = null + // We can still ask AI if there is an autocomplete search error + const inErrorState = aiSearchError || (autoCompleteSearchError && !isAskAIState) + if (inErrorState) { + OverlayContents = ( + <> + + {renderSearchGroups( + t, + autoCompleteSearchError ? userInputOptions : generalOptionsWithUserInput, + aiSearchError ? [] : aiOptionsWithUserInput, + generalSearchOptionOnSelect, + aiSearchOptionOnSelect, + selectedIndex, + setSelectedIndex, + listElementsRef, + handleKeyDown, + )} + + {/* Always show the AI Search UI error message when it is needed */} + {aiSearchError && ( +
    + + + {t('search.overlay.ai_autocomplete_list_heading')} + + +
    + )} + {/* Only show the autocomplete search UI error message in Dev */} + {process.env.NODE_ENV === 'development' && autoCompleteSearchError && !aiSearchError && ( + + )} + + ) + } else if (isAskAIState) { + OverlayContents = ( + { + setAISearchError(true) + }} + /> + ) + } else if (searchLoading) { + OverlayContents = ( + + + + ) + } else { + OverlayContents = ( + + {renderSearchGroups( + t, + generalOptionsWithUserInput, + aiOptionsWithUserInput, + generalSearchOptionOnSelect, + aiSearchOptionOnSelect, + selectedIndex, + setSelectedIndex, + listElementsRef, + handleKeyDown, + )} + + ) + } + + const overlayHeadingId = 'overlay-heading' + return ( + <> +
    + +
    + + { + setIsAskAIState(false) + }} + icon={ChevronLeftIcon} + aria-label={t('search.overlay.return_to_search')} + /> + + ) : ( + + ) + } + aria-labelledby={overlayHeadingId} + placeholder={t('search.input.placeholder')} + trailingAction={ + + { + setUrlSearchInputQuery('') + if (!isAskAIState) { + updateAutocompleteResults('') + } + }} + icon={XCircleFillIcon} + aria-label={t('search.overlay.clear_search_query')} + /> + + } + /> +
    +
    + + ) +} + +interface AutocompleteSearchHitWithUserQuery extends AutocompleteSearchHit { + isUserQuery?: boolean +} + +// Render the autocomplete suggestions with AI suggestions first, headings, and a divider between the two +function renderSearchGroups( + t: any, + generalOptionsWithUserInput: AutocompleteSearchHitWithUserQuery[], + aiOptionsWithUserInput: AutocompleteSearchHitWithUserQuery[], + generalAutocompleteOnSelect: (selectedOption: AutocompleteSearchHit) => void, + aiAutocompleteOnSelect: (selectedOption: AutocompleteSearchHit) => void, + selectedIndex: number, + setSelectedIndex: (value: SetStateAction) => void, + listElementsRef: RefObject>, + handleKeyDown: ( + event: KeyboardEvent, + manuallyPassedIndex?: number | undefined, + ) => void, +) { + const groups = [] + + if (aiOptionsWithUserInput.length) { + groups.push( + + + + {t('search.overlay.ai_autocomplete_list_heading')} + + {aiOptionsWithUserInput.map((option: AutocompleteSearchHitWithUserQuery, index: number) => { + const isActive = selectedIndex === index + const item = ( + aiAutocompleteOnSelect(option)} + onKeyDown={(e: React.KeyboardEvent) => { + handleKeyDown(e, index) + }} + onFocus={() => { + setSelectedIndex(index) + }} + active={isActive} + tabIndex={0} + ref={(element) => { + if (listElementsRef.current) { + listElementsRef.current[index] = element + } + }} + > + + + + {option.term} + + + + + ) + return item + })} + , + ) + if (generalOptionsWithUserInput.length) { + groups.push() + } + } + + if (generalOptionsWithUserInput.length) { + groups.push( + + + {t('search.overlay.general_autocomplete_list_heading')} + + {generalOptionsWithUserInput.map( + (option: AutocompleteSearchHitWithUserQuery, index: number) => { + // Since AI Search comes first, we need to add an offset for general search options + const indexWithOffset = aiOptionsWithUserInput.length + index + const isActive = selectedIndex === indexWithOffset + const item = ( + generalAutocompleteOnSelect(option)} + onKeyDown={(e) => { + handleKeyDown(e, indexWithOffset) + }} + onFocus={() => { + setSelectedIndex(indexWithOffset) + }} + active={isActive} + tabIndex={0} + ref={(element) => { + if (listElementsRef.current) { + listElementsRef.current[indexWithOffset] = element + } + }} + > + + + + {option.term} + + + + + ) + return item + }, + )} + , + ) + } + + return groups +} diff --git a/src/search/components/input/variables.scss b/src/search/components/input/variables.scss new file mode 100644 index 000000000000..35fd606713ad --- /dev/null +++ b/src/search/components/input/variables.scss @@ -0,0 +1,11 @@ +// Widths of the search bar button at different breakpoints +$smHeaderSearchInputWidth: 100%; // Technically we don't show the search bar at this breakpoint +$mdHeaderSearchInputWidth: 100%; // Technically we don't show the search bar at this breakpoint +$lgHeaderSearchInputWidth: 30rem; +$xlHeaderSearchInputWidth: 40rem; + +// Widths of the search overlay popup at different breakpoints +$smSearchOverlayWidth: 100vw; +$mdSearchOverlayWidth: 100vw; +$lgSearchOverlayWidth: 40rem; +$xlSearchOverlayWidth: 50rem; diff --git a/src/search/components/Aggregations.tsx b/src/search/components/results/Aggregations.tsx similarity index 100% rename from src/search/components/Aggregations.tsx rename to src/search/components/results/Aggregations.tsx diff --git a/src/search/components/NoQuery.tsx b/src/search/components/results/NoQuery.tsx similarity index 93% rename from src/search/components/NoQuery.tsx rename to src/search/components/results/NoQuery.tsx index 753d450344db..e3e121560395 100644 --- a/src/search/components/NoQuery.tsx +++ b/src/search/components/results/NoQuery.tsx @@ -4,7 +4,7 @@ import { useMainContext } from 'src/frame/components/context/MainContext' import { useTranslation } from 'src/languages/components/useTranslation' export function NoQuery() { - const { t } = useTranslation(['search']) + const { t } = useTranslation('old_search') const mainContext = useMainContext() // Use TypeScript's "not null assertion" because `context.page` should // will present in main context if it's gotten to the stage of React diff --git a/src/search/components/results/README.md b/src/search/components/results/README.md new file mode 100644 index 000000000000..22544d71224e --- /dev/null +++ b/src/search/components/results/README.md @@ -0,0 +1,6 @@ +# Search Results + +This directory contains the view logic (React components) for: + +- Search Results: When a user performs a general search, we show this page with a list of search results +- Sidebar Aggregates: On larger widths we show the "categories" or "aggregates" of the search results that a user can select to filter their results \ No newline at end of file diff --git a/src/search/components/SearchResults.module.scss b/src/search/components/results/SearchResults.module.scss similarity index 100% rename from src/search/components/SearchResults.module.scss rename to src/search/components/results/SearchResults.module.scss diff --git a/src/search/components/SearchResults.tsx b/src/search/components/results/SearchResults.tsx similarity index 97% rename from src/search/components/SearchResults.tsx rename to src/search/components/results/SearchResults.tsx index f12843cd5cd2..fb63bd69aea0 100644 --- a/src/search/components/SearchResults.tsx +++ b/src/search/components/results/SearchResults.tsx @@ -14,6 +14,7 @@ import styles from './SearchResults.module.scss' import type { SearchQueryContentT } from 'src/search/components/types' import type { GeneralSearchHitWithoutIncludes, GeneralSearchResponse } from 'src/search/types' import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types' +import { GENERAL_SEARCH_RESULTS } from '@/events/components/event-groups' type Props = { results: GeneralSearchResponse @@ -105,6 +106,7 @@ function SearchResultHit({ href={hit.url} className="color-fg-accent search-result-link" dangerouslySetInnerHTML={{ __html: title }} + data-group-key={GENERAL_SEARCH_RESULTS} onClick={() => { sendEvent({ type: EventType.searchResult, diff --git a/src/search/components/SidebarSearchAggregates.tsx b/src/search/components/results/SidebarSearchAggregates.tsx similarity index 85% rename from src/search/components/SidebarSearchAggregates.tsx rename to src/search/components/results/SidebarSearchAggregates.tsx index 54dbb112c93d..0646f5cb4906 100644 --- a/src/search/components/SidebarSearchAggregates.tsx +++ b/src/search/components/results/SidebarSearchAggregates.tsx @@ -1,4 +1,4 @@ -import { useSearchContext } from './context/SearchContext' +import { useSearchContext } from '../context/SearchContext' import { SearchResultsAggregations } from './Aggregations' export function SidebarSearchAggregates() { diff --git a/src/search/components/ValidationErrors.tsx b/src/search/components/results/ValidationErrors.tsx similarity index 90% rename from src/search/components/ValidationErrors.tsx rename to src/search/components/results/ValidationErrors.tsx index ce8bf15c5577..75f9e6b74bf7 100644 --- a/src/search/components/ValidationErrors.tsx +++ b/src/search/components/results/ValidationErrors.tsx @@ -1,7 +1,7 @@ import { Flash } from '@primer/react' import { useTranslation } from 'src/languages/components/useTranslation' -import type { SearchValidationErrorEntry } from '../types' +import type { SearchValidationErrorEntry } from '../../types' interface Props { errors: SearchValidationErrorEntry[] diff --git a/src/search/components/index.tsx b/src/search/components/results/index.tsx similarity index 88% rename from src/search/components/index.tsx rename to src/search/components/results/index.tsx index 26fc041be6a6..242dfa78187c 100644 --- a/src/search/components/index.tsx +++ b/src/search/components/results/index.tsx @@ -3,11 +3,11 @@ import { Heading } from '@primer/react' import { useTranslation } from 'src/languages/components/useTranslation' import { DEFAULT_VERSION, useVersion } from 'src/versions/components/useVersion' -import { useNumberFormatter } from 'src/search/components/useNumberFormatter' -import { SearchResults } from 'src/search/components/SearchResults' -import { NoQuery } from 'src/search/components/NoQuery' +import { useNumberFormatter } from 'src/search/components/hooks/useNumberFormatter' +import { SearchResults } from 'src/search/components/results/SearchResults' +import { NoQuery } from 'src/search/components/results/NoQuery' import { useMainContext } from 'src/frame/components/context/MainContext' -import { ValidationErrors } from 'src/search/components/ValidationErrors' +import { ValidationErrors } from 'src/search/components/results/ValidationErrors' import { useSearchContext } from 'src/search/components/context/SearchContext' import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types' diff --git a/src/search/lib/ai-search-proxy.ts b/src/search/lib/ai-search-proxy.ts index 9ddfdecb02a8..cc75d605f2f2 100644 --- a/src/search/lib/ai-search-proxy.ts +++ b/src/search/lib/ai-search-proxy.ts @@ -40,29 +40,48 @@ export const aiSearchProxy = async (req: Request, res: Response) => { } try { - const stream = got.post(`${process.env.CSE_COPILOT_ENDPOINT}/answers`, { + const stream = got.stream.post(`${process.env.CSE_COPILOT_ENDPOINT}/answers`, { json: body, headers: { Authorization: getHmacWithEpoch(), 'Content-Type': 'application/json', }, - isStream: true, }) - // Set response headers - res.setHeader('Content-Type', 'application/x-ndjson') - res.flushHeaders() + // Handle the upstream response before piping + stream.on('response', (upstreamResponse) => { + if (upstreamResponse.statusCode !== 200) { + const errorMessage = `Upstream server responded with status code ${upstreamResponse.statusCode}` + console.error(errorMessage) + res.status(500).json({ errors: [{ message: errorMessage }] }) + stream.destroy() + } else { + // Set response headers + res.setHeader('Content-Type', 'application/x-ndjson') + res.flushHeaders() - // Pipe the got stream directly to the response - stream.pipe(res) + // Pipe the got stream directly to the response + stream.pipe(res) + } + }) // Handle stream errors - stream.on('error', (error) => { + stream.on('error', (error: any) => { console.error('Error streaming from cse-copilot:', error) - // Only send error response if headers haven't been sent + + if (error?.code === 'ERR_NON_2XX_3XX_RESPONSE') { + return res + .status(400) + .json({ errors: [{ message: 'Sorry I am unable to answer this question.' }] }) + } + if (!res.headersSent) { res.status(500).json({ errors: [{ message: 'Internal server error' }] }) } else { + // Send error message via the stream + const errorMessage = + JSON.stringify({ errors: [{ message: 'Internal server error' }] }) + '\n' + res.write(errorMessage) res.end() } }) diff --git a/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts b/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts index 4d63dd62d3a9..1a3bdab704b4 100644 --- a/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts +++ b/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts @@ -14,38 +14,53 @@ export async function getAISearchAutocompleteResults({ indexName, query, size, + debug = false, }: AutocompleteResultsArgs): Promise { const t0 = new Date() const client = getElasticsearchClient() as Client - const matchQueries = getAISearchAutocompleteMatchQueries(query.trim(), { - fuzzy: { - minLength: 3, - maxLength: 20, - }, - }) - const matchQuery = { - bool: { - should: matchQueries, - }, - } - - const highlight = getHighlightConfiguration(query, ['term']) - - const searchQuery = { + let searchQuery: any = { index: indexName, - highlight, size, - query: matchQuery, + // Send absolutely minimal from Elasticsearch to here. Less data => faster. _source_includes: ['term'], } + const trimmedQuery = query.trim() + // When the query is empty, we want to return the top `size` most popular terms + if (trimmedQuery === '') { + searchQuery.query = { match_all: {} } + searchQuery.sort = [{ popularity: { order: 'desc' } }] + } else { + const matchQueries = getAISearchAutocompleteMatchQueries(trimmedQuery, { + fuzzy: { + minLength: 3, + maxLength: 20, + }, + }) + const matchQuery: QueryDslQueryContainer = { + bool: { + should: matchQueries, + }, + } + + searchQuery.query = matchQuery + searchQuery.highlight = getHighlightConfiguration(trimmedQuery, ['term']) + } + const result = await client.search<{ term: string }>(searchQuery) const hitsAll = result.hits const hits = hitsAll.hits.map((hit) => ({ term: hit._source?.term, highlights: (hit.highlight && hit.highlight.term) || [], + ...(debug && { + score: hit._score ?? 0.0, + es_url: + process.env.NODE_ENV !== 'production' + ? `http://localhost:9200/${indexName}/_doc/${hit._id}` + : '', + }), })) return { diff --git a/src/search/lib/get-elasticsearch-results/general-autocomplete.ts b/src/search/lib/get-elasticsearch-results/general-autocomplete.ts index 0f3653940e72..57ff42dd7f04 100644 --- a/src/search/lib/get-elasticsearch-results/general-autocomplete.ts +++ b/src/search/lib/get-elasticsearch-results/general-autocomplete.ts @@ -15,39 +15,62 @@ export async function getAutocompleteSearchResults({ indexName, query, size, + debug = false, }: AutocompleteResultsArgs): Promise { const t0 = new Date() const client = getElasticsearchClient() as Client - const matchQueries = getAutocompleteMatchQueries(query.trim(), { - fuzzy: { - minLength: 3, - maxLength: 20, - }, - }) - const matchQuery = { - bool: { - should: matchQueries, - }, - } - - const highlight = getHighlightConfiguration(query, ['term']) - - const searchQuery = { + let searchQuery: any = { index: indexName, - highlight, size, - query: matchQuery, // Send absolutely minimal from Elasticsearch to here. Less data => faster. _source_includes: ['term'], } + const trimmedQuery = query.trim() + // When the query is empty, return no results + if (trimmedQuery === '') { + return { + meta: { + found: { + value: 0, + relation: 'eq', + }, + took: { query_msec: 0, total_msec: new Date().getTime() - t0.getTime() }, + size, + }, + hits: [], + } + } else { + const matchQueries = getAutocompleteMatchQueries(trimmedQuery, { + fuzzy: { + minLength: 3, + maxLength: 20, + }, + }) + const matchQuery: QueryDslQueryContainer = { + bool: { + should: matchQueries, + }, + } + + searchQuery.query = matchQuery + searchQuery.highlight = getHighlightConfiguration(trimmedQuery, ['term']) + } + const result = await client.search(searchQuery) const hitsAll = result.hits const hits = hitsAll.hits.map((hit) => ({ term: hit._source?.term, highlights: (hit.highlight && hit.highlight.term) || [], + ...(debug && { + score: hit._score ?? 0.0, + es_url: + process.env.NODE_ENV !== 'production' + ? `http://localhost:9200/${indexName}/_doc/${hit._id}` + : '', + }), })) return { diff --git a/src/search/lib/get-elasticsearch-results/types.ts b/src/search/lib/get-elasticsearch-results/types.ts index da6fa59f5612..eaf5e211bf8e 100644 --- a/src/search/lib/get-elasticsearch-results/types.ts +++ b/src/search/lib/get-elasticsearch-results/types.ts @@ -2,6 +2,7 @@ export interface AutocompleteResultsArgs { indexName: string query: string size: number + debug?: boolean } export interface FuzzyConfig { diff --git a/src/search/lib/helpers/cse-copilot-docs-versions.ts b/src/search/lib/helpers/cse-copilot-docs-versions.ts index 9b96aa9ddcf5..0f617fc49a89 100644 --- a/src/search/lib/helpers/cse-copilot-docs-versions.ts +++ b/src/search/lib/helpers/cse-copilot-docs-versions.ts @@ -1,5 +1,6 @@ // Versions used by cse-copilot import { allVersions } from '@/versions/lib/all-versions' +import { versionToIndexVersionMap } from '../elasticsearch-versions' const CSE_COPILOT_DOCS_VERSIONS = ['dotcom', 'ghec', 'ghes'] // Languages supported by cse-copilot @@ -12,7 +13,8 @@ export function getCSECopilotSource( version: (typeof CSE_COPILOT_DOCS_VERSIONS)[number], language: (typeof DOCS_LANGUAGES)[number], ) { - const cseCopilotDocsVersion = getMiscBaseNameFromVersion(version) + const mappedVersion = versionToIndexVersionMap[version] + const cseCopilotDocsVersion = getMiscBaseNameFromVersion(mappedVersion) if (!CSE_COPILOT_DOCS_VERSIONS.includes(cseCopilotDocsVersion)) { throw new Error( `Invalid 'version' in request body: '${version}'. Must be one of: ${CSE_COPILOT_DOCS_VERSIONS.join(', ')}`, @@ -23,7 +25,7 @@ export function getCSECopilotSource( `Invalid 'language' in request body '${language}'. Must be one of: ${DOCS_LANGUAGES.join(', ')}`, ) } - return `docs_${version}_${language}` + return `docs_${cseCopilotDocsVersion}_${language}` } function getMiscBaseNameFromVersion(Version: string): string { diff --git a/src/search/lib/routes/combined-autocomplete-route.ts b/src/search/lib/routes/combined-autocomplete-route.ts new file mode 100644 index 000000000000..6c347085bcb2 --- /dev/null +++ b/src/search/lib/routes/combined-autocomplete-route.ts @@ -0,0 +1,120 @@ +import { getSearchFromRequestParams } from '@/search/lib/search-request-params/get-search-from-request-params' +import { getAISearchAutocompleteResults } from '@/search/lib/get-elasticsearch-results/ai-search-autocomplete' +import { getAutocompleteSearchResults } from '@/search/lib/get-elasticsearch-results/general-autocomplete' +import { searchCacheControl } from '@/frame/middleware/cache-control' +import { SURROGATE_ENUMS, setFastlySurrogateKey } from '@/frame/middleware/set-fastly-surrogate-key' +import { handleGetSearchResultsError } from '@/search/middleware/search-routes' + +import type { Request, Response } from 'express' +import type { CombinedAutocompleteSearchResponse } from '@/search/types' + +interface CacheEntry { + timestamp: number + data: CombinedAutocompleteSearchResponse +} + +// We want to cache when the query is just an empty string +// These are the defaults that are shown when the user first opens the search overlay +const autocompleteCache = new Map() + +// Within 24 hours the top autocomplete options might be updated +const EMPTY_QUERY_CACHE_KEY = 'emptyQueries' +const CACHE_DURATION_MS = 24 * 60 * 60 * 1000 // 24 hours +const size = 5 + +export async function combinedAutocompleteRoute(req: Request, res: Response) { + const { + indexName: aiIndexName, + validationErrors: aiValidationErrors, + searchParams: { query: aiQuery, debug }, + } = getSearchFromRequestParams(req, 'aiSearchAutocomplete', { + // Force query to override validation to allow empty string + query: typeof req.query.query !== 'string' ? '' : req.query.query, + }) + + const { + indexName: generalIndexName, + validationErrors: generalValidationErrors, + searchParams: { query: generalQuery }, + } = getSearchFromRequestParams(req, 'generalAutocomplete', { + query: typeof req.query.query !== 'string' ? '' : req.query.query, + }) + + const combinedValidationErrors = aiValidationErrors.concat(generalValidationErrors) + if (combinedValidationErrors.length) { + return res.status(400).json(combinedValidationErrors[0]) + } + + // Check if both queries are empty + const isEmptyQuery = + (!aiQuery || aiQuery.trim() === '') && (!generalQuery || generalQuery.trim() === '') + + if (isEmptyQuery) { + const cached = autocompleteCache.get(EMPTY_QUERY_CACHE_KEY) + const now = Date.now() + + if (cached && now - cached.timestamp < CACHE_DURATION_MS) { + // Serve cached results + return res.status(200).json(cached.data) + } + } + + try { + // Async fetch both results from Elasticsearch + const [aiSearchResults, generalSearchResults] = await Promise.all([ + getAISearchAutocompleteResults({ + indexName: aiIndexName, + query: aiQuery, + size, + debug, + }), + getAutocompleteSearchResults({ + indexName: generalIndexName, + query: generalQuery, + size, + debug, + }), + ]) + + if (process.env.NODE_ENV !== 'development') { + searchCacheControl(res) + setFastlySurrogateKey(res, SURROGATE_ENUMS.MANUAL) + } + + const results: CombinedAutocompleteSearchResponse = { + aiAutocomplete: { + meta: aiSearchResults.meta, + hits: aiSearchResults.hits, + }, + generalAutocomplete: { + meta: generalSearchResults.meta, + hits: generalSearchResults.hits, + }, + } + + // If both queries are empty, cache the results + if (isEmptyQuery) { + autocompleteCache.set(EMPTY_QUERY_CACHE_KEY, { + timestamp: Date.now(), + data: results, + }) + } + + res.status(200).json(results) + } catch (error) { + await handleGetSearchResultsError( + req, + res, + error, + JSON.stringify( + { + indexName: { aiIndexName, generalIndexName }, + query: { aiQuery, generalQuery }, + size, + }, + null, + 2, + ), + ) + } +} diff --git a/src/search/middleware/search-routes.ts b/src/search/middleware/search-routes.ts index b7e7f89bed06..72327e37fcf0 100644 --- a/src/search/middleware/search-routes.ts +++ b/src/search/middleware/search-routes.ts @@ -3,6 +3,7 @@ For general search (client searches on docs.github.com) we use the middleware in ./general-search-middleware to get the search results */ +// TODO: Move the routes implementations in this files to lib/routes so you can at-a-glance see all of the routes without the implementation logic import express, { Request, Response } from 'express' import FailBot from '@/observability/lib/failbot.js' @@ -16,13 +17,14 @@ import { getAutocompleteSearchResults } from '@/search/lib/get-elasticsearch-res import { getAISearchAutocompleteResults } from '@/search/lib/get-elasticsearch-results/ai-search-autocomplete' import { getSearchFromRequestParams } from '@/search/lib/search-request-params/get-search-from-request-params' import { getGeneralSearchResults } from '@/search/lib/get-elasticsearch-results/general-search' -import { createRateLimiter } from '#src/shielding/middleware/rate-limit.js' +import { combinedAutocompleteRoute } from '@/search/lib/routes/combined-autocomplete-route' +import { createRateLimiter } from '@/shielding/middleware/rate-limit.js' const router = express.Router() if (process.env.NODE_ENV === 'development') { router.use(createRateLimiter(10)) // just 1 worker in dev so 10 requests per minute allowed } else if (process.env.NODE_ENV === 'production') { - router.use(createRateLimiter(1)) // 1 * 25 requests per minute for prod + router.use(createRateLimiter(30)) // 30 requests per minute allowed } router.get('/legacy', (req: Request, res: Response) => { @@ -69,7 +71,7 @@ router.get( const { indexName, validationErrors, - searchParams: { query, size }, + searchParams: { query, size, debug }, } = getSearchFromRequestParams(req, 'generalAutocomplete') if (validationErrors.length) { return res.status(400).json(validationErrors[0]) @@ -79,6 +81,7 @@ router.get( indexName, query, size, + debug, } try { const { meta, hits } = await getAutocompleteSearchResults(options) @@ -98,11 +101,18 @@ router.get( router.get( '/ai-search-autocomplete/v1', catchMiddlewareError(async (req: Request, res: Response) => { + // If no query is provided, we want to return the top 5 most popular terms + // This is a special case for AI search autocomplete + // So we use `force` to allow the query to be empty without the usual validation error + let force = {} as any + if (!req.query.query) { + force.query = '' + } const { indexName, validationErrors, - searchParams: { query, size }, - } = getSearchFromRequestParams(req, 'aiSearchAutocomplete') + searchParams: { query, size, debug }, + } = getSearchFromRequestParams(req, 'aiSearchAutocomplete', force) if (validationErrors.length) { return res.status(400).json(validationErrors[0]) } @@ -111,6 +121,7 @@ router.get( indexName, query, size, + debug, } try { const { meta, hits } = await getAISearchAutocompleteResults(getResultOptions) @@ -127,7 +138,20 @@ router.get( }), ) -async function handleGetSearchResultsError(req: Request, res: Response, error: any, options: any) { +// Route used by our frontend to fetch ai & general autocomplete search results in a single request +router.get( + '/combined-autocomplete/v1', + catchMiddlewareError(async (req: Request, res: Response) => { + combinedAutocompleteRoute(req, res) + }), +) + +export async function handleGetSearchResultsError( + req: Request, + res: Response, + error: any, + options: any, +) { if (process.env.NODE_ENV === 'development') { console.error(`Error calling getSearchResults(${options})`, error) } else { @@ -137,7 +161,7 @@ async function handleGetSearchResultsError(req: Request, res: Response, error: a res.status(500).json({ error: error.message }) } -// Redirects for latest versions +// Redirects search routes to their latest versions router.get('/', (req: Request, res: Response) => { res.redirect(307, req.originalUrl.replace('/search', '/search/v1')) }) @@ -153,4 +177,11 @@ router.get('/ai-search-autocomplete', (req: Request, res: Response) => { ) }) +router.get('/combined-autocomplete', (req: Request, res: Response) => { + res.redirect( + 307, + req.originalUrl.replace('/search/combined-autocomplete', '/search/combined-autocomplete/v1'), + ) +}) + export default router diff --git a/src/search/pages/search.tsx b/src/search/pages/search-results.tsx similarity index 97% rename from src/search/pages/search.tsx rename to src/search/pages/search-results.tsx index 55c0333a2e25..a043ad3dc86a 100644 --- a/src/search/pages/search.tsx +++ b/src/search/pages/search-results.tsx @@ -8,7 +8,7 @@ import { } from 'src/frame/components/context/MainContext' import { DefaultLayout } from 'src/frame/components/DefaultLayout' import { SearchContext } from 'src/search/components/context/SearchContext' -import { Search } from 'src/search/components/index' +import { Search } from 'src/search/components/results/index' import { SearchOnReqObject } from 'src/search/types' import type { SearchContextT } from 'src/search/components/types' diff --git a/src/search/tests/api-ai-search-autocomplete.ts b/src/search/tests/api-ai-search-autocomplete.ts index 191f1854a287..2275497d0e9a 100644 --- a/src/search/tests/api-ai-search-autocomplete.ts +++ b/src/search/tests/api-ai-search-autocomplete.ts @@ -66,7 +66,7 @@ describeIfElasticsearchURL('search/ai-search-autocomplete v1 middleware', () => const sp = new URLSearchParams() sp.set('query', 'fo') sp.set('version', 'never-heard-of') - const res = await get(`${aiSearchEndpoint}?{sp}`) + const res = await get(`${aiSearchEndpoint}?${sp}`) expect(res.statusCode).toBe(400) expect(JSON.parse(res.body).error).toBeTruthy() }) @@ -134,31 +134,24 @@ describeIfElasticsearchURL('search/ai-search-autocomplete v1 middleware', () => const res = await get(getSearchEndpointWithParams(sp)) expect(res.statusCode).toBe(200) const results = JSON.parse(res.body) as AutocompleteSearchResponse - console.log(JSON.stringify(results, null, 2)) const hit = results.hits[0] expect(hit.term).toBe('How do I clone a repository?') expect(hit.highlights).toBeTruthy() expect(hit.highlights[0]).toBe('How do I clone a repository?') }) - test('invalid query', async () => { + test('support empty query', async () => { const sp = new URLSearchParams() // No query at all { const res = await get(getSearchEndpointWithParams(sp)) - expect(res.statusCode).toBe(400) + expect(res.statusCode).toBe(200) } // Empty query { sp.set('query', '') const res = await get(getSearchEndpointWithParams(sp)) - expect(res.statusCode).toBe(400) - } - // Empty when trimmed - { - sp.set('query', ' ') - const res = await get(getSearchEndpointWithParams(sp)) - expect(res.statusCode).toBe(400) + expect(res.statusCode).toBe(200) } }) }) diff --git a/src/search/tests/api-combined-search.ts b/src/search/tests/api-combined-search.ts new file mode 100644 index 000000000000..d21f481947f1 --- /dev/null +++ b/src/search/tests/api-combined-search.ts @@ -0,0 +1,170 @@ +/** + * To be able to run these tests you need to index the fixtures! + * And you need to have an Elasticsearch URL to connect to for the server. + * + * To index the fixtures, run: + * + * ELASTICSEARCH_URL=http://localhost:9200 npm run index-test-fixtures + * + * This will replace any "real" Elasticsearch indexes you might have so + * once you're done working on vitest tests you need to index real + * content again. + */ + +import { expect, test, vi } from 'vitest' + +import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs.js' +import { get } from '@/tests/helpers/e2etest-ts' + +import type { CombinedAutocompleteSearchResponse } from '@/search/types' + +if (!process.env.ELASTICSEARCH_URL) { + console.warn( + 'None of the API search middleware tests are run because ' + + "the environment variable 'ELASTICSEARCH_URL' is currently not set.", + ) +} + +const combinedSearchEndpoint = '/api/search/combined-autocomplete/v1' +const getSearchEndpointWithParams = (searchParams: URLSearchParams) => + `${combinedSearchEndpoint}?${searchParams}` + +// This suite only runs if $ELASTICSEARCH_URL is set. +describeIfElasticsearchURL('search/combined-autocomplete v1 middleware', () => { + vi.setConfig({ testTimeout: 60 * 1000 }) + + test('basic search', async () => { + const sp = new URLSearchParams() + sp.set('query', 'how do I') + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(200) + const results = JSON.parse(res.body) as CombinedAutocompleteSearchResponse + + // For aiAutocomplete + expect(results.aiAutocomplete.meta).toBeTruthy() + expect(results.aiAutocomplete.meta.found.value).toBeGreaterThanOrEqual(1) + expect(results.aiAutocomplete.meta.found.relation).toBeTruthy() + + expect(results.aiAutocomplete.hits).toBeTruthy() + + const aiHit = results.aiAutocomplete.hits[0] + expect(aiHit.term).toBe('How do I clone a repository?') + expect(aiHit.highlights).toBeTruthy() + expect(aiHit.highlights[0]).toBe('How do I clone a repository?') + + // For generalAutocomplete + expect(results.generalAutocomplete.meta).toBeTruthy() + expect(results.generalAutocomplete.meta.found.value).toBeGreaterThanOrEqual(1) + expect(results.generalAutocomplete.meta.found.relation).toBeTruthy() + + expect(results.generalAutocomplete.hits).toBeTruthy() + + const generalHit = results.generalAutocomplete.hits[0] + expect(generalHit.term).toBe('inputs') + expect(generalHit.highlights).toBeTruthy() + expect(generalHit.highlights[0]).toBe('inputs') + + // Check that it can be cached at the CDN + expect(res.headers['set-cookie']).toBeUndefined() + expect(res.headers['cache-control']).toContain('public') + expect(res.headers['cache-control']).toMatch(/max-age=[1-9]/) + expect(res.headers['surrogate-control']).toContain('public') + expect(res.headers['surrogate-control']).toMatch(/max-age=[1-9]/) + expect(res.headers['surrogate-key']).toBe('manual-purge') + }) + + test('invalid version', async () => { + const sp = new URLSearchParams() + sp.set('query', 'rest') + sp.set('version', 'never-heard-of') + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(400) + expect(JSON.parse(res.body).error).toBeTruthy() + }) + + test('variations on version name', async () => { + const sp = new URLSearchParams() + sp.set('query', 'rest') + const versions = ['enterprise-cloud', 'ghec', 'fpt', 'free-pro-team@latest'] + for (const version of versions) { + sp.set('version', version) + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(200) + } + }) + + test('invalid language', async () => { + const sp = new URLSearchParams() + sp.set('query', 'rest') + sp.set('language', 'xx') + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(400) + expect(JSON.parse(res.body).error).toBeTruthy() + }) + + test('only english supported', async () => { + const sp = new URLSearchParams() + sp.set('query', 'rest') + sp.set('language', 'ja') + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(400) + expect(JSON.parse(res.body).error).toBeTruthy() + }) + + test('autocomplete term search', async () => { + const sp = new URLSearchParams() + sp.set('query', 'rest') + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(200) + const results = JSON.parse(res.body) as CombinedAutocompleteSearchResponse + + // aiAutocomplete results + const aiHits = results.aiAutocomplete.hits + expect(aiHits.length).toBeGreaterThanOrEqual(2) + expect(aiHits[0].term).toBe( + 'How do I manage OAuth app access restrictions for my organization?', + ) + expect(aiHits[0].highlights[0]).toBe( + 'How do I manage OAuth app access restrictions for my organization?', + ) + expect(aiHits[1].term).toBe('How do I test my SSH connection to GitHub?') + expect(aiHits[1].highlights[0]).toBe('How do I test my SSH connection to GitHub?') + + // generalAutocomplete results + const generalHits = results.generalAutocomplete.hits + expect(generalHits.length).toBeGreaterThanOrEqual(3) + expect(generalHits[0].term).toBe('rest') + expect(generalHits[0].highlights[0]).toBe('rest') + expect(generalHits[1].term).toBe('rest api') + expect(generalHits[1].highlights[0]).toBe('rest api') + expect(generalHits[2].term).toBe('rest api endpoints') + expect(generalHits[2].highlights[0]).toBe('rest api endpoints') + }) + + test('empty query returns default results', async () => { + const sp = new URLSearchParams() + // No query at all + { + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(200) + const results = JSON.parse(res.body) as CombinedAutocompleteSearchResponse + expect(results).toBeTruthy() + } + // Empty query + { + sp.set('query', '') + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(200) + const results = JSON.parse(res.body) as CombinedAutocompleteSearchResponse + expect(results).toBeTruthy() + } + // Empty when trimmed + { + sp.set('query', ' ') + const res = await get(getSearchEndpointWithParams(sp)) + expect(res.statusCode).toBe(200) + const results = JSON.parse(res.body) as CombinedAutocompleteSearchResponse + expect(results).toBeTruthy() + } + }) +}) diff --git a/src/search/tests/fix-incomplete-markdown.ts b/src/search/tests/fix-incomplete-markdown.ts new file mode 100644 index 000000000000..aee62baedc0d --- /dev/null +++ b/src/search/tests/fix-incomplete-markdown.ts @@ -0,0 +1,147 @@ +import { expect, test, describe } from 'vitest' +import { fixIncompleteMarkdown } from '@/search/components/helpers/fix-incomplete-markdown' + +// Unit tests for the `fixIncompleteMarkdown` function +describe('fixIncompleteMarkdown', () => { + test('should close unclosed bold syntax with double asterisks', () => { + const input = 'This is **bold text' + const expected = 'This is **bold text**' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed bold syntax with double underscores', () => { + const input = 'This is __bold text' + const expected = 'This is __bold text__' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed italics syntax with single asterisk', () => { + const input = 'This is *italic text' + const expected = 'This is *italic text*' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed italics syntax with single underscore', () => { + const input = 'This is _italic text' + const expected = 'This is _italic text_' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed bold and italics syntax', () => { + const input = 'This is ***bold and italic text' + const expected = 'This is ***bold and italic text***' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed link syntax without URL', () => { + const input = 'This is a [link text' + const expected = 'This is a [link text]' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed link syntax with URL', () => { + const input = 'This is a [link text](https://example.com' + const expected = 'This is a [link text](https://example.com)' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed inline code syntax', () => { + const input = 'This is `inline code' + const expected = 'This is `inline code`' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed code block syntax', () => { + const input = 'Here is some code:\n```\nconst x = 10;' + const expected = 'Here is some code:\n```\nconst x = 10;\n```' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should handle nested markdown elements', () => { + const input = 'This is **bold and _italic text' + const expected = 'This is **bold and _italic text_**' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should not alter complete markdown', () => { + const input = 'This is **bold text** and *italic text*' + const expected = 'This is **bold text** and *italic text*' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should handle multiline input with unclosed link', () => { + const input = + 'Start of text [link text](https://example.com) and **bold text**\n\nI am a new paragraph with *italic text*\n\nThis is the end of the text, with a [link to the end' + const expected = + 'Start of text [link text](https://example.com) and **bold text**\n\nI am a new paragraph with *italic text*\n\nThis is the end of the text, with a [link to the end]' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed strikethrough syntax', () => { + const input = 'This is ~~strikethrough text' + const expected = 'This is ~~strikethrough text~~' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should close unclosed images syntax', () => { + const input = '![Alt text](' + const expected = '![Alt text]()' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should handle unclosed nested emphasis', () => { + const input = 'Some _italic and **bold text' + const expected = 'Some _italic and **bold text**_' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should handle unclosed code blocks with language specified', () => { + const input = '```javascript\nconsole.log("Hello, world!");' + const expected = '```javascript\nconsole.log("Hello, world!");\n```' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should not alter headings', () => { + const input = '### Heading level 3' + const expected = '### Heading level 3' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should not alter incomplete horizontal rules', () => { + const input = 'Some text\n---' + const expected = 'Some text\n---' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should handle incomplete tables', () => { + const input = '| Header1 | Header2 |\n|---------|---------|\n| Row1Col1' + const expected = '| Header1 | Header2 |\n|---------|---------|\n| Row1Col1 | |' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) + + test('should handle unclosed emphasis with tildes', () => { + const input = 'This is ~tilde emphasis' + const expected = 'This is ~tilde emphasis~' + const result = fixIncompleteMarkdown(input) + expect(result).toBe(expected) + }) +}) diff --git a/src/search/types.ts b/src/search/types.ts index bab9fb97b734..98133f1ff5b0 100644 --- a/src/search/types.ts +++ b/src/search/types.ts @@ -1,4 +1,5 @@ import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types' + import type { AdditionalIncludes, ComputedSearchQueryParamsMap, @@ -20,6 +21,11 @@ export interface AutocompleteSearchResponse { hits: AutocompleteSearchHit[] } +export interface CombinedAutocompleteSearchResponse { + aiAutocomplete: AutocompleteSearchResponse + generalAutocomplete: AutocompleteSearchResponse +} + // Response to middleware /search route export interface SearchOnReqObject { searchParams: ComputedSearchQueryParamsMap[Type] @@ -52,7 +58,7 @@ export type GeneralSearchHit = GeneralSearchHitWithoutIncludes & { [key in AdditionalIncludes]?: string } -interface AutocompleteSearchHit { +export interface AutocompleteSearchHit { term?: string highlights: string[] } diff --git a/src/shielding/middleware/handle-invalid-query-strings.ts b/src/shielding/middleware/handle-invalid-query-strings.ts index c5c958929978..786bfe984d3a 100644 --- a/src/shielding/middleware/handle-invalid-query-strings.ts +++ b/src/shielding/middleware/handle-invalid-query-strings.ts @@ -28,8 +28,12 @@ const RECOGNIZED_KEYS_BY_ANY = new Set([ 'tool', // When apiVersion isn't the only one. E.g. ?apiVersion=XXX&tool=vscode 'apiVersion', - // Search + // Search results page 'query', + // Any page, Search Overlay + 'search-overlay-input', + 'search-overlay-open', + 'search-overlay-ask-ai', // The drop-downs on "Webhook events and payloads" 'actionType', // Used by the tracking middleware From ce3ed1f01d18a215c9938b5748b100e88a193fa5 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 5 Feb 2025 13:08:52 -0800 Subject: [PATCH 4/4] Two little tweaks post ai search ship (#54265) --- src/frame/components/hooks/useQueryParam.ts | 1 - .../components/page-header/hooks/useInnerWindowWidth.ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frame/components/hooks/useQueryParam.ts b/src/frame/components/hooks/useQueryParam.ts index ff43ec42acfd..f590e1ebc166 100644 --- a/src/frame/components/hooks/useQueryParam.ts +++ b/src/frame/components/hooks/useQueryParam.ts @@ -43,7 +43,6 @@ export function useQueryParam( // If the query param changes in the URL, update the state useEffect(() => { - console.log('updating state') const paramValue = router.query[queryParamKey] if (paramValue) { diff --git a/src/frame/components/page-header/hooks/useInnerWindowWidth.ts b/src/frame/components/page-header/hooks/useInnerWindowWidth.ts index bb4c4d425508..faf60d062a88 100644 --- a/src/frame/components/page-header/hooks/useInnerWindowWidth.ts +++ b/src/frame/components/page-header/hooks/useInnerWindowWidth.ts @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react' +import throttle from 'lodash/throttle' export function useInnerWindowWidth() { const hasWindow = typeof window !== 'undefined' @@ -14,9 +15,9 @@ export function useInnerWindowWidth() { useEffect(() => { if (hasWindow) { - const handleResize = function () { + const handleResize = throttle(function () { setWidth(getWidth()) - } + }, 100) window.addEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize)