diff --git a/.gitignore b/.gitignore index e3332cc2..05c7cd44 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,46 @@ Temporary Items .pem #temp directory ignore -deploy/ +# deploy/ service/python/Dockerfile + +.certs +.artifacts +deploy/k8s/backend/application.yaml +deploy/k8s/overlays/prod/kustomization.yaml +deploy/k8s/backend/wallet/ + +generated/ + +.env.json +*.bkp +*.zip +kubeconfig + +# Terraform +**/.terraform/* +*.tfstate +*.tfstate.* +crash.log +crash.*.log +*.tfvars +*.tfvars.json +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc + +# Node +node_modules/ + +# Java +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +.idea +bin/ +dist/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e26dfee..7b016a89 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 00000000..a7ff3eeb --- /dev/null +++ b/FAQ.md @@ -0,0 +1,15 @@ +# FAQ + +## Technical help + +### Get the Load Balancer Public IP address + +```bash +kubectl get service -n ingress-nginx -o jsonpath='{.items[?(@.spec.type=="LoadBalancer")].status.loadBalancer.ingress[0].ip}' +``` + +### Get the `dockerconfigjson` from the secret + +```bash +kubectl get secret ocir-secret --output="jsonpath={.data.\.dockerconfigjson}" | base64 --decode | jq +``` diff --git a/JET.md b/JET.md new file mode 100644 index 00000000..363522dd --- /dev/null +++ b/JET.md @@ -0,0 +1,213 @@ +# Enhance Engagement Using Content Generation with OCI Generative AI + +[![License: UPL](https://img.shields.io/badge/license-UPL-green)](https://img.shields.io/badge/license-UPL-green) [![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=oracle-devrel_oci-generative-ai-jet-ui)](https://sonarcloud.io/dashboard?id=oracle-devrel_oci-generative-ai-jet-ui) + +## Introduction + +Using Oracle JET, create a user-friendly prompt-led user interface (UI) to interact with Oracle's new Generative AI service. This toolkit will configure your Generative AI Service connection so you can begin your journey with AI, or migrate your existing (local or Cloud) LLMs to the Oracle AppDev ecosystem. + +Oracle JET(Preact) allows you to craft pixel-perfect UIs which are fast, lightweight, and engaging. Your code takes centre stage with Oracle JET, while its powerful features enable you to create dynamic user experiences quickly and reliably. + +Oracle's Generative AI service allows developers to unlock a better user experience for chat systems, question-and-answer solutions, and much more. This project provides a front end to that service so you can experiment and get a sense of the immense power of Oracle Generative AI. This is an excellent starting point on your AI journey, and experienced developers will be able to quickly port their LLMs to leverage this powerful service. + +Check out [demo here](https://youtu.be/hpRoQ93YeaQ) + +![alt text here](images/demo.gif) + +## Getting Started + +### 0. Prerequisites and setup + +- Oracle Cloud Infrastructure (OCI) Account +- Oracle Cloud Infrastructure (OCI) Generative AI Service - [Getting Started with Generative AI](https://docs.oracle.com/en-us/iaas/Content/generative-ai/getting-started.htm) +- Oracle Cloud Infrastructure Documentation - [Generative AI](https://docs.oracle.com/en-us/iaas/Content/generative-ai/home.htm) +- Oracle Cloud Infrastructure (OCI) Generative AI Service SDK - [Oracle Cloud Infrastructure Python SDK](https://pypi.org/project/oci/) +- Node v16 - [Node homepage](https://nodejs.org/en) +- Oracle JET v15 - [Oracle JET Homepage](https://www.oracle.com/webfolder/technetwork/jet/index.html) + +Follow the links below to generate a config file and a key pair in your ~/.oci directory + +- [SDK config](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm) +- [API signing key](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm) +- [CLI install](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm#configfile) + +After completion, you should have the following 2 things in your `~/.oci directory` + +- A config file(where key file point to private key:key_file=`~/.oci/oci_api_key.pem`) +- A key pair named `oci_api_key.pem` and `oci_api_key_public.pem` +- Now make sure you change the reference of the key file in the config file +- Append OCI Generative-AI service compartment and endpoint URL + +```console +vim service/python/server.py +``` + +```Python +#TODO: Update this section with your tenancy details +compartment_id = "ocid1.compartment.oc1.." +CONFIG_PROFILE = "DEFAULT" +config = oci.config.from_file("~/.oci/config", CONFIG_PROFILE) +endpoint = "https://inference.generativeai..oci.oraclecloud.com" +generative_ai_inference_client = ( + oci.generative_ai_inference.GenerativeAiInferenceClient( + config=config, + service_endpoint=endpoint, + retry_strategy=oci.retry.NoneRetryStrategy(), + timeout=(10, 240), + ) +) +``` + +### 1. (Optional) Modify websocket ports + +- In the root of the project directory run to edit ports + +```console +vim app/src/components/content/index.tsx +``` + +```js +const gateway = ws://${window.location.hostname}:1234; +``` + +- Update default port in Python websocket server: + +```console +vim service/python/server.py +``` + +```Python +async def start_server(): + await websockets.serve(handle_websocket, "localhost", 1234 ) +``` + +### 2. Upload Public Key + +- Upload your oci_api_key_public.pem to console: +[API signing key](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm#three) + +### 3. Install all dependencies + +We suggest you install dependencies in a virtual environment to avoid conflicts on your system. + +- Navigate to the server root folder + +```console +cd service/python +``` + +- Create a virtual environment: + +```console +python3 -m venv venv +``` + +- Activate your virtual environment: + +```console +. venv/bin/activate +``` + +- Upgrade pip: + +```console +pip3 install --upgrade pip +``` + +- Install requirements: + +```console +pip3 install -r requirements.txt +``` + +## 4. Start the websocket server app + +Once dependencies are installed and your service credentials are updated you can run server.py + +```console +python3 server.py +``` + +## 5. Start JET Client + +- Open app directory: + +```console +cd ../../app +``` + +- Install dependencies: + +```console +npm install +``` + +- Run local version: + +```console +npx ojet serve +``` + +- Or package for web deployment + +```console +npx ojet build web +``` + + You can now ask question to generate LLM based response. + ![alt text here](images/QandA.png) + + Note that sample app can generate markdown. + ![alt text here](images/Markdown.png) + +## Appendix: Token-based Authentication + +Check [Token-based Authentication for the CLI](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clitoken.htm#Running_Scripts_on_a_Computer_without_a_Browser) + +```Python +config = oci.config.from_file('~/.oci/config', profile_name="DEFAULT") + +def make_security_token_signer(oci_config): + pk = oci.signer.load_private_key_from_file(oci_config.get("key_file"), None) + with open(oci_config.get("security_token_file")) as f: + st_string = f.read() + return oci.auth.signers.SecurityTokenSigner(st_string, pk) + +signer = make_security_token_signer(oci_config=config) +# Service endpoint +endpoint = "https://generativeai.aiservice..oci.oraclecloud.com" + +generative_ai_client = oci.generative_ai.generative_ai_client.GenerativeAiClient(config=config, service_endpoint=endpoint, retry_strategy=oci.retry.NoneRetryStrategy(), signer=signer) +``` + +## Notes/Issues + +Additional Use Cases like summarization and embedding coming soon. + +To change output parameters edit server.py + +```Python + cohere_generate_text_request.max_tokens = 500 # choose the number of tokens 1-4000 + cohere_generate_text_request.temperature = 0.75 # adjust temperature 0-1 + cohere_generate_text_request.top_p = 0.7 # adjust top_p 0-1 + cohere_generate_text_request.frequency_penalty = 1.0 # adjust frequency_penalty +``` + +## URLs + +- [Oracle AI](https://www.oracle.com/artificial-intelligence/) +- [AI for Developers](https://developer.oracle.com/technologies/ai.html) + +## Contributing + +This project is open source. Please submit your contributions by forking this repository and submitting a pull request! Oracle appreciates any contributions that are made by the open-source community. + +## License + +Copyright (c) 2024 Oracle and/or its affiliates. + +Licensed under the Universal Permissive License (UPL), Version 1.0. + +See [LICENSE](LICENSE) for more details. + +ORACLE AND ITS AFFILIATES DO NOT PROVIDE ANY WARRANTY WHATSOEVER, EXPRESS OR IMPLIED, FOR ANY SOFTWARE, MATERIAL OR CONTENT OF ANY KIND CONTAINED OR PRODUCED WITHIN THIS REPOSITORY, AND IN PARTICULAR SPECIFICALLY DISCLAIM ANY AND ALL IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. FURTHERMORE, ORACLE AND ITS AFFILIATES DO NOT REPRESENT THAT ANY CUSTOMARY SECURITY REVIEW HAS BEEN PERFORMED WITH RESPECT TO ANY SOFTWARE, MATERIAL OR CONTENT CONTAINED OR PRODUCED WITHIN THIS REPOSITORY. IN ADDITION, AND WITHOUT LIMITING THE FOREGOING, THIRD PARTIES MAY HAVE POSTED SOFTWARE, MATERIAL OR CONTENT TO THIS REPOSITORY WITHOUT ANY REVIEW. USE AT YOUR OWN RISK. \ No newline at end of file diff --git a/K8S.md b/K8S.md new file mode 100644 index 00000000..47c45bd1 --- /dev/null +++ b/K8S.md @@ -0,0 +1,179 @@ +# Accelerating AI Application Deployment Using Cloud Native Strategies + +## Introduction + +Kubernetes has become the standard for managing containerized applications, and Oracle Cloud Infrastructure Container Engine for Kubernetes (OKE) is a managed Kubernetes service that delivers outstanding cloud reliability. Now, imagine using OKE with AI to create powerful, scalable, highly available AI applications. + +This project deploys an AI pipeline with a multipurpose front end for text generation and summarization. The pipeline integrates with a database to track interactions, enabling fine-tuning and performance monitoring for application optimization. It leverages OCI Generative AI APIs on a Kubernetes cluster. + +## Getting Started + +### 0. Prerequisites and setup + +- Oracle Cloud Infrastructure (OCI) Account - [sign-up page](https://www-sites.oracle.com/artificial-intelligence/solutions/deploy-ai-apps-fast/#:~:text=Oracle%20Cloud%20account%E2%80%94-,sign%2Dup%20page,-Oracle%20Cloud%20Infrastructure) +- Oracle Cloud Infrastructure (OCI) Generative AI Service - [Getting Started with Generative AI](https://docs.oracle.com/en-us/iaas/Content/generative-ai/getting-started.htm) +- Oracle Cloud Infrastructure Documentation - [Generative AI](https://docs.oracle.com/en-us/iaas/Content/generative-ai/home.htm) +- Oracle Cloud Infrastructure (OCI) Generative AI Service SDK - [Oracle Cloud Infrastructure Python SDK](https://pypi.org/project/oci/) +- Node v16 - [Node homepage](https://nodejs.org/en) +- Oracle JET v15 - [Oracle JET Homepage](https://www.oracle.com/webfolder/technetwork/jet/index.html) +- OCI Container Engine for Kubernetes — [documentation](https://www-sites.oracle.com/artificial-intelligence/solutions/deploy-ai-apps-fast/#:~:text=Engine%20for%20Kubernetes%E2%80%94-,documentation,-Oracle%20Autonomous%20Database) +- Oracle Autonomous Database — [documentation](https://www-sites.oracle.com/artificial-intelligence/solutions/deploy-ai-apps-fast/#:~:text=Oracle%20Autonomous%20Database,Boot%20framework%E2%80%94documentation) +- Spring Boot framework — [documentation](https://www-sites.oracle.com/artificial-intelligence/solutions/deploy-ai-apps-fast/#:~:text=Spring%20Boot%20framework%E2%80%94-,documentation,-Getting%20started) + +![Architecture](./images/architecture.png) + +Get troubleshoot help on the [FAQ](FAQ.md) + +## Set Up environment + +Install Node.js 16 on Cloud Shell. + +```bash +nvm install 16 && nvm use 16 +``` + +Install dependencies for scripts. + +```bash +cd scripts/ && npm install && cd .. +``` + +### Set the environment variables + +Generate `genai.json` file with all environment variables. + +```bash +npx zx scripts/setenv.mjs +``` + +> Answer the Compartment name where you want to deploy the infrastructure. Root compartment is the default. + +### Deploy Infrastructure + +Generate `terraform.tfvars` file for Terraform. + +```bash +npx zx scripts/tfvars.mjs +``` + +```bash +cd deploy/terraform +``` + +Init Terraform providers: + +```bash +terraform init +``` + +Apply deployment: + +```bash +terraform apply --auto-approve +``` + +```bash +cd ../.. +``` + +## Release and create Kustomization files + +Build and push images: + +```bash +npx zx scripts/release.mjs +``` + +Create Kustomization files + +```bash +npx zx scripts/kustom.mjs +``` + +### Kubernetes Deployment + +```bash +export KUBECONFIG="deploy/terraform/generated/kubeconfig" +``` + +```bash +kubectl cluster-info +``` + +```bash +kubectl apply -k deploy/k8s/overlays/prod +``` + +Run `get deploy` a few times: + +```bash +kubectl get deploy +``` + +Wait for all deployments to be `Ready` and `Available`. + +``` +NAME READY UP-TO-DATE AVAILABLE AGE +backend 1/1 1 1 3m28s +web 1/1 1 1 3m21s +``` + +Access your application: + +```bash +echo $(kubectl get service \ + -n ingress-nginx \ + -o jsonpath='{.items[?(@.spec.type=="LoadBalancer")].status.loadBalancer.ingress[0].ip}') +``` + +> This command will list the services on the `ingress-nginx` namespace and filter for the Load Balancer. If the response is an empty string, wait a bit and execute the command again. The Load Balancer takes a bit of time to create the Public IP address. + +Take the Public IP to your browser. + +## Clean up + +Delete Kubernetes components + +```bash +kubectl delete -k deploy/k8s/overlays/prod +``` + +Destroy infrastructure with Terraform. + +```bash +cd deploy/terraform +``` + +```bash +terraform destroy -auto-approve +``` + +```bash +cd ../.. +``` + +Clean up the artifacts on Object Storage + +```bash +npx zx scripts/clean.mjs +``` + +## Local deployment + +Run locally with these steps [Local](LOCAL.md) + +## Known Issues + +Deploying artifacts as Object Storage. + +> There is an issue in Terraform `oracle/oci` provider on version `v5.25.0`. It is not updated to the specific version of `terraform-plugin-sdk` that fix the underlying gRCP limit of 4Mb. +> +> The project would want to upload artifacts to Object Storage, like the backend jar file, which is bigger than 4Mb. +> +> ```terraform +> data "local_file" "backend_jar_tgz" { +> filename = "${path.module}/../../.artifacts/backend_jar.tar.gz" +> } +> ``` +> +> As a workaround, a `script/deliver.mjs` script and a `script/clean.mjs` script will deliver and clean the artifacts into Object Storage and make Pre-Authenticated Requests available for Terraform resources. diff --git a/LOCAL.md b/LOCAL.md new file mode 100644 index 00000000..61cc8b9f --- /dev/null +++ b/LOCAL.md @@ -0,0 +1,63 @@ +# Run Local + +## Web + +Run locally in a terminal with: + +```bash +cd web +``` + +```bash +npm run dev +``` + +## Backend + +Run locally on another terminal with: + +```bash +cd backend +``` + +Edit `/backend/src/main/resources/application.yaml` to have the correct values. + +```bash +./gradlew bootRun +``` + +## Build for distribution + +## Build artifacts + +### Build Java Application: + +```bash +cd backend +``` + +```bash +./gradlew bootJar +``` + +> `build/libs/backend-0.0.1.jar` jar file generated + +```bash +cd .. +``` + +### Build Web Application: + +```bash +cd web +``` + +```bash +npm run build +``` + +> `dist` folder generated + +```bash +cd .. +``` diff --git a/README.md b/README.md index 48c351de..d5eb2dca 100644 --- a/README.md +++ b/README.md @@ -1,202 +1,15 @@ -# oci-generative-ai-jet-ui +# OCI Generative AI toolkit [![License: UPL](https://img.shields.io/badge/license-UPL-green)](https://img.shields.io/badge/license-UPL-green) [![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=oracle-devrel_oci-generative-ai-jet-ui)](https://sonarcloud.io/dashboard?id=oracle-devrel_oci-generative-ai-jet-ui) + ## Introduction Using Oracle JET, create a user-friendly prompt-led user interface (UI) to interact with Oracle's new Generative AI service. This toolkit will configure your Generative AI Service connection so you can begin your journey with AI, or migrate your existing (local or Cloud) LLMs to the Oracle AppDev ecosystem. +[Enhance Engagement Using Content Generation with OCI Generative AI](JET.md) -Oracle JET(Preact) allows you to craft pixel-perfect UIs which are fast, lightweight, and engaging. Your code takes centre stage with Oracle JET, while its powerful features enable you to create dynamic user experiences quickly and reliably. - -Oracle's Generative AI service allows developers to unlock a better user experience for chat systems, question-and-answer solutions, and much more. This project provides a front end to that service so you can experiment and get a sense of the immense power of Oracle Generative AI. This is an excellent starting point on your AI journey, and experienced developers will be able to quickly port their LLMs to leverage this powerful service. - -Check out [demo here](https://youtu.be/hpRoQ93YeaQ) - -![alt text here](images/demo.gif) - -## Getting Started - -### 0. Prerequisites and setup - -- Oracle Cloud Infrastructure (OCI) Account -- Oracle Cloud Infrastructure (OCI) Generative AI Service - [Getting Started with Generative AI](https://docs.oracle.com/en-us/iaas/Content/generative-ai/getting-started.htm) -- Oracle Cloud Infrastructure Documentation - [Generative AI](https://docs.oracle.com/en-us/iaas/Content/generative-ai/home.htm) -- Oracle Cloud Infrastructure (OCI) Generative AI Service SDK - [Oracle Cloud Infrastructure Python SDK](https://pypi.org/project/oci/) -- Node v16 - [Node homepage](https://nodejs.org/en) -- Oracle JET v15 - [Oracle JET Homepage](https://www.oracle.com/webfolder/technetwork/jet/index.html) - -Follow the links below to generate a config file and a key pair in your ~/.oci directory - -- [SDK config](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm) -- [API signing key](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm) -- [CLI install](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm#configfile) - -After completion, you should have the following 2 things in your `~/.oci directory` - -- A config file(where key file point to private key:key_file=`~/.oci/oci_api_key.pem`) -- A key pair named `oci_api_key.pem` and `oci_api_key_public.pem` -- Now make sure you change the reference of the key file in the config file -- Append OCI Generative-AI service compartment and endpoint URL - -```console -vim service/python/server.py -``` - -```Python -#TODO: Update this section with your tenancy details -compartment_id = "ocid1.compartment.oc1.." -CONFIG_PROFILE = "DEFAULT" -config = oci.config.from_file("~/.oci/config", CONFIG_PROFILE) -endpoint = "https://inference.generativeai..oci.oraclecloud.com" -generative_ai_inference_client = ( - oci.generative_ai_inference.GenerativeAiInferenceClient( - config=config, - service_endpoint=endpoint, - retry_strategy=oci.retry.NoneRetryStrategy(), - timeout=(10, 240), - ) -) -``` - -### 1. (Optional) Modify websocket ports - -- In the root of the project directory run to edit ports - -```console -vim app/src/components/content/index.tsx -``` - -```js -const gateway = ws://${window.location.hostname}:1234; -``` - -- Update default port in Python websocket server: - -```console -vim service/python/server.py -``` - -```Python -async def start_server(): - await websockets.serve(handle_websocket, "localhost", 1234 ) -``` - -### 2. Upload Public Key - -- Upload your oci_api_key_public.pem to console: -[API signing key](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm#three) - -### 3. Install all dependencies - -We suggest you install dependencies in a virtual environment to avoid conflicts on your system. - -- Navigate to the server root folder - -```console -cd service/python -``` - -- Create a virtual environment: - -```console -python3 -m venv venv -``` - -- Activate your virtual environment: - -```console -. venv/bin/activate -``` - -- Upgrade pip: - -```console -pip3 install --upgrade pip -``` - -- Install requirements: - -```console -pip3 install -r requirements.txt -``` - -## 4. Start the websocket server app - -Once dependencies are installed and your service credentials are updated you can run server.py - -```console -python3 server.py -``` - -## 5. Start JET Client - -- Open app directory: - -```console -cd ../../app -``` - -- Install dependencies: - -```console -npm install -``` - -- Run local version: - -```console -npx ojet serve -``` - -- Or package for web deployment - -```console -npx ojet build web -``` - - You can now ask question to generate LLM based response. - ![alt text here](images/QandA.png) - - Note that sample app can generate markdown. - ![alt text here](images/Markdown.png) - -## Appendix: Token-based Authentication - -Check [Token-based Authentication for the CLI](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clitoken.htm#Running_Scripts_on_a_Computer_without_a_Browser) - -```Python -config = oci.config.from_file('~/.oci/config', profile_name="DEFAULT") - -def make_security_token_signer(oci_config): - pk = oci.signer.load_private_key_from_file(oci_config.get("key_file"), None) - with open(oci_config.get("security_token_file")) as f: - st_string = f.read() - return oci.auth.signers.SecurityTokenSigner(st_string, pk) - -signer = make_security_token_signer(oci_config=config) -# Service endpoint -endpoint = "https://generativeai.aiservice..oci.oraclecloud.com" - -generative_ai_client = oci.generative_ai.generative_ai_client.GenerativeAiClient(config=config, service_endpoint=endpoint, retry_strategy=oci.retry.NoneRetryStrategy(), signer=signer) -``` - -## Notes/Issues - -Additional Use Cases like summarization and embedding coming soon. - -To change output parameters edit server.py - -```Python - cohere_generate_text_request.max_tokens = 500 # choose the number of tokens 1-4000 - cohere_generate_text_request.temperature = 0.75 # adjust temperature 0-1 - cohere_generate_text_request.top_p = 0.7 # adjust top_p 0-1 - cohere_generate_text_request.frequency_penalty = 1.0 # adjust frequency_penalty -``` - -## URLs - -- [Oracle AI](https://www.oracle.com/artificial-intelligence/) -- [AI for Developers](https://developer.oracle.com/technologies/ai.html) +This project deploys an AI pipeline with a multipurpose front end for text generation and summarization. The pipeline integrates with a database to track interactions, enabling fine-tuning and performance monitoring for application optimization. It leverages OCI Generative AI APIs on a Kubernetes cluster. +[Accelerating AI Application Deployment Using Cloud Native Strategies](K8S.md) ## Contributing diff --git a/app/oraclejetconfig.json b/app/oraclejetconfig.json index 3fe834f5..5605e78a 100644 --- a/app/oraclejetconfig.json +++ b/app/oraclejetconfig.json @@ -18,10 +18,10 @@ "defaultBrowser": "chrome", "sassVer": "8.0.0", "defaultTheme": "redwood", - "typescriptLibraries": "typescript@5.0.4 yargs-parser@~13.1.2", + "typescriptLibraries": "typescript@5.3.2 yargs-parser@~13.1.2", "webpackLibraries": "webpack@5.75.0 @types/node@18.16.3 webpack-dev-server style-loader css-loader sass-loader sass ts-loader@8.4.0 raw-loader noop-loader html-webpack-plugin html-replace-webpack-plugin copy-webpack-plugin @prefresh/webpack @prefresh/babel-plugin webpack-merge compression-webpack-plugin mini-css-extract-plugin clean-webpack-plugin css-fix-url-loader", - "mochaTestingLibraries": "karma mocha sinon chai coverage karma-chai@0.1.0 karma-coverage@2.2.0 karma-chrome-launcher@3.1.1 karma-mocha@2.0.1 karma-mocha-reporter@2.2.5 karma-requirejs@1.1.0 karma-fixture@0.2.6 karma-sinon@1.0.5 karma-typescript@5.5.4 @types/chai@4.3.4 @types/karma-fixture@0.2.5 @types/mocha@10.0.1 @types/sinon@10.0.13", - "jestTestingLibraries": "jest@27.5.1 @testing-library/preact@2.0.1 @types/jest@27.0 jest-environment-jsdom@27.5.1 @oracle/oraclejet-jest-preset@~15.1.0", + "mochaTestingLibraries": "karma mocha sinon chai@^4 coverage karma-chai@0.1.0 karma-coverage@2.2.0 karma-chrome-launcher@3.1.1 karma-mocha@2.0.1 karma-mocha-reporter@2.2.5 karma-requirejs@1.1.0 karma-fixture@0.2.6 karma-sinon@1.0.5 karma-typescript@5.5.4 @types/chai@4.3.4 @types/karma-fixture@0.2.5 @types/mocha@10.0.1 @types/sinon@10.0.13", + "jestTestingLibraries": "jest@^29 @testing-library/preact@2.0.1 @types/jest@29.0 jest-environment-jsdom@27.5.1 @oracle/oraclejet-jest-preset@~16.0.0", "architecture": "vdom", "watchInterval": 1000, "exchange-url": "https://exchange.oraclecorp.com/api/0.2.0/" diff --git a/app/package.json b/app/package.json index ee9117be..2c1b7c27 100644 --- a/app/package.json +++ b/app/package.json @@ -1,21 +1,21 @@ { "name": "JETGenAI", "version": "1.0.0", - "description": "An Oracle JavaScript Extension Toolkit(JET) web app", + "description": "Sample Client app showing communication with OCI Generative AI services via Websocket", "dependencies": { - "@oracle/oraclejet": "~15.1.0", - "@oracle/oraclejet-core-pack": "~15.1.0", + "@oracle/oraclejet": "~16.0.0", + "@oracle/oraclejet-core-pack": "~16.0.0", "marked": "^4.3.0", "uuid": "^9.0.1" }, "devDependencies": { - "@oracle/ojet-cli": "~15.1.0", - "@oracle/oraclejet-audit": "^15.1.3", + "@oracle/ojet-cli": "~16.0.0", + "@oracle/oraclejet-audit": "^16.0.0", "@types/uuid": "^9.0.7", "extract-zip": "^1.7.0", "fs-extra": "^8.1.0", "glob": "7.2.0", - "typescript": "5.0.4", + "typescript": "5.3.2", "underscore": "^1.10.2", "yargs-parser": "13.1.2" }, diff --git a/app/path_mapping.json b/app/path_mapping.json index fe42d0f4..128fddc3 100644 --- a/app/path_mapping.json +++ b/app/path_mapping.json @@ -2,12 +2,12 @@ "use": "local", "cdns": { "jet": { - "prefix": "https://static.oracle.com/cdn/jet/15.1.0/default/js", - "css": "https://static.oracle.com/cdn/jet/15.1.0/default/css", - "csspreact": "https://static.oracle.com/cdn/jet/15.1.0/3rdparty/oraclejet-preact/amd", + "prefix": "https://static.oracle.com/cdn/jet/16.0.0/default/js", + "css": "https://static.oracle.com/cdn/jet/16.0.0/default/css", + "csspreact": "https://static.oracle.com/cdn/jet/16.0.0/3rdparty/oraclejet-preact/amd", "config": "bundles-config.js" }, - "3rdparty": "https://static.oracle.com/cdn/jet/15.1.0/3rdparty" + "3rdparty": "https://static.oracle.com/cdn/jet/16.0.0/3rdparty" }, "libs": { "knockout": { @@ -262,7 +262,8 @@ "src": ["compat.umd.js", "compat.umd.js.map"], "path": "libs/preact/compat/dist/compat.umd.js", "cdnPath": "preact/compat/dist/compat.umd" - } + }, + "requireMap": "react" }, "preact/jsx-runtime": { "cdn": "3rdparty", @@ -365,6 +366,20 @@ "pathSuffix": "/min/ojcss'", "cdnPath": "ojcss" } + }, + "chai": { + "cdn": "3rdparty", + "cwd": "node_modules/chai/", + "debug": { + "src": "chai.js", + "path": "libs/chai/chai.js", + "cdnPath": "chai/chai-4.3.10" + }, + "release": { + "src": "chai.js", + "path": "libs/chai/chai.js", + "cdnPath": "chai/chai-4.3.10.min" + } } } } diff --git a/app/src/components/content/answer.tsx b/app/src/components/content/answer.tsx index 055c6418..64276a6e 100644 --- a/app/src/components/content/answer.tsx +++ b/app/src/components/content/answer.tsx @@ -13,30 +13,55 @@ declare global { type Props = { item: ojListView.ItemTemplateContext; + sim: boolean; }; -export const Answer = ({ item }: Props) => { +export const Answer = ({ item, sim }: Props) => { const answer = item.data.answer; return ( -
  • -
    -
    - -
    - -
    - -
    -
    -
  • + <> + {sim && ( +
  • +
    +
    + +
    +
    + +
    +
    +
  • + )} + {!sim && ( +
  • +
    +
    + +
    +
    + +
    +
    +
  • + )} + ); }; diff --git a/app/src/components/content/chat.tsx b/app/src/components/content/chat.tsx index d2f9aa6f..2ad52692 100644 --- a/app/src/components/content/chat.tsx +++ b/app/src/components/content/chat.tsx @@ -2,7 +2,7 @@ import { Question } from "./question"; import { Answer } from "./answer"; import { Loading } from "./loading"; import { ComponentProps } from "preact"; -import { useEffect, useRef, useState } from "preact/hooks"; +import { useEffect, useRef, useState, MutableRef } from "preact/hooks"; import "ojs/ojlistview"; import { ojListView } from "ojs/ojlistview"; import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); @@ -11,6 +11,8 @@ import Context = require("ojs/ojcontext"); type Props = { testId?: string; data: any; + questionChanged: (event: any) => void; + question: MutableRef; }; type Item = { @@ -25,7 +27,7 @@ const madp = new MutableArrayDataProvider([], { keyAttributes: "id", }); -const Chat = ({ testId, data }: Props) => { +export const Chat = ({ testId, data, questionChanged, question }: Props) => { const dataProvider = useRef(madp); const listRef = useRef>(null); const [lastKey, setLastKey] = useState(0); @@ -59,29 +61,38 @@ const Chat = ({ testId, data }: Props) => { const chatItemTemplate = (item: ojListView.ItemTemplateContext) => { return ( <> - {item.data.answer && } + {item.data.answer && } {item.data.loading && } - {item.data.question && } + {item.data.question && } ); }; return ( -
    - - - - -
    + <> +
    + + + + +
    + + ); }; -export default Chat; diff --git a/app/src/components/content/data/answers.json b/app/src/components/content/data/answers.json new file mode 100644 index 00000000..c8eb4166 --- /dev/null +++ b/app/src/components/content/data/answers.json @@ -0,0 +1,6 @@ +[ + "Answer 1", + "Answer 2", + "Answer 3", + "Answer 4" +] diff --git a/app/src/components/content/data/questions.json b/app/src/components/content/data/questions.json new file mode 100644 index 00000000..10b977ae --- /dev/null +++ b/app/src/components/content/data/questions.json @@ -0,0 +1,6 @@ +[ + "Question 1", + "Question 2", + "Question 3", + "Question 4" +] diff --git a/app/src/components/content/index.tsx b/app/src/components/content/index.tsx index a2f3ef85..64814312 100644 --- a/app/src/components/content/index.tsx +++ b/app/src/components/content/index.tsx @@ -1,15 +1,32 @@ -import Chat from "./chat"; +import { Chat } from "./chat"; +import { Summary } from "./summary"; +import { Simulation } from "./simulation"; +import { Settings } from "./settings"; import "preact"; import "ojs/ojinputsearch"; import "oj-c/message-toast"; +import "oj-c/drawer-popup"; import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); import { MessageToastItem } from "oj-c/message-toast"; import { InputSearchElement } from "ojs/ojinputsearch"; import { useState, useEffect, useRef } from "preact/hooks"; +import * as Questions from "text!./data/questions.json"; +import * as Answers from "text!./data/answers.json"; +type ServiceTypes = "text" | "summary" | "sim"; +type Chat = { + id?: number; + question?: string; + answer?: string; + loading?: string; +}; const Content = () => { const [update, setUpdate] = useState>([]); const [busy, setBusy] = useState(false); + const [summaryResults, setSummaryResults] = useState(""); + const [summaryPrompt, setSummaryPrompt] = useState(); + const [serviceType, setServiceType] = useState("summary"); + const [settingsOpened, setSettingsOpened] = useState(false); const question = useRef(); const chatData = useRef>([]); const socket = useRef(); @@ -29,6 +46,7 @@ const Content = () => { const initWebSocket = () => { console.log("Trying to open a WebSocket connection..."); socket.current = new WebSocket(gateway); + socket.current.binaryType = "arraybuffer"; socket.current.onopen = onOpen; socket.current.onerror = onError; socket.current.onclose = onClose; @@ -39,16 +57,21 @@ const Content = () => { const onMessage = (event: any) => { const msg = JSON.parse(event.data); - switch (Object.keys(msg)[0]) { + switch (msg.msgType) { + // switch (Object.keys(msg)[0]) { case "message": - console.log("message: ", msg.message); - return msg.message; + console.log("message: ", msg.data); + return msg.data; case "question": - console.log("question: ", msg.question); - return msg.question; + console.log("question: ", msg.data); + return msg.data; + case "summary": + console.log("summary"); + setSummaryResults(msg.data); + return; case "answer": - console.log("answer: ", msg.answer); - if (msg.answer !== "connected") { + console.log("answer: ", msg.data); + if (msg.data !== "connected") { let tempArray = [...chatData.current]; // remove the animation item before adding answer setBusy(false); @@ -56,12 +79,12 @@ const Content = () => { messagesDP.current.data = []; tempArray.push({ id: tempArray.length as number, - answer: msg.answer, + answer: msg.data, }); chatData.current = tempArray; setUpdate(chatData.current); } - return msg.answer; + return msg.data; default: return "unknown"; } @@ -70,7 +93,9 @@ const Content = () => { const onOpen = () => { clearInterval(sockTimer); console.log("Connection opened"); - socket.current?.send(JSON.stringify({ message: "connected" })); + socket.current?.send( + JSON.stringify({ msgType: "message", data: "connected" }) + ); setConnState("Connected"); }; @@ -85,13 +110,51 @@ const Content = () => { socket.current?.close(); } + // Simulation code + const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + const runSimulation = async () => { + let Q = true; + let x: number = 0; + let y: number = 0; + let tempArray: Array = []; + for (let index = 0; index < 8; index++) { + if (Q) { + if (x > 0) tempArray.pop(); + tempArray.push({ question: JSON.parse(Questions)[x] }); + // tempArray.push({ loading: "loading" }); + Q = false; + x++; + } else { + tempArray.push({ answer: JSON.parse(Answers)[y] }); + if (y < JSON.parse(Answers).length - 1) + tempArray.push({ loading: "loading" }); + Q = true; + y++; + } + setUpdate([...tempArray]); + await sleep(2000); + } + }; useEffect(() => { - initWebSocket(); + switch (serviceType) { + case "text": + initWebSocket(); + console.log("Running Gen AI"); + return; + case "sim": + runSimulation(); + console.log("running simulation"); + return; + case "summary": + initWebSocket(); + console.log("summary loading"); + return; + } return () => { socket.current ? (socket.current.onclose = () => {}) : null; socket.current?.close(); }; - }, []); + }, [serviceType]); const handleQuestionChange = ( event: InputSearchElement.ojValueAction @@ -130,39 +193,91 @@ const Content = () => { // simulating the delay for now just to show what the animation looks like. setTimeout(() => { - socket.current?.send(JSON.stringify({ question: question.current })); + socket.current?.send( + JSON.stringify({ msgType: "question", data: question.current }) + ); }, 300); } }; + const handleFileUpload = (file: ArrayBuffer) => { + socket.current?.send(file); + }; + + const handleDrawerState = () => { + setSettingsOpened(false); + }; + const toggleDrawer = () => { + setSettingsOpened(!settingsOpened); + }; const handleToastClose = () => { messagesDP.current.data = []; }; + const serviceTypeChangeHandler = (service: ServiceTypes) => { + setUpdate([]); + chatData.current = []; + setServiceType(service); + toggleDrawer(); + }; + + const clearSummary = () => { + setSummaryResults(""); + }; + + const updateSummaryPrompt = (val: string) => { + setSummaryPrompt(val); + }; + return (
    + + +
    -

    + {/*

    */}
    {/*
    {connState}
    */} + + +
    -
    - -
    - + {serviceType === "text" && ( + + )} + {serviceType === "sim" && ( + + )} + {serviceType === "summary" && ( + + )}
    ); }; diff --git a/app/src/components/content/question.tsx b/app/src/components/content/question.tsx index 7afa1849..28b58f41 100644 --- a/app/src/components/content/question.tsx +++ b/app/src/components/content/question.tsx @@ -4,23 +4,45 @@ import "ojs/ojavatar"; type Props = { item: ojListView.ItemTemplateContext; + sim: boolean; }; -export const Question = ({ item }: Props) => { +export const Question = ({ item, sim }: Props) => { return ( -
  • -
    -
    - -
    -
    - {item.data.question} -
    -
    -
  • + <> + {sim && ( +
  • +
    +
    + +
    +
    + {item.data.question} +
    +
    +
  • + )} + {!sim && ( +
  • +
    +
    + +
    +
    + {item.data.question} +
    +
    +
  • + )} + ); }; diff --git a/app/src/components/content/settings.tsx b/app/src/components/content/settings.tsx new file mode 100644 index 00000000..681c51aa --- /dev/null +++ b/app/src/components/content/settings.tsx @@ -0,0 +1,48 @@ +import "preact"; +import { useState } from "preact/hooks"; +import "oj-c/radioset"; +import "oj-c/form-layout"; +import { CRadiosetElement } from "oj-c/radioset"; +import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); + +type ServiceTypeVal = "text" | "summary" | "sim"; +type Services = { + label: string; + value: ServiceTypeVal; +}; +type Props = { + serviceType: "text" | "summary" | "sim"; + serviceChange: (service: ServiceTypeVal) => void; +}; + +const serviceTypes = [ + { value: "text", label: "Generative Text" }, + { value: "summary", label: "Summarize" }, + { value: "sim", label: "Simulation" }, +]; +const serviceOptionsDP = new MutableArrayDataProvider< + Services["value"], + Services +>(serviceTypes, { keyAttributes: "value" }); + +export const Settings = (props: Props) => { + const handleServiceTypeChange = (event: any) => { + if (event.detail.updatedFrom === "internal") + props.serviceChange(event.detail.value); + }; + + return ( +
    +

    Service Settings

    + + + +
    + ); +}; diff --git a/app/src/components/content/simulation.tsx b/app/src/components/content/simulation.tsx new file mode 100644 index 00000000..8c0642c0 --- /dev/null +++ b/app/src/components/content/simulation.tsx @@ -0,0 +1,103 @@ +import { Question } from "./question"; +import { Answer } from "./answer"; +import { Loading } from "./loading"; +import { ComponentProps } from "preact"; +import { useEffect, useRef, useState, MutableRef } from "preact/hooks"; +import "ojs/ojlistview"; +import { ojListView } from "ojs/ojlistview"; +import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); +import Context = require("ojs/ojcontext"); + +type Props = { + testId?: string; + data: any; + questionChanged: (event: any) => void; + question: MutableRef; +}; + +type Item = { + id: number; + answer?: string; + question?: string; + loading?: string; +}; + +type ListProps = ComponentProps<"oj-list-view">; +const madp = new MutableArrayDataProvider([], { + keyAttributes: "id", +}); + +export const Simulation = ({ + testId, + data, + questionChanged, + question, +}: Props) => { + const dataProvider = useRef(madp); + const listRef = useRef>(null); + const [lastKey, setLastKey] = useState(0); + + const scrollPos: ListProps["scrollPosition"] = { key: lastKey }; + let busyContext = Context.getContext( + listRef.current as ojListView + ).getBusyContext(); + + useEffect(() => { + dataProvider.current.data = data; + console.log("lastKey before set: ", lastKey); + + // the use of BusyContext here should not be required. It's a workaround for JET-64237. + // it can be removed once the bug is fixed. + busyContext.whenReady().then(() => { + setLastKey(data.length - 1); + }); + }, [data, busyContext]); + + const chatNoDataTemplate = () => { + return ( +
    +
    + Be the first to ask a question! +
    +
    + ); + }; + + const chatItemTemplate = (item: ojListView.ItemTemplateContext) => { + return ( + <> + {item.data.answer && } + {item.data.loading && } + {item.data.question && } + + ); + }; + + return ( + <> +
    + + + + +
    + + + ); +}; diff --git a/app/src/components/content/summary.tsx b/app/src/components/content/summary.tsx new file mode 100644 index 00000000..275c50a4 --- /dev/null +++ b/app/src/components/content/summary.tsx @@ -0,0 +1,286 @@ +import "preact"; +import { useState, useRef, useEffect } from "preact/hooks"; +import "md-wrapper/loader"; +import "ojs/ojtoolbar"; +import "oj-c/file-picker"; +import "oj-c/message-toast"; +import "oj-c/input-text"; +import "oj-c/progress-bar"; +import "oj-c/button"; +import "ojs/ojvalidationgroup"; +import { ojValidationGroup } from "ojs/ojvalidationgroup"; +import { CFilePickerElement } from "oj-c/file-picker"; +import { CInputTextElement } from "oj-c/input-text"; +import { CButtonElement } from "oj-c/button"; +import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); + +declare global { + namespace preact.JSX { + interface IntrinsicElements { + "md-wrapper": any; + } + } +} +type Props = { + fileChanged: (file: ArrayBuffer) => void; + summary: string | null; + clear: () => void; + prompt: (val: string) => void; +}; + +const acceptArr: string[] = ["application/pdf", "*.pdf"]; +const messages: { id: number; severity: string; summary: string }[] = []; + +export const Summary = ({ fileChanged, summary, clear, prompt }: Props) => { + const [invalidMessage, setInvalidMessage] = useState(null); + const [summaryPrompt, setSummaryPrompt] = useState(""); + const [fileNames, setFileNames] = useState(null); + const [messages, setMessages] = useState([]); + const [pdfFile, setPDFFile] = useState(); + const [loading, setLoading] = useState(false); + const invalidFiles = useRef([]); + const valGroupRef = useRef(null); + const promptInputRef = useRef>(null); + + // Message toast related methods + const closeMessage = () => { + setMessages([]); + invalidFiles.current = []; + setFileNames(null); + }; + + const messagesDP = new MutableArrayDataProvider(messages, { + keyAttributes: "id", + }); + + // Prompt input related methods + const submitPrompt = (event: CInputTextElement.valueChanged) => { + let tempStr = event.detail.value + ? event.detail.value + : "Generate a summary"; + setSummaryPrompt(tempStr); + prompt(tempStr); + }; + + // FilePicker related methods + const selectListener = async (event: CFilePickerElement.ojSelect) => { + setInvalidMessage(""); + const files: FileList = event.detail.files; + const filesArray: File[] = Array.from(files); + let names = filesArray.map((file: File) => { + return file?.name; + }); + + const fr = new FileReader(); + let ab = new ArrayBuffer(200000000); + fr.onload = (ev: ProgressEvent) => { + let ab = fr.result; + setPDFFile(ab as ArrayBuffer); + }; + fr.readAsArrayBuffer(files[0]); + setFileNames(names); + }; + + const buildSummaryData = (rawData: ArrayBuffer) => { + const metaJson = { + type: "summary", + msgPrompt: summaryPrompt, + }; + + // _must_ do this to encode as a ArrayBuffer / Uint8Array + // credit to Ben Wills and this article for inspiration with the below code. + // https://enomem.io/sending-and-receiving-binary-files-via-websockets/ + const enc = new TextEncoder(); // always utf-8, Uint8Array() + const buf1 = enc.encode(JSON.stringify(metaJson)); + const buf2 = enc.encode("\r\n\r\n"); + const buf3 = rawData; + + let sendData = new Uint8Array( + buf1.byteLength + buf2.byteLength + buf3.byteLength + ); + sendData.set(new Uint8Array(buf1), 0); + sendData.set(new Uint8Array(buf2), buf1.byteLength); + sendData.set(new Uint8Array(buf3), buf1.byteLength + buf2.byteLength); + + return sendData; + }; + + const invalidListener = (event: CFilePickerElement.ojInvalidSelect) => { + setFileNames([]); + const promise = event.detail.until; + + if (promise) { + promise.then(() => { + setInvalidMessage(""); + }); + } + }; + const beforeSelectListener = (event: CFilePickerElement.ojBeforeSelect) => { + const accept: (acceptPromise: Promise) => void = event.detail.accept; + const files: FileList = event.detail.files; + let file: File; + let tempArray: Array = []; + + for (let i = 0; i < files.length; i++) { + file = files[i]; + // Cohere has a character limit of 100kb so we are restricting it here as well. + if (file.size > 100000) { + tempArray.push(file.name); + invalidFiles.current = tempArray; + } + } + + if (invalidFiles.current.length === 0) { + accept(Promise.resolve()); + } else { + if (invalidFiles.current.length === 1) { + let temp: Array = []; + temp.push({ + id: 0, + severity: "Error", + summary: + "File " + + invalidFiles.current[0] + + " is too big. The maximum size is 100KB.", + }); + setMessages(temp); + } else { + const fileNames = invalidFiles.current.join(", "); + let temp: Array = []; + temp.push({ + id: 0, + severity: "Error", + summary: + "These files are too big: " + + fileNames + + ". The maximum size is 100KB.", + }); + setMessages(temp); + } + + accept(Promise.reject(messages)); + } + }; + + useEffect(() => { + if (summary !== "") setLoading(!loading); + }, [summary]); + + useEffect(() => { + return () => { + clearSummarization(); + }; + }, []); + + const _checkValidationGroup = () => { + const tracker = valGroupRef.current as ojValidationGroup; + if (tracker.valid === "valid") { + return true; + } else { + // show messages on all the components that are invalidHiddden, i.e., the + // required fields that the user has yet to fill out. + tracker.showMessages(); + tracker.focusOn("@firstInvalidShown"); + return false; + } + }; + + // Summarize methods + const summarizeFile = (event: CButtonElement.ojAction) => { + const valid = _checkValidationGroup(); + if (valid) { + clear(); + console.log("Calling websocket API to process PDF"); + console.log("Filename: ", fileNames); + console.log("Prompt: ", summaryPrompt); + fileChanged(buildSummaryData(pdfFile as ArrayBuffer)); + setLoading(true); + } + }; + const clearSummarization = () => { + promptInputRef.current?.reset(); + setFileNames(null); + clear(); + setLoading(false); + }; + + return ( + <> + + +
    +

    Document Summarization

    +
    + Upload a PDF file +
    + + + + + {invalidFiles.current.length !== 1 && fileNames && ( + <> +
    + File: + {fileNames} +
    + + + + + + )} + {invalidFiles.current.length !== 1 && fileNames && loading && ( + <> +
    + Loading summary +
    + + + )} + {invalidFiles.current.length !== 1 && fileNames && summary && ( +
    + +
    + )} +
    + + ); +}; diff --git a/app/src/styles/app.css b/app/src/styles/app.css index baecdaa7..d0522eaa 100644 --- a/app/src/styles/app.css +++ b/app/src/styles/app.css @@ -70,6 +70,16 @@ samp { text-align: start; max-width: 1440px; } +.demo-sim-answer-layout { + min-height: 50px; + display: inherit; + margin: 20px 10px 50px 10px; + border: 2px black; + border-radius: 10px; + padding: 10px 10px 10px 65px; + text-align: end; + max-width: 1440px; +} .demo-question-layout { min-height: 50px; /* background-color: #226b95; */ @@ -81,6 +91,15 @@ samp { padding: 10px; text-align: start; } +.demo-sim-question-layout { + min-height: 50px; + display: inherit; + margin: 20px 10px 10px 10px; + border: 2px black; + border-radius: 10px; + padding: 10px; + text-align: start; +} .demo-chat-layout { max-height: 100%; diff --git a/app/src/styles/images/ai.svg b/app/src/styles/images/ai.svg new file mode 100644 index 00000000..46d7b087 --- /dev/null +++ b/app/src/styles/images/ai.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/styles/images/placeholder-female-01.png b/app/src/styles/images/placeholder-female-01.png new file mode 100644 index 00000000..d0e1fcfc Binary files /dev/null and b/app/src/styles/images/placeholder-female-01.png differ diff --git a/app/src/styles/images/placeholder-female-02.png b/app/src/styles/images/placeholder-female-02.png new file mode 100644 index 00000000..4dc34f09 Binary files /dev/null and b/app/src/styles/images/placeholder-female-02.png differ diff --git a/app/src/styles/images/placeholder-male-01.png b/app/src/styles/images/placeholder-male-01.png new file mode 100644 index 00000000..f9e2213d Binary files /dev/null and b/app/src/styles/images/placeholder-male-01.png differ diff --git a/app/src/styles/images/placeholder-male-05.png b/app/src/styles/images/placeholder-male-05.png new file mode 100644 index 00000000..3c6f798c Binary files /dev/null and b/app/src/styles/images/placeholder-male-05.png differ diff --git a/architecture.drawio b/architecture.drawio new file mode 100644 index 00000000..2adbfe68 --- /dev/null +++ b/architecture.drawio @@ -0,0 +1,889 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..e69de29b diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..80ce81c9 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,15 @@ +FROM --platform=linux/amd64 container-registry.oracle.com/java/jdk-no-fee-term:21-oraclelinux8 as build + +RUN mkdir /opt/src +COPY . /opt/src +WORKDIR /opt/src +RUN ./gradlew clean bootJar + +FROM --platform=linux/amd64 container-registry.oracle.com/java/jdk-no-fee-term:21-oraclelinux8 + +VOLUME /tmp +COPY --from=build /opt/src/build/libs/*.jar /tmp/ +RUN mkdir /app +RUN mv /tmp/backend*.jar /app/backend.jar + +ENTRYPOINT ["java","-jar","/app/backend.jar"] \ No newline at end of file diff --git a/backend/build.gradle b/backend/build.gradle new file mode 100644 index 00000000..4f2a17c5 --- /dev/null +++ b/backend/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.2.2' + id 'io.spring.dependency-management' version '1.1.4' +} + +group = 'dev.victormartin.oci.genai.backend' +version = '0.0.3' + +java { + sourceCompatibility = '17' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-rest' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'com.oracle.oci.sdk:oci-java-sdk-shaded-full:3.33.0' + implementation 'com.oracle.oci.sdk:oci-java-sdk-core:3.35.0' + implementation 'com.oracle.oci.sdk:oci-java-sdk-common:3.35.0' + implementation 'com.oracle.oci.sdk:oci-java-sdk-addons-oke-workload-identity:3.35.0' + implementation 'com.oracle.oci.sdk:oci-java-sdk-generativeai:3.35.0' + implementation 'com.oracle.database.jdbc:ojdbc11-production:21.8.0.0' + implementation 'com.oracle.database.jdbc:ucp:21.8.0.0' + implementation 'com.oracle.database.security:oraclepki:21.8.0.0' + implementation 'com.oracle.database.security:osdt_cert:21.8.0.0' + implementation 'com.oracle.database.security:osdt_core:21.8.0.0' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..d64cd491 Binary files /dev/null and b/backend/gradle/wrapper/gradle-wrapper.jar differ diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/backend/gradlew b/backend/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/backend/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/backend/gradlew.bat b/backend/gradlew.bat new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/backend/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/backend/settings.gradle b/backend/settings.gradle new file mode 100644 index 00000000..0f5036dc --- /dev/null +++ b/backend/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'backend' diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/BackendApplication.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/BackendApplication.java new file mode 100644 index 00000000..cd8bddc8 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/BackendApplication.java @@ -0,0 +1,13 @@ +package dev.victormartin.oci.genai.backend.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BackendApplication { + + public static void main(String[] args) { + SpringApplication.run(BackendApplication.class, args); + } + +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/ClientConfigurationBean.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/ClientConfigurationBean.java new file mode 100644 index 00000000..aeba0c91 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/ClientConfigurationBean.java @@ -0,0 +1,19 @@ +package dev.victormartin.oci.genai.backend.backend; + +import com.oracle.bmc.ClientConfiguration; +import com.oracle.bmc.retrier.RetryConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ClientConfigurationBean { + @Bean + public ClientConfiguration clientConfiguration() { + ClientConfiguration clientConfiguration = + ClientConfiguration.builder() + .readTimeoutMillis(240000) + .retryConfiguration(RetryConfiguration.NO_RETRY_CONFIGURATION) + .build(); + return clientConfiguration; + } +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenAIController.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenAIController.java new file mode 100644 index 00000000..e30bcc18 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenAIController.java @@ -0,0 +1,47 @@ +package dev.victormartin.oci.genai.backend.backend; + +import com.oracle.bmc.ClientConfiguration; +import com.oracle.bmc.generativeai.GenerativeAiClient; +import com.oracle.bmc.generativeai.model.ModelCapability; +import com.oracle.bmc.generativeai.requests.ListModelsRequest; +import com.oracle.bmc.generativeai.responses.ListModelsResponse; +import dev.victormartin.oci.genai.backend.backend.dao.GenAiModel; +import org.bouncycastle.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +public class GenAIController { + Logger logger = LoggerFactory.getLogger(GenAIController.class); + + @Value("${genai.compartment_id}") + private String COMPARTMENT_ID; + + @Autowired + private GenerativeAiClient generativeAiClient; + + // repository + + // constructor + + @GetMapping("/api/genai/models") + public List getModels() { + logger.info("getModels()"); + ListModelsRequest listModelsRequest = ListModelsRequest.builder().compartmentId(COMPARTMENT_ID).build(); + ListModelsResponse response = generativeAiClient.listModels(listModelsRequest); + return response.getModelCollection().getItems().stream().map(m -> { + List capabilities = m.getCapabilities().stream().map(ModelCapability::getValue).collect(Collectors.toList()); + GenAiModel model = new GenAiModel(m.getId(),m.getDisplayName(), m.getVendor(), m.getVersion(), + capabilities, + m.getTimeCreated()); + return model; + }).collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenerativeAiClientConfig.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenerativeAiClientConfig.java new file mode 100644 index 00000000..6061b5ad --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenerativeAiClientConfig.java @@ -0,0 +1,90 @@ +package dev.victormartin.oci.genai.backend.backend; + +import com.oracle.bmc.ClientConfiguration; +import com.oracle.bmc.ConfigFileReader; +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AuthenticationDetailsProvider; +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; +import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider; +import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider; +import com.oracle.bmc.generativeai.GenerativeAiClient; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import java.io.IOException; + +@Configuration +public class GenerativeAiClientConfig { + + Logger logger = LoggerFactory.getLogger(GenerativeAiClientConfig.class); + + @Autowired + private Environment environment; + + @Autowired + ClientConfiguration clientConfiguration; + + @Value("${genai.endpoint}") + private String ENDPOINT; + @Value("${genai.region}") + private String regionCode; + @Value("${genai.config.location}") + private String CONFIG_LOCATION; + @Value("${genai.config.profile}") + private String CONFIG_PROFILE; + + @Value("${genai.model_id}") + private String modelId; + + private Region region; + + @PostConstruct + private void postConstruct() { + logger.info("Region Code: " + regionCode); + region = Region.fromRegionCode(regionCode); + } + + @Bean + GenerativeAiClient genAiClient() throws IOException { + String[] activeProfiles = environment.getActiveProfiles(); + String profile = activeProfiles[0]; + if (profile.equals("production")) { + return instancePrincipalConfig(); + } else { + return localConfig(); + } + } + + GenerativeAiClient instancePrincipalConfig() throws IOException { + final OkeWorkloadIdentityAuthenticationDetailsProvider okeProvider = + new OkeWorkloadIdentityAuthenticationDetailsProvider + .OkeWorkloadIdentityAuthenticationDetailsProviderBuilder() + .build(); +// final InstancePrincipalsAuthenticationDetailsProvider provider = +// new InstancePrincipalsAuthenticationDetailsProvider.InstancePrincipalsAuthenticationDetailsProviderBuilder().build(); + + GenerativeAiClient generativeAiClient = new GenerativeAiClient(okeProvider, clientConfiguration); + generativeAiClient.setRegion(okeProvider.getRegion()); + generativeAiClient.setEndpoint(ENDPOINT); + return generativeAiClient; + } + + GenerativeAiClient localConfig() throws IOException { + // Configuring the AuthenticationDetailsProvider. It's assuming there is a default OCI config file + // "~/.oci/config", and a profile in that config with the name defined in CONFIG_PROFILE variable. + final ConfigFileReader.ConfigFile configFile = ConfigFileReader.parse(CONFIG_LOCATION, CONFIG_PROFILE); + final AuthenticationDetailsProvider provider = new ConfigFileAuthenticationDetailsProvider(configFile); + + GenerativeAiClient generativeAiClient = new GenerativeAiClient(provider, + clientConfiguration); + generativeAiClient.setEndpoint(ENDPOINT); + generativeAiClient.setRegion(region); + return generativeAiClient; + } +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenerativeAiInferenceClientConfig.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenerativeAiInferenceClientConfig.java new file mode 100644 index 00000000..ae4c65bb --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/GenerativeAiInferenceClientConfig.java @@ -0,0 +1,87 @@ +package dev.victormartin.oci.genai.backend.backend; + +import com.oracle.bmc.ClientConfiguration; +import com.oracle.bmc.ConfigFileReader; +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AuthenticationDetailsProvider; +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; +import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider; +import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import java.io.IOException; + +@Configuration +public class GenerativeAiInferenceClientConfig { + + Logger logger = LoggerFactory.getLogger(GenerativeAiInferenceClientConfig.class); + + @Autowired + private Environment environment; + + @Autowired + ClientConfiguration clientConfiguration; + + @Value("${genai.endpoint}") + private String ENDPOINT; + @Value("${genai.region}") + private String regionCode; + @Value("${genai.config.location}") + private String CONFIG_LOCATION; + @Value("${genai.config.profile}") + private String CONFIG_PROFILE; + + @Value("${genai.model_id}") + private String modelId; + + private Region region; + + @PostConstruct + private void postConstruct() { + logger.info("Region Code: " + regionCode); + region = Region.fromRegionCode(regionCode); + } + + @Bean + GenerativeAiInferenceClient genAiInferenceClient() throws IOException { + String[] activeProfiles = environment.getActiveProfiles(); + String profile = activeProfiles[0]; + logger.info("Profile: " + profile); + if (profile.equals("production")) { + return instancePrincipalConfig(); + } else { + return localConfig(); + } + } + + GenerativeAiInferenceClient instancePrincipalConfig() throws IOException { + final InstancePrincipalsAuthenticationDetailsProvider provider = + new InstancePrincipalsAuthenticationDetailsProvider.InstancePrincipalsAuthenticationDetailsProviderBuilder().build(); + + GenerativeAiInferenceClient generativeAiInferenceClient = new GenerativeAiInferenceClient(provider, + clientConfiguration); + generativeAiInferenceClient.setEndpoint(ENDPOINT); + generativeAiInferenceClient.setRegion(provider.getRegion()); + return generativeAiInferenceClient; + } + + GenerativeAiInferenceClient localConfig() throws IOException { + // Configuring the AuthenticationDetailsProvider. It's assuming there is a default OCI config file + // "~/.oci/config", and a profile in that config with the name defined in CONFIG_PROFILE variable. + final ConfigFileReader.ConfigFile configFile = ConfigFileReader.parse(CONFIG_LOCATION, CONFIG_PROFILE); + final AuthenticationDetailsProvider provider = new ConfigFileAuthenticationDetailsProvider(configFile); + + GenerativeAiInferenceClient generativeAiInferenceClient = new GenerativeAiInferenceClient(provider, + clientConfiguration); + generativeAiInferenceClient.setEndpoint(ENDPOINT); + generativeAiInferenceClient.setRegion(region); + return generativeAiInferenceClient; + } +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/InvalidPromptRequest.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/InvalidPromptRequest.java new file mode 100644 index 00000000..96f0fa75 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/InvalidPromptRequest.java @@ -0,0 +1,8 @@ +package dev.victormartin.oci.genai.backend.backend; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Invalid request params") +public class InvalidPromptRequest extends RuntimeException { +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/OCIGenAIService.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/OCIGenAIService.java new file mode 100644 index 00000000..21b91c3c --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/OCIGenAIService.java @@ -0,0 +1,49 @@ +package dev.victormartin.oci.genai.backend.backend; + +import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient; +import com.oracle.bmc.generativeaiinference.model.*; +import com.oracle.bmc.generativeaiinference.requests.GenerateTextRequest; +import com.oracle.bmc.generativeaiinference.responses.GenerateTextResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.stream.Collectors; + +@Service +public class OCIGenAIService { + @Value("${genai.compartment_id}") + private String COMPARTMENT_ID; + + @Autowired + private GenerativeAiInferenceClient generativeAiInferenceClient; + + public String request(String input, String modelId) { + // Build generate text request, send, and get response + CohereLlmInferenceRequest llmInferenceRequest = + CohereLlmInferenceRequest.builder() + .prompt(input) + .maxTokens(600) + .temperature((double)1) + .frequencyPenalty((double)0) + .topP((double)0.75) + .isStream(false) + .isEcho(false) + .build(); + + GenerateTextDetails generateTextDetails = GenerateTextDetails.builder() + .servingMode(OnDemandServingMode.builder().modelId(modelId).build()) + .compartmentId(COMPARTMENT_ID) + .inferenceRequest(llmInferenceRequest) + .build(); + GenerateTextRequest generateTextRequest = GenerateTextRequest.builder() + .generateTextDetails(generateTextDetails) + .build(); + GenerateTextResponse generateTextResponse = generativeAiInferenceClient.generateText(generateTextRequest); + CohereLlmInferenceResponse response = + (CohereLlmInferenceResponse) generateTextResponse.getGenerateTextResult().getInferenceResponse(); + String responseTexts = response.getGeneratedTexts().stream().map(t -> t.getText()).collect(Collectors.joining(",")); + return responseTexts; + } +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/PromptController.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/PromptController.java new file mode 100644 index 00000000..8fb0777b --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/PromptController.java @@ -0,0 +1,72 @@ +package dev.victormartin.oci.genai.backend.backend; + +import com.oracle.bmc.model.BmcException; +import dev.victormartin.oci.genai.backend.backend.dao.Answer; +import dev.victormartin.oci.genai.backend.backend.dao.Prompt; +import dev.victormartin.oci.genai.backend.backend.data.Interaction; +import dev.victormartin.oci.genai.backend.backend.data.InteractionRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.stereotype.Controller; +import org.springframework.web.util.HtmlUtils; + +import java.util.Date; + +@Controller +public class PromptController { + Logger logger = LoggerFactory.getLogger(PromptController.class); + + @Autowired + private final InteractionRepository interactionRepository; + + @Autowired + OCIGenAIService genAI; + + public PromptController(InteractionRepository interactionRepository, OCIGenAIService genAI) { + this.interactionRepository = interactionRepository; + this.genAI = genAI; + } + + @MessageMapping("/prompt") + @SendToUser("/queue/answer") + public Answer handlePrompt(Prompt prompt) { + String promptEscaped = HtmlUtils.htmlEscape(prompt.content()); + logger.info("Prompt " + promptEscaped + " received, on model " + prompt.modelId()); + Interaction interaction = new Interaction(); + interaction.setConversationId(prompt.conversationId()); + interaction.setDatetimeRequest(new Date()); + interaction.setModelId(prompt.modelId()); + interaction.setRequest(promptEscaped); + Interaction saved = interactionRepository.save(interaction); + try { + if (prompt.content() == null || prompt.content().length()< 1) { throw new InvalidPromptRequest(); } + if (prompt.modelId() == null || !prompt.modelId().startsWith("ocid1.generativeaimodel.")) { throw new InvalidPromptRequest(); } + String responseFromGenAI = genAI.request(promptEscaped, prompt.modelId()); + saved.setDatetimeResponse(new Date()); + saved.setResponse(responseFromGenAI); + interactionRepository.save(saved); + return new Answer(responseFromGenAI, ""); + } catch (BmcException exception) { + logger.error(exception.getOriginalMessage()); + String unmodifiedMessage = exception.getUnmodifiedMessage(); + int statusCode = exception.getStatusCode(); + String errorMessage = statusCode + " " + unmodifiedMessage; + logger.error(errorMessage); + saved.setErrorMessage(errorMessage); + interactionRepository.save(saved); + return new Answer("", errorMessage); + } catch (InvalidPromptRequest exception) { + int statusCode = HttpStatus.BAD_REQUEST.value(); + String errorMessage = statusCode + " Invalid Prompt "; + logger.error(errorMessage); + saved.setErrorMessage(errorMessage); + interactionRepository.save(saved); + return new Answer("", errorMessage); + } + } + +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/WebSocketConfig.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/WebSocketConfig.java new file mode 100644 index 00000000..8074e3e0 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/WebSocketConfig.java @@ -0,0 +1,25 @@ +package dev.victormartin.oci.genai.backend.backend; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/queue"); + config.setApplicationDestinationPrefixes("/genai"); + config.setUserDestinationPrefix("/user"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/websocket").setAllowedOrigins("*"); + } + +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/Answer.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/Answer.java new file mode 100644 index 00000000..b74b0119 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/Answer.java @@ -0,0 +1,32 @@ +package dev.victormartin.oci.genai.backend.backend.dao; + +public class Answer { + + private String content; + + private String errorMessage; + + public Answer() { + } + + public Answer(String content, String errorMessage) { + this.content = content; + this.errorMessage = errorMessage; + } + public void setContent(String content) { + this.content = content; + } + + public String getContent() { + return content; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/GenAiModel.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/GenAiModel.java new file mode 100644 index 00000000..233a9664 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/GenAiModel.java @@ -0,0 +1,8 @@ +package dev.victormartin.oci.genai.backend.backend.dao; + +import java.util.Date; +import java.util.List; + +public record GenAiModel(String id, String name, String vendor, String version, List capabilities, + Date timeCreated) { +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/Prompt.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/Prompt.java new file mode 100644 index 00000000..54a23559 --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/dao/Prompt.java @@ -0,0 +1,3 @@ +package dev.victormartin.oci.genai.backend.backend.dao; + +public record Prompt(String content, String conversationId, String modelId) {}; \ No newline at end of file diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/data/Interaction.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/data/Interaction.java new file mode 100644 index 00000000..0c1b915a --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/data/Interaction.java @@ -0,0 +1,111 @@ +package dev.victormartin.oci.genai.backend.backend.data; + +import jakarta.persistence.*; + +import java.util.Date; +import java.util.Objects; + +@Entity +public class Interaction { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + String conversationId; + + @Temporal(TemporalType.DATE) + Date datetimeRequest; + + String modelId; + + @Lob + @Column + String request; + + @Temporal(TemporalType.DATE) + Date datetimeResponse; + + @Lob + @Column + String response; + + @Lob + @Column + String errorMessage; + + public Interaction() { + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Interaction that = (Interaction) o; + return Objects.equals(id, that.id) && Objects.equals(conversationId, that.conversationId) && Objects.equals(datetimeRequest, that.datetimeRequest) && Objects.equals(modelId, that.modelId) && Objects.equals(request, that.request) && Objects.equals(datetimeResponse, that.datetimeResponse) && Objects.equals(response, that.response) && Objects.equals(errorMessage, that.errorMessage); + } + + @Override + public int hashCode() { + return Objects.hash(id, conversationId, datetimeRequest, modelId, request, datetimeResponse, response, errorMessage); + } + + public Long getId() { + return id; + } + + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + this.conversationId = conversationId; + } + + public Date getDatetimeRequest() { + return datetimeRequest; + } + + public void setDatetimeRequest(Date datetimeRequest) { + this.datetimeRequest = datetimeRequest; + } + + public String getModelId() { + return modelId; + } + + public void setModelId(String modelId) { + this.modelId = modelId; + } + + public String getRequest() { + return request; + } + + public void setRequest(String request) { + this.request = request; + } + + public Date getDatetimeResponse() { + return datetimeResponse; + } + + public void setDatetimeResponse(Date datetimeResponse) { + this.datetimeResponse = datetimeResponse; + } + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/data/InteractionRepository.java b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/data/InteractionRepository.java new file mode 100644 index 00000000..32b1ccca --- /dev/null +++ b/backend/src/main/java/dev/victormartin/oci/genai/backend/backend/data/InteractionRepository.java @@ -0,0 +1,6 @@ +package dev.victormartin.oci.genai.backend.backend.data; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface InteractionRepository extends JpaRepository { +} diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml new file mode 100644 index 00000000..ea57646c --- /dev/null +++ b/backend/src/main/resources/application.yaml @@ -0,0 +1,32 @@ +spring: + main: + banner-mode: "off" + profiles: + active: default + datasource: + driver-class-name: oracle.jdbc.OracleDriver + url: jdbc:oracle:thin:@DB_SERVICE_high?TNS_ADMIN=/PATH/TO/WALLET + username: ADMIN + password: "PASSWORD" + type: oracle.ucp.jdbc.PoolDataSource + oracleucp: + sql-for-validate-connection: SELECT * FROM dual + connection-pool-name: connectionPoolName1 + initial-pool-size: 5 + min-pool-size: 5 + max-pool-size: 10 + jpa: + hibernate: + use-new-id-generator-mappings: false + ddl-auto: update +oracle: + jdbc: + fanEnabled: true + +genai: + endpoint: "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com" + region: "US_CHICAGO_1" + config: + location: "~/.oci/config" + profile: "DEFAULT" + compartment_id: "COMPARTMENT_ID" diff --git a/backend/src/test/java/dev/victormartin/oci/genai/backend/backend/BackendApplicationTests.java b/backend/src/test/java/dev/victormartin/oci/genai/backend/backend/BackendApplicationTests.java new file mode 100644 index 00000000..1da255b4 --- /dev/null +++ b/backend/src/test/java/dev/victormartin/oci/genai/backend/backend/BackendApplicationTests.java @@ -0,0 +1,13 @@ +package dev.victormartin.oci.genai.backend.backend; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class BackendApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/deploy/k8s/backend/application.yaml.mustache b/deploy/k8s/backend/application.yaml.mustache new file mode 100644 index 00000000..745be48b --- /dev/null +++ b/deploy/k8s/backend/application.yaml.mustache @@ -0,0 +1,30 @@ +spring: + main: + banner-mode: "off" + profiles: + active: production + datasource: + driver-class-name: oracle.jdbc.OracleDriver + url: jdbc:oracle:thin:@{{{db_service}}}_high?TNS_ADMIN={{{path_to_wallet}}} + username: ADMIN + password: "{{{db_password}}}" + type: oracle.ucp.jdbc.PoolDataSource + oracleucp: + sql-for-validate-connection: SELECT * FROM dual + connection-pool-name: connectionPoolName1 + initial-pool-size: 5 + min-pool-size: 5 + max-pool-size: 10 + jpa: + hibernate: + use-new-id-generator-mappings: false + ddl-auto: update +oracle: + jdbc: + fanEnabled: true + +genai: + endpoint: "https://inference.generativeai.{{{region_name}}}.oci.oraclecloud.com" + region: "{{{region_name}}}" + compartment_id: "{{{compartment_ocid}}}" + model_id: "{{{genai_model_ocid}}}" diff --git a/deploy/k8s/backend/backend-svc.yaml b/deploy/k8s/backend/backend-svc.yaml new file mode 100644 index 00000000..f65352a4 --- /dev/null +++ b/deploy/k8s/backend/backend-svc.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: backend + name: backend +spec: + ports: + - port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: backend \ No newline at end of file diff --git a/deploy/k8s/backend/backend.yaml b/deploy/k8s/backend/backend.yaml new file mode 100644 index 00000000..2f7d61b9 --- /dev/null +++ b/deploy/k8s/backend/backend.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: backend + name: backend +spec: + replicas: 1 + minReadySeconds: 60 + selector: + matchLabels: + app: backend + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + template: + metadata: + labels: + app: backend + spec: + serviceAccountName: genai-sa + automountServiceAccountToken: true + initContainers: + - name: unzip + image: busybox + command: ["unzip", "/walletzip/wallet.zip", "-d", "/wallet"] + volumeMounts: + - name: wallet-config + mountPath: /walletzip + - name: wallet-volume + mountPath: /wallet + containers: + - image: backend + imagePullPolicy: "Always" + name: backend + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: 8080 + initialDelaySeconds: 90 + periodSeconds: 5 + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: 8080 + initialDelaySeconds: 90 + periodSeconds: 5 + ports: + - containerPort: 8080 + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 500m + memory: 1024Mi + volumeMounts: + - name: config-volume + mountPath: /config + - name: wallet-volume + mountPath: /wallet + volumes: + - name: config-volume + configMap: + name: backend-properties + - name: wallet-config + configMap: + name: wallet-zip + - name: wallet-volume + emptyDir: + sizeLimit: 50Mi + imagePullSecrets: + - name: ocir-secret \ No newline at end of file diff --git a/deploy/k8s/backend/genai-sa.yaml b/deploy/k8s/backend/genai-sa.yaml new file mode 100644 index 00000000..bcf1f435 --- /dev/null +++ b/deploy/k8s/backend/genai-sa.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: genai-sa diff --git a/deploy/k8s/backend/kustomization.yaml b/deploy/k8s/backend/kustomization.yaml new file mode 100644 index 00000000..2e18e01e --- /dev/null +++ b/deploy/k8s/backend/kustomization.yaml @@ -0,0 +1,11 @@ +resources: + - genai-sa.yaml + - backend.yaml + - backend-svc.yaml +configMapGenerator: + - name: backend-properties + files: + - application.yaml + - name: wallet-zip + files: + - wallet/wallet.zip \ No newline at end of file diff --git a/deploy/k8s/ingress/ingress-controller.yaml b/deploy/k8s/ingress/ingress-controller.yaml new file mode 100644 index 00000000..06e08025 --- /dev/null +++ b/deploy/k8s/ingress/ingress-controller.yaml @@ -0,0 +1,333 @@ +apiVersion: v1 +data: + allow-snippet-annotations: "true" +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-controller + namespace: ingress-nginx +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + annotations: + service.beta.kubernetes.io/oci-load-balancer-shape: "flexible" + service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10Mbps" + service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "40Mbps" + # nginx.ingress.kubernetes.io/affinity: "cookie" + # nginx.ingress.kubernetes.io/session-cookie-name: "sticky" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600" + nginx.ingress.kubernetes.io/websocket-services: "ws-server" + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + externalTrafficPolicy: Local + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: LoadBalancer +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-controller-admission + namespace: ingress-nginx +spec: + ports: + - appProtocol: https + name: https-webhook + port: 443 + targetPort: webhook + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + minReadySeconds: 0 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + spec: + containers: + - args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + image: registry.k8s.io/ingress-nginx/controller:v1.5.1@sha256:4ba73c697770664c1e00e9f968de14e08f606ff961c76e5d7033a4a9c593c629 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: controller + ports: + - containerPort: 80 + name: http + protocol: TCP + - containerPort: 443 + name: https + protocol: TCP + - containerPort: 8443 + name: webhook + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 90Mi + securityContext: + allowPrivilegeEscalation: true + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + runAsUser: 101 + volumeMounts: + - mountPath: /usr/local/certificates/ + name: webhook-cert + readOnly: true + dnsPolicy: ClusterFirst + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-create + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-create + spec: + containers: + - args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20220916-gd32f8c343@sha256:39c5b2e3310dc4264d638ad28d9d1d96c4cbb2b2dcfb52368fe4e3c63f61e10f + imagePullPolicy: IfNotPresent + name: create + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-patch + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-patch + spec: + containers: + - args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20220916-gd32f8c343@sha256:39c5b2e3310dc4264d638ad28d9d1d96c4cbb2b2dcfb52368fe4e3c63f61e10f + imagePullPolicy: IfNotPresent + name: patch + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: nginx +spec: + controller: k8s.io/ingress-nginx +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: ingress-nginx-controller-admission + namespace: ingress-nginx + path: /networking/v1/ingresses + failurePolicy: Fail + matchPolicy: Equivalent + name: validate.nginx.ingress.kubernetes.io + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None \ No newline at end of file diff --git a/deploy/k8s/ingress/ingress-ns.yaml b/deploy/k8s/ingress/ingress-ns.yaml new file mode 100644 index 00000000..a7e998d0 --- /dev/null +++ b/deploy/k8s/ingress/ingress-ns.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + name: ingress-nginx \ No newline at end of file diff --git a/deploy/k8s/ingress/ingress-rbac.yaml b/deploy/k8s/ingress/ingress-rbac.yaml new file mode 100644 index 00000000..bcbf9d3e --- /dev/null +++ b/deploy/k8s/ingress/ingress-rbac.yaml @@ -0,0 +1,327 @@ +apiVersion: v1 +automountServiceAccountToken: true +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx + namespace: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resourceNames: + - ingress-nginx-leader + resources: + - configmaps + verbs: + - get + - update +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create +- apiGroups: + - coordination.k8s.io + resourceNames: + - ingress-nginx-leader + resources: + - leases + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission + namespace: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + - namespaces + verbs: + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - get +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission +rules: +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx +--- \ No newline at end of file diff --git a/deploy/k8s/ingress/ingress.yaml b/deploy/k8s/ingress/ingress.yaml new file mode 100644 index 00000000..2e846edc --- /dev/null +++ b/deploy/k8s/ingress/ingress.yaml @@ -0,0 +1,32 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress +spec: + ingressClassName: nginx + tls: + - secretName: tls + rules: + - http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /websocket + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: / + pathType: Prefix + backend: + service: + name: web + port: + number: 80 \ No newline at end of file diff --git a/deploy/k8s/ingress/kustomization.yaml b/deploy/k8s/ingress/kustomization.yaml new file mode 100644 index 00000000..91171f17 --- /dev/null +++ b/deploy/k8s/ingress/kustomization.yaml @@ -0,0 +1,11 @@ +resources: + - ingress-ns.yaml + - ingress-rbac.yaml + - ingress-controller.yaml + - ingress.yaml +secretGenerator: +- name: tls + files: + - .certs/tls.crt + - .certs/tls.key + type: "kubernetes.io/tls" \ No newline at end of file diff --git a/deploy/k8s/overlays/prod/kustomization.yaml.mustache b/deploy/k8s/overlays/prod/kustomization.yaml.mustache new file mode 100644 index 00000000..204c52e4 --- /dev/null +++ b/deploy/k8s/overlays/prod/kustomization.yaml.mustache @@ -0,0 +1,12 @@ +resources: + - "../../ingress" + - "../../web" + - "../../backend" + +images: +- name: web + newName: {{region_key}}.ocir.io/{{{tenancy_namespace}}}/{{{project_name}}}/web + newTag: {{{web_version}}} +- name: backend + newName: {{region_key}}.ocir.io/{{{tenancy_namespace}}}/{{{project_name}}}/backend + newTag: {{{backend_version}}} \ No newline at end of file diff --git a/deploy/k8s/web/kustomization.yaml b/deploy/k8s/web/kustomization.yaml new file mode 100644 index 00000000..42d4e11e --- /dev/null +++ b/deploy/k8s/web/kustomization.yaml @@ -0,0 +1,3 @@ +resources: + - web.yaml + - web-svc.yaml \ No newline at end of file diff --git a/deploy/k8s/web/web-svc.yaml b/deploy/k8s/web/web-svc.yaml new file mode 100644 index 00000000..bec7ac84 --- /dev/null +++ b/deploy/k8s/web/web-svc.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: web + name: web +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: web \ No newline at end of file diff --git a/deploy/k8s/web/web.yaml b/deploy/k8s/web/web.yaml new file mode 100644 index 00000000..3aaa235f --- /dev/null +++ b/deploy/k8s/web/web.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: web + name: web +spec: + replicas: 1 + selector: + matchLabels: + app: web + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + template: + metadata: + labels: + app: web + spec: + containers: + - image: web + name: web + readinessProbe: + httpGet: + scheme: HTTP + path: /index.html + port: 80 + initialDelaySeconds: 10 + periodSeconds: 5 + ports: + - containerPort: 80 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 250m + memory: 521Mi + imagePullSecrets: + - name: ocir-secret \ No newline at end of file diff --git a/deploy/terraform/.terraform.lock.hcl b/deploy/terraform/.terraform.lock.hcl new file mode 100644 index 00000000..854822c7 --- /dev/null +++ b/deploy/terraform/.terraform.lock.hcl @@ -0,0 +1,164 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/cloudinit" { + version = "2.3.4" + constraints = ">= 2.2.0" + hashes = [ + "h1:S3j8poSaLbaftlKq2STBkQEkZH253ZLaHhBHBifdpBQ=", + "zh:09f1f1e1d232da96fbf9513b0fb5263bc2fe9bee85697aa15d40bb93835efbeb", + "zh:381e74b90d7a038c3a8dcdcc2ce8c72d6b86da9f208a27f4b98cabe1a1032773", + "zh:398eb321949e28c4c5f7c52e9b1f922a10d0b2b073b7db04cb69318d24ffc5a9", + "zh:4a425679614a8f0fe440845828794e609b35af17db59134c4f9e56d61e979813", + "zh:4d955d8608ece4984c9f1dacda2a59fdb4ea6b0243872f049b388181aab8c80a", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a48fbee1d58d55a1f4c92c2f38c83a37c8b2f2701ed1a3c926cefb0801fa446a", + "zh:b748fe6631b16a1dafd35a09377c3bffa89552af584cf95f47568b6cd31fc241", + "zh:d4b931f7a54603fa4692a2ec6e498b95464babd2be072bed5c7c2e140a280d99", + "zh:f1c9337fcfe3a7be39d179eb7986c22a979cfb2c587c05f1b3b83064f41785c5", + "zh:f58fc57edd1ee3250a28943cd84de3e4b744cdb52df0356a53403fc240240636", + "zh:f5f50de0923ff530b03e1bca0ac697534d61bb3e5fc7f60e13becb62229097a9", + ] +} + +provider "registry.terraform.io/hashicorp/helm" { + version = "2.13.1" + constraints = ">= 2.9.0" + hashes = [ + "h1:crwHSTDCQ6fS8dQYGkoi700MI5UpbA2BDLgMZgL3B+E=", + "zh:1bf0ae1ecfd2a5d5a57f695a33b2328ef197138f27ff372fed820c975eac9783", + "zh:4676295e3a929848b98869d3040f54f17fbed3d133342b6a1f7b72d5797239e0", + "zh:4bf3705e061e28d16a525aad9229fdd842cdc96f7c23d040d3148957ba3149d8", + "zh:69db9550eacd61d85cf456d438f08addfefea4fcbc4f4a8119105093ea3d950a", + "zh:6e11560e3ea61b141f03842771bfad143ff1c56bd0d1bc01069496107cad0ab6", + "zh:733ea41e2eb4bd63cfdae6886ed47d224dabb0cd37959c6e2b213b1914a80121", + "zh:74caefb2dc8e6055259d716c11194cc0709261c592d41466abf2dc0b21d88297", + "zh:89682ab50b5cf1f1c41eabfc76f53a56482ac7b4bf77d9cb087d789524fd3e31", + "zh:a5ff95092f2f123027b89f585612a225c9bce7e65977b4ffaf4de3ae3e7870bc", + "zh:c85fce024cb5a387702ceb42a3a06e32519cd1e61bc9dd820a762da21110ab96", + "zh:d828ef2db612798179322bcb3fe829a43dd47e740cabb67e3654c8561ae661ff", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/http" { + version = "3.4.2" + constraints = ">= 3.2.1" + hashes = [ + "h1:vaoPfsLm6mOk6avKTrWi35o+9p4fEeZAY3hzYoXVTfo=", + "zh:0ba051c9c8659ce0fec94a3d50926745f11759509c4d6de0ad5f5eb289f0edd9", + "zh:23e6760e8406fef645913bf47bfab1ca984c1c5805d2bb0ef8310b16913d29cd", + "zh:3c69fde4548bfe65b968534c4df8d699648c921d6a065b97fec5faece73a442b", + "zh:41c7f9a8c117704b7a8fa96a57ebfb92b72129d9625128eeb0dee7d5a09d1110", + "zh:59d09d2e00727df10565cc82a33250b44201fcd353eb2b1579507a5a0adcce18", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:c95b2f63d4357b3068531b90d9dca62a32551d7693defb7ab14b650b5d139c57", + "zh:cc0a3bbd3026191b35f417d3a8f26bdfad376d15be9e8d99a8803487ca5b0105", + "zh:d1185c6abb3ba25123fb7df1ad7dbe2b9cd8f43962628da551040fbe1934656f", + "zh:dfb26fccab7ecdc150f67415e6cfe19d699dc43e8bf5722f36032b17b46a0fbe", + "zh:eb1fcc00073bc0463f64e49600a73d925b1a0c0ae5b94dd7b67d3ebac248a113", + "zh:ec9b9ad69cf790cb0603a1036d758063bbbc35c0c75f72dd04a1eddaf46ad010", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.5.1" + constraints = "~> 2.0" + hashes = [ + "h1:/GAVA/xheGQcbOZEq0qxANOg+KVLCA7Wv8qluxhTjhU=", + "zh:0af29ce2b7b5712319bf6424cb58d13b852bf9a777011a545fac99c7fdcdf561", + "zh:126063ea0d79dad1f68fa4e4d556793c0108ce278034f101d1dbbb2463924561", + "zh:196bfb49086f22fd4db46033e01655b0e5e036a5582d250412cc690fa7995de5", + "zh:37c92ec084d059d37d6cffdb683ccf68e3a5f8d2eb69dd73c8e43ad003ef8d24", + "zh:4269f01a98513651ad66763c16b268f4c2da76cc892ccfd54b401fff6cc11667", + "zh:51904350b9c728f963eef0c28f1d43e73d010333133eb7f30999a8fb6a0cc3d8", + "zh:73a66611359b83d0c3fcba2984610273f7954002febb8a57242bbb86d967b635", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7ae387993a92bcc379063229b3cce8af7eaf082dd9306598fcd42352994d2de0", + "zh:9e0f365f807b088646db6e4a8d4b188129d9ebdbcf2568c8ab33bddd1b82c867", + "zh:b5263acbd8ae51c9cbffa79743fbcadcb7908057c87eb22fd9048268056efbc4", + "zh:dfcd88ac5f13c0d04e24be00b686d069b4879cc4add1b7b1a8ae545783d97520", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.2" + constraints = ">= 3.2.1" + hashes = [ + "h1:IMVAUHKoydFrlPrl9OzasDnw/8ntZFerCC9iXw1rXQY=", + "zh:3248aae6a2198f3ec8394218d05bd5e42be59f43a3a7c0b71c66ec0df08b69e7", + "zh:32b1aaa1c3013d33c245493f4a65465eab9436b454d250102729321a44c8ab9a", + "zh:38eff7e470acb48f66380a73a5c7cdd76cc9b9c9ba9a7249c7991488abe22fe3", + "zh:4c2f1faee67af104f5f9e711c4574ff4d298afaa8a420680b0cb55d7bbc65606", + "zh:544b33b757c0b954dbb87db83a5ad921edd61f02f1dc86c6186a5ea86465b546", + "zh:696cf785090e1e8cf1587499516b0494f47413b43cb99877ad97f5d0de3dc539", + "zh:6e301f34757b5d265ae44467d95306d61bef5e41930be1365f5a8dcf80f59452", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:913a929070c819e59e94bb37a2a253c228f83921136ff4a7aa1a178c7cce5422", + "zh:aa9015926cd152425dbf86d1abdbc74bfe0e1ba3d26b3db35051d7b9ca9f72ae", + "zh:bb04798b016e1e1d49bcc76d62c53b56c88c63d6f2dfe38821afef17c416a0e1", + "zh:c23084e1b23577de22603cff752e59128d83cfecc2e6819edadd8cf7a10af11e", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.1" + constraints = "~> 3.0, >= 3.4.3" + hashes = [ + "h1:a+Goawwh6Qtg4/bRWzfDtIdrEFfPlnVy0y4LdUQY3nI=", + "zh:2a0ec154e39911f19c8214acd6241e469157489fc56b6c739f45fbed5896a176", + "zh:57f4e553224a5e849c99131f5e5294be3a7adcabe2d867d8a4fef8d0976e0e52", + "zh:58f09948c608e601bd9d0a9e47dcb78e2b2c13b4bda4d8f097d09152ea9e91c5", + "zh:5c2a297146ed6fb3fe934c800e78380f700f49ff24dbb5fb5463134948e3a65f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7ce41e26f0603e31cdac849085fc99e5cd5b3b73414c6c6d955c0ceb249b593f", + "zh:8c9e8d30c4ef08ee8bcc4294dbf3c2115cd7d9049c6ba21422bd3471d92faf8a", + "zh:93e91be717a7ffbd6410120eb925ebb8658cc8f563de35a8b53804d33c51c8b0", + "zh:982542e921970d727ce10ed64795bf36c4dec77a5db0741d4665230d12250a0d", + "zh:b9d1873f14d6033e216510ef541c891f44d249464f13cc07d3f782d09c7d18de", + "zh:cfe27faa0bc9556391c8803ade135a5856c34a3fe85b9ae3bdd515013c0c87c1", + "zh:e4aabf3184bbb556b89e4b195eab1514c86a2914dd01c23ad9813ec17e863a8a", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.11.1" + hashes = [ + "h1:pQGSL9mdgw4qsLndFYsEF93mbsIxyxNoAyIbBqhS3Xo=", + "zh:19a393db736ec4fd024d098d55aefaef07056c37a448ece3b55b3f5f4c2c7e4a", + "zh:227fa1e221de2907f37be78d40c06ca6a6f7b243a1ec33ade014dfaf6d92cd9c", + "zh:29970fecbf4a3ca23bacbb05d6b90cdd33dd379f90059fe39e08289951502d9f", + "zh:65024596f22f10e7dcb5e0e4a75277f275b529daa0bc0daf34ca7901c678ab88", + "zh:694d080cb5e3bf5ef08c7409208d061c135a4f5f4cdc93ea8607860995264b2e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b29d15d13e1b3412e6a4e1627d378dbd102659132f7488f64017dd6b6d5216d3", + "zh:bb79f4cae9f8c17c73998edc54aa16c2130a03227f7f4e71fc6ac87e230575ec", + "zh:ceccf80e95929d97f62dcf1bb3c7c7553d5757b2d9e7d222518722fc934f7ad5", + "zh:f40e638336527490e294d9c938ae55919069e6987e85a80506784ba90348792a", + "zh:f99ef33b1629a3b2278201142a3011a8489e66d92da832a5b99e442204de18fb", + "zh:fded14754ea46fdecc62a52cd970126420d4cd190e598cb61190b4724a727edb", + ] +} + +provider "registry.terraform.io/oracle/oci" { + version = "5.39.0" + constraints = ">= 4.67.3, >= 4.119.0, ~> 5.38" + hashes = [ + "h1:Fz75vMgyrbbX15nCCKBsHaBk6WQSFmj+AGt2feO3i30=", + "zh:16ecadd604105acbbf0c672312cd8bcd767d1d70f4c22d42c87a8b47cb091af7", + "zh:25aa3b4c7393b871964a3191c9b29ad8903c8b14c3992b201112fbd088c3f62a", + "zh:2da84f62599e2ba05cc0c68ba57f70b86c35b5de2486ad687885473921f5cc73", + "zh:33c436714d21402a5284082b264b1f1f97be171b038a1dcc9d502e670b1252a7", + "zh:63ced8cd3826e7b9e72ee3181ec0839a4df766910866e5c4a9dfa3116ffb4581", + "zh:7b069a26ed5996cb352115b024efd4b000206226285449766eb2135f81c7b630", + "zh:8cb0fbb334dab8d4192458dbcfb65c413c987caea1ab26025da88e805657d383", + "zh:941bc8354db5fc99c5029a6c5ac9c0e1f77a97b8e066fff96b3d10a3ee08a5a4", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b290e335dd764a215e6b76ab90043f946c9c031c99f0642f6887b0c21d594410", + "zh:c5ae643fe1a0ecc437c211878ee6c70470ba1ea4cc4d81f0c711fd6163de2ad6", + "zh:dea1baf7d1e452c385ed428bb9409620486160b263a61f0579421ebea4a49059", + "zh:e7ee0a50d6f94e248a2b6b513461690f0cd7d1df87d6894e849a89f3cb13caf3", + "zh:f99c0ce0433f95a57a53f9f4c4e15a07616fcfb7a00d3daf4fc5588c9b4c8d71", + "zh:fae86a1450561e463beeb0edfdc5b0dbfe82e4ed0a6fe246d4b94ef538ebce85", + ] +} diff --git a/deploy/terraform/adb.tf b/deploy/terraform/adb.tf new file mode 100644 index 00000000..866bab4e --- /dev/null +++ b/deploy/terraform/adb.tf @@ -0,0 +1,63 @@ +variable "autonomous_database_db_workload" { + type = string + default = "OLTP" +} + +variable "autonomous_database_db_license" { + type = string + default = "BRING_YOUR_OWN_LICENSE" +} + +variable "autonomous_database_db_whitelisted_ips" { + type = list(string) + default = ["0.0.0.0/0"] # Don't do this in prod +} + +variable "autonomous_database_cpu_core_count" { + type = number + default = 1 +} + +variable "autonomous_database_data_storage_size_in_tbs" { + type = number + default = 1 +} + +resource "random_password" "adb_admin_password" { + length = 16 + special = true + min_numeric = 3 + min_special = 3 + min_lower = 3 + min_upper = 3 + override_special = "()-_[]{}?" +} + +resource "oci_database_autonomous_database" "adb" { + compartment_id = var.compartment_ocid + db_name = "${local.project_name}${local.deploy_id}" + + #Optional + admin_password = random_password.adb_admin_password.result + cpu_core_count = var.autonomous_database_cpu_core_count + data_storage_size_in_tbs = var.autonomous_database_data_storage_size_in_tbs + db_workload = var.autonomous_database_db_workload + display_name = "${local.project_name}${local.deploy_id}" + is_mtls_connection_required = true + whitelisted_ips = var.autonomous_database_db_whitelisted_ips + is_auto_scaling_enabled = true + license_model = var.autonomous_database_db_license +} + +# For mTLS and Wallet connectivity consider the following code + +resource "oci_database_autonomous_database_wallet" "adb_wallet" { + autonomous_database_id = oci_database_autonomous_database.adb.id + password = random_password.adb_admin_password.result + base64_encode_content = "true" +} + +resource "local_file" "adb_wallet_file" { + content_base64 = oci_database_autonomous_database_wallet.adb_wallet.content + filename = "${path.module}/generated/wallet.zip" +} \ No newline at end of file diff --git a/deploy/terraform/data.tf b/deploy/terraform/data.tf new file mode 100644 index 00000000..c65c970b --- /dev/null +++ b/deploy/terraform/data.tf @@ -0,0 +1,31 @@ +data "oci_identity_tenancy" "tenant_details" { + tenancy_id = var.tenancy_ocid + + provider = oci +} + +data "oci_identity_regions" "home" { + filter { + name = "key" + values = [data.oci_identity_tenancy.tenant_details.home_region_key] + } + + provider = oci +} + +data "oci_identity_compartment" "compartment" { + id = var.compartment_ocid +} + +data "oci_identity_availability_domains" "ads" { + compartment_id = var.tenancy_ocid +} + +data "oci_objectstorage_namespace" "objectstorage_namespace" { + compartment_id = var.tenancy_ocid +} + + +data "oci_containerengine_cluster_option" "oke" { + cluster_option_id = "all" +} diff --git a/deploy/terraform/genai.tf b/deploy/terraform/genai.tf new file mode 100644 index 00000000..660e44f6 --- /dev/null +++ b/deploy/terraform/genai.tf @@ -0,0 +1,7 @@ +data "oci_generative_ai_models" "genai_models" { + compartment_id = var.compartment_ocid + + capability = ["TEXT_GENERATION"] + state = "ACTIVE" + vendor = "cohere" +} \ No newline at end of file diff --git a/deploy/terraform/iam.tf b/deploy/terraform/iam.tf new file mode 100644 index 00000000..216b9cbb --- /dev/null +++ b/deploy/terraform/iam.tf @@ -0,0 +1,55 @@ +locals { + oke_policy_name = "${local.project_name}_${local.deploy_id}_oke" + ocir_group_name = "${local.project_name}-${local.deploy_id}-group" +} + +resource "oci_identity_policy" "allow-oke-genai-policy" { + provider = oci.home + compartment_id = var.tenancy_ocid + name = "${local.oke_policy_name}" + description = "Allow OKE workload to manage gen ai service for ${local.project_name} ${local.deploy_id}" + statements = [ + "Allow any-user to manage generative-ai-family in compartment id ${var.compartment_ocid} where all { request.principal.type = 'workload', request.principal.namespace = 'default', request.principal.service_account = 'genai-sa', request.principal.cluster_id = '${module.oke.cluster_id}' }", + "Allow any-user to manage generative-ai-model in compartment id ${var.compartment_ocid} where all { request.principal.type = 'workload', request.principal.namespace = 'default', request.principal.service_account = 'genai-sa', request.principal.cluster_id = '${module.oke.cluster_id}' }" + ] +} + +resource "oci_identity_group" "ocir_group" { + provider = oci.home + compartment_id = var.tenancy_ocid + description = "Group for Oracle Container Image Registry users for ${local.project_name}${local.deploy_id}" + name = local.ocir_group_name +} + +resource "oci_identity_user" "ocir_user" { + provider = oci.home + compartment_id = var.tenancy_ocid + description = "User for pushing images to OCIR" + name = "${local.project_name}-${local.deploy_id}-ocir-user" + + email = "${local.project_name}-${local.deploy_id}-ocir-user@example.com" +} + +resource "oci_identity_user_group_membership" "ocir_user_group_membership" { + provider = oci.home + group_id = oci_identity_group.ocir_group.id + user_id = oci_identity_user.ocir_user.id +} + +resource "oci_identity_auth_token" "ocir_user_auth_token" { + provider = oci.home + description = "User Auth Token to publish images to OCIR" + user_id = oci_identity_user.ocir_user.id +} + +// FIXME allowing manage repos in tenancy, compartment will throw a 403 when pushing image +// FIXME make it compartment specific +resource "oci_identity_policy" "manage_ocir_compartment" { + provider = oci.home + compartment_id = var.tenancy_ocid + name = "manage_ocir_in_compartment_for_${local.project_name}_${random_string.deploy_id.result}" + description = "Allow group to manage ocir at compartment level for ${local.project_name} ${random_string.deploy_id.result}" + statements = [ + "allow group ${local.ocir_group_name} to manage repos in tenancy", + ] +} diff --git a/deploy/terraform/main.tf b/deploy/terraform/main.tf new file mode 100644 index 00000000..591f77e8 --- /dev/null +++ b/deploy/terraform/main.tf @@ -0,0 +1,6 @@ +locals { + project_name = var.project_name + deploy_id = random_string.deploy_id.result + anywhere = "0.0.0.0/0" + tcp = "6" +} \ No newline at end of file diff --git a/deploy/terraform/oke.tf b/deploy/terraform/oke.tf new file mode 100644 index 00000000..96547bec --- /dev/null +++ b/deploy/terraform/oke.tf @@ -0,0 +1,131 @@ +locals { + cluster_k8s_latest_version = reverse(sort(data.oci_containerengine_cluster_option.oke.kubernetes_versions))[0] + lb_subnet_cidr = "10.22.128.0/27" + workers_subnet_cidr = "10.22.144.0/20" + cp_subnet_cidr = "10.22.0.8/29" + vcn_cidr = "10.22.0.0/16" +} + +module "oke" { + source = "oracle-terraform-modules/oke/oci" + version = "5.1.5" + + tenancy_id = var.tenancy_ocid + compartment_id = var.compartment_ocid + region = var.region + home_region = var.tenancy_ocid + + providers = { + oci = oci + oci.home = oci.home + } + + kubernetes_version = local.cluster_k8s_latest_version + cluster_name = "${local.project_name}-${local.deploy_id}-oke" + vcn_name = "${local.project_name}-${local.deploy_id}-vcn" + + assign_public_ip_to_control_plane = true + control_plane_is_public = true + control_plane_allowed_cidrs = ["0.0.0.0/0"] + + create_bastion = false + create_operator = false + + ssh_private_key_path = var.ssh_private_key_path + ssh_public_key = var.ssh_public_key + + # IAM - Policies + create_iam_autoscaler_policy = "never" + create_iam_kms_policy = "never" + create_iam_operator_policy = "never" + create_iam_worker_policy = "never" + # Network module - VCN + subnets = { + bastion = { + create = "never" + } + operator = { + create = "never" + } + cp = { + create = "always", + cidr = local.cp_subnet_cidr + } + pub_lb = { + create = "always", + cidr = local.lb_subnet_cidr + } + workers = { + create = "always", + cidr = local.workers_subnet_cidr + } + int_lb = { + create = "never" + } + pods = { + create = "never" + } + } + nsgs = { + bastion = { create = "never" } + operator = { create = "never" } + cp = { create = "always" } + int_lb = { create = "never" } + pub_lb = { create = "always" } // never + workers = { create = "always" } + pods = { create = "always" } // never + } + + assign_dns = true + create_vcn = true + vcn_cidrs = [local.vcn_cidr] + vcn_dns_label = "oke" + + # lockdown_default_seclist = true + # allow_rules_public_lb = { + # "Allow TCP ingress to public load balancers for HTTPS traffic from anywhere" : { protocol = 6, port = 443, source = "0.0.0.0/0", source_type = "CIDR_BLOCK" }, + # "Allow TCP ingress to public load balancers for HTTP traffic from anywhere" : { protocol = 6, port = 80, source = "0.0.0.0/0", source_type = "CIDR_BLOCK" } + # } + # Network module - security + # allow_node_port_access = true + # allow_worker_internet_access = true + # allow_worker_ssh_access = true + # enable_waf = false + # load_balancers = "public" + # preferred_load_balancer = "public" + # worker_is_public = false + + # Cluster module + create_cluster = true + cni_type = "npn" + cluster_type = "enhanced" + + pods_cidr = "10.244.0.0/16" + services_cidr = "10.96.0.0/16" + use_signed_images = false + use_defined_tags = false + + # Workers + worker_pool_mode = "node-pool" + worker_pool_size = 2 + + worker_pools = { + node_pool_1 = { + shape = "VM.Standard.E4.Flex", + ocpus = 1, + memory = 32, + boot_volume_size = 120, + create = true + } + } +} + +data "oci_containerengine_cluster_kube_config" "kube_config" { + cluster_id = module.oke.cluster_id +} + + +resource "local_file" "kubeconfig" { + content = data.oci_containerengine_cluster_kube_config.kube_config.content + filename = "${path.module}/generated/kubeconfig" +} diff --git a/deploy/terraform/outputs.tf b/deploy/terraform/outputs.tf new file mode 100644 index 00000000..94bbe054 --- /dev/null +++ b/deploy/terraform/outputs.tf @@ -0,0 +1,66 @@ +locals { + model_collection = data.oci_generative_ai_models.genai_models.model_collection[0] + cohere_models = tolist([for each in local.model_collection.items : each + if contains(each.capabilities, "TEXT_GENERATION") + && each.vendor == "cohere"]) +} + +output "project" { + value = "${var.project_name}${random_string.deploy_id.result}" +} + +# output "load_balancer" { +# value = oci_core_public_ip.reserved_ip.ip_address +# } + +# output "web_instances" { +# value = oci_core_instance.web[*].private_ip +# } + +# output "backend_instances" { +# value = oci_core_instance.backend[*].private_ip +# } + +output "cohere_model_id" { + value = local.cohere_models[0].id +} + +# output "ssh_bastion_session_backend" { +# value = oci_bastion_session.backend_session.ssh_metadata.command +# } + +# output "ssh_bastion_session_web" { +# value = oci_bastion_session.web_session.ssh_metadata.command +# } + +output "db_service" { + value = "${local.project_name}${local.deploy_id}" +} + +output "db_password" { + value = random_password.adb_admin_password.result + sensitive = true +} + +output "kubeconfig" { + value = data.oci_containerengine_cluster_kube_config.kube_config.content + sensitive = true +} + +output "oke_cluster_ocid" { + value = module.oke.cluster_id +} + +output "ocir_user" { + value = oci_identity_user.ocir_user.name +} + +output "ocir_user_token" { + sensitive = true + value = oci_identity_auth_token.ocir_user_auth_token.token +} + +output "ocir_user_email" { + sensitive = false + value = oci_identity_user.ocir_user.email +} \ No newline at end of file diff --git a/deploy/terraform/provider.tf b/deploy/terraform/provider.tf new file mode 100644 index 00000000..768e35f8 --- /dev/null +++ b/deploy/terraform/provider.tf @@ -0,0 +1,10 @@ +provider "oci" { + tenancy_ocid = var.tenancy_ocid + region = var.region +} + +provider "oci" { + alias = "home" + tenancy_ocid = var.tenancy_ocid + region = lookup(data.oci_identity_regions.home.regions[0], "name") +} diff --git a/deploy/terraform/random.tf b/deploy/terraform/random.tf new file mode 100644 index 00000000..14a2bb7e --- /dev/null +++ b/deploy/terraform/random.tf @@ -0,0 +1,5 @@ +resource "random_string" "deploy_id" { + length = 2 + special = false + upper = false +} \ No newline at end of file diff --git a/deploy/terraform/storage.tf b/deploy/terraform/storage.tf new file mode 100644 index 00000000..3321d834 --- /dev/null +++ b/deploy/terraform/storage.tf @@ -0,0 +1,22 @@ +resource "oci_objectstorage_bucket" "artifacts_bucket" { + compartment_id = var.compartment_ocid + name = "artifacts_${local.project_name}_${local.deploy_id}" + namespace = data.oci_objectstorage_namespace.objectstorage_namespace.namespace +} + +resource "oci_objectstorage_object" "wallet_object" { + bucket = oci_objectstorage_bucket.artifacts_bucket.name + content = oci_database_autonomous_database_wallet.adb_wallet.content + namespace = data.oci_objectstorage_namespace.objectstorage_namespace.namespace + object = "wallet.zip.b64" +} + + +resource "oci_objectstorage_preauthrequest" "wallet_par" { + namespace = data.oci_objectstorage_namespace.objectstorage_namespace.namespace + bucket = oci_objectstorage_bucket.artifacts_bucket.name + name = "wallet_par" + access_type = "ObjectRead" + object_name = oci_objectstorage_object.wallet_object.object + time_expires = timeadd(timestamp(), "${var.artifacts_par_expiration_in_days * 24}h") +} \ No newline at end of file diff --git a/deploy/terraform/terraform.tfvars.mustache b/deploy/terraform/terraform.tfvars.mustache new file mode 100644 index 00000000..dd0e6bf3 --- /dev/null +++ b/deploy/terraform/terraform.tfvars.mustache @@ -0,0 +1,14 @@ +region = "{{ regionName }}" +tenancy_ocid = "{{ tenancyId }}" +compartment_ocid = "{{ compartmentId }}" +ssh_public_key = "{{{ ssh_public_key }}}" +ssh_private_key_path = "{{{ ssh_private_key_path }}}" +cert_fullchain = "{{{ cert_fullchain }}}" +cert_private_key = "{{{ cert_private_key }}}" +genai_endpoint = "{{{ genai_endpoint }}}" +genai_model_id = "{{ genai_model_id }}" + +# web_artifact_url = "" +# backend_artifact_url = "" +# ansible_web_artifact_url = "" +# ansible_backend_artifact_url = "" \ No newline at end of file diff --git a/deploy/terraform/variables.tf b/deploy/terraform/variables.tf new file mode 100644 index 00000000..08f563bd --- /dev/null +++ b/deploy/terraform/variables.tf @@ -0,0 +1,74 @@ +variable "tenancy_ocid" { + type = string +} + +variable "region" { + type = string + default = "us-chicago-1" +} + +variable "compartment_ocid" { + type = string +} + +variable "cert_fullchain" { + type = string +} + +variable "cert_private_key" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "ssh_public_key" { + type = string +} + +variable "project_name" { + type = string + default = "genai" +} + +# variable "web_artifact_url" { +# type = string +# } + +# variable "backend_artifact_url" { +# type = string +# } + +# variable "ansible_web_artifact_url" { +# type = string +# } + +# variable "ansible_backend_artifact_url" { +# type = string +# } + +# variable "instance_shape" { +# default = "VM.Standard.E4.Flex" +# } + +# variable "web_node_count" { +# default = "1" +# } + +# variable "backend_node_count" { +# default = "1" +# } + +variable "artifacts_par_expiration_in_days" { + type = number + default = 7 +} + +variable "genai_endpoint" { + type = string +} + +variable "genai_model_id" { + type = string +} diff --git a/deploy/terraform/versions.tf b/deploy/terraform/versions.tf new file mode 100644 index 00000000..504319da --- /dev/null +++ b/deploy/terraform/versions.tf @@ -0,0 +1,19 @@ +terraform { + required_providers { + oci = { + source = "oracle/oci" + version = "~> 5.38" + configuration_aliases = [oci.home] + } + local = { + source = "hashicorp/local" + version = "~> 2" + # https://registry.terraform.io/providers/hashicorp/local/ + } + random = { + source = "hashicorp/random" + version = "~> 3" + # https://registry.terraform.io/providers/hashicorp/random/ + } + } +} diff --git a/images/architecture.png b/images/architecture.png new file mode 100644 index 00000000..a7cf5aa4 Binary files /dev/null and b/images/architecture.png differ diff --git a/scripts/clean.mjs b/scripts/clean.mjs new file mode 100644 index 00000000..e5ee17d7 --- /dev/null +++ b/scripts/clean.mjs @@ -0,0 +1,43 @@ +#!/usr/bin/env zx +import Configstore from "configstore"; +import clear from "clear"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +clear(); +console.log( + "Clean up config files, certs, ssh keys and Object Storage bucket..." +); + +const projectName = "genai"; + +const config = new Configstore(projectName, { projectName }); + +const privateKeyPath = config.get("privateKeyPath"); +await $`rm -f ${privateKeyPath}`; +const publicKeyPath = config.get("publicKeyPath"); +await $`rm -f ${publicKeyPath}`; + +const certPath = path.join(__dirname, "..", ".certs"); +await $`rm -rf ${certPath}`; + +await $`rm -f deploy/k8s/backend/application.yaml`; +await $`rm -f deploy/k8s/overlays/prod/kustomization.yaml`; + +const certsK8sPath = "deploy/k8s/ingress/.certs"; +await $`rm -rf ${certsK8sPath}`; +console.log(`Files in ${chalk.green(certsK8sPath)} deleted`); +const walletK8sPath = "deploy/k8s/backend/wallet"; +await $`rm -rf ${walletK8sPath}`; +console.log(`Files in ${chalk.green(walletK8sPath)} deleted`); + +// TODO delete images pushed +// ${regionKey}.ocir.io/${namespace}/${projectName}/web:${webVersion} +// ${regionKey}.ocir.io/${namespace}/${projectName}/backend:${backendBersion} + +await $`rm -rf ./.artifacts`; +await $`rm -rf ./deploy/terraform/generated/wallet`; + +config.clear(); diff --git a/scripts/kustom.mjs b/scripts/kustom.mjs new file mode 100644 index 00000000..5be7cd59 --- /dev/null +++ b/scripts/kustom.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env zx +import Configstore from "configstore"; +import clear from "clear"; +import Mustache from "mustache"; +import { getOutputValues } from "./lib/terraform.mjs"; +import { exitWithError } from "./lib/utils.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +clear(); +console.log("Create kustomization configuration..."); + +const projectName = "genai"; + +const config = new Configstore(projectName, { projectName }); + +const compartmentId = config.get("compartmentId"); +const namespace = config.get("namespace"); +const regionName = config.get("regionName"); +const regionKey = config.get("regionKey"); +const webVersion = config.get("webVersion"); +const backendVersion = config.get("webVersion"); +const certFullchain = config.get("certFullchain"); +const certPrivateKey = config.get("certPrivateKey"); + +const { db_service, db_password, cohere_model_id } = await getOutputValues( + "./deploy/terraform" +); + +await createBackendProperties(); +await createProdKustomization(); +await copyCerts(); +await copyWallet(); +await createRegistrySecret(); + +async function createBackendProperties() { + const backendPropertiesPath = "deploy/k8s/backend/application.yaml"; + + const backendPropertiesTemplate = await fs.readFile( + `${backendPropertiesPath}.mustache`, + "utf-8" + ); + + const backendPropertiesOutput = Mustache.render(backendPropertiesTemplate, { + db_service: db_service, + db_password: db_password, + path_to_wallet: "/wallet", + region_name: regionName, + compartment_ocid: compartmentId, + genai_model_ocid: cohere_model_id, + }); + + await fs.writeFile(backendPropertiesPath, backendPropertiesOutput); + + console.log(`File ${chalk.green(backendPropertiesPath)} created`); +} + +async function createProdKustomization() { + const prodKustomizationPath = "deploy/k8s/overlays/prod/kustomization.yaml"; + + const prodKustomizationTemplate = await fs.readFile( + `${prodKustomizationPath}.mustache`, + "utf-8" + ); + + const prodKustomizationOutput = Mustache.render(prodKustomizationTemplate, { + region_key: regionKey, + tenancy_namespace: namespace, + project_name: projectName, + web_version: webVersion, + backend_version: backendVersion, + }); + + await fs.writeFile(prodKustomizationPath, prodKustomizationOutput); + + console.log(`File ${chalk.green(prodKustomizationPath)} created`); +} + +async function copyCerts() { + const ingressCertsPath = "deploy/k8s/ingress/.certs"; + await $`mkdir -p ${ingressCertsPath}`; + await $`cp ${certFullchain} ${ingressCertsPath}/`; + console.log(`File ${chalk.green(certFullchain)} copied`); + await $`cp ${certPrivateKey} ${ingressCertsPath}/`; + console.log(`File ${chalk.green(certPrivateKey)} copied`); +} + +async function copyWallet() { + const backendWalletPath = "deploy/k8s/backend/wallet"; + await $`mkdir -p ${backendWalletPath}`; + const walletSourcePath = "deploy/terraform/generated/wallet.zip"; + await $`cp ${walletSourcePath} ${backendWalletPath}/`; + console.log(`File ${chalk.green(walletSourcePath)} copied`); +} + +async function createRegistrySecret() { + const user = config.get("ocir_user"); + const email = config.get("ocir_user_email"); + const token = config.get("ocir_user_token"); + try { + const { exitCode, stdout } = + await $`KUBECONFIG="deploy/terraform/generated/kubeconfig" kubectl \ + create secret docker-registry ocir-secret \ + --save-config \ + --dry-run=client \ + --docker-server=${regionKey}.ocir.io \ + --docker-username=${namespace}/${user} \ + --docker-password=${token} \ + --docker-email=${email} \ + -o yaml | \ + KUBECONFIG="deploy/terraform/generated/kubeconfig" kubectl apply -f -`; + if (exitCode !== 0) { + exitWithError("docker-registry ocir-secret secret not created"); + } else { + console.log( + `Secret ${chalk.green( + "ocir-secret" + )} created on Kubernetes cluster: ${stdout}` + ); + } + } catch (error) { + exitWithError(error.stderr); + } +} diff --git a/scripts/lib/README.md b/scripts/lib/README.md new file mode 100644 index 00000000..52f6eb07 --- /dev/null +++ b/scripts/lib/README.md @@ -0,0 +1,286 @@ +# OCI ZX Scripts + +## Usage + +From the root folder of your project, download the scripts folder: + +```sh +wget -cO - \ + https://github.com/vmleon/oci-zx-scripts/archive/refs/heads/main.zip > scripts.zip \ + && unzip scripts.zip -d scripts \ + && rm scripts.zip \ + && mv scripts/oci-zx-scripts-main scripts/lib +``` + +## Example + +Create a `setenv.mjs` script to prepare environment: + +```sh +touch scripts/setenv.mjs +``` + +Use the following example: + +```js +#!/usr/bin/env zx + +import { getNamespace } from "./lib/oci.mjs"; +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +console.log("Check fake dependencies..."); +const dependencies = ["git", "unzip"]; +const namespace = await getNamespace(); +console.log(namespace); +``` + +Run the script: + +```sh +npx zx scripts/setenv.mjs +``` + +## Create new functions + +```javascript +#!/usr/bin/env zx +import { exitWithError } from "./utils.mjs"; + +export async function example() { + try { + const { stdout, exitCode, stderr } = await $`pwd`; + if (exitCode !== 0) { + exitWithError(stderr); + } + return stdout.trim(); + } catch (error) { + exitWithError(error.stderr); + } +} +``` + +## Get Region example + +```javascript +import { getRegions } from "./lib/oci.mjs"; +import { setVariableFromEnvOrPrompt, printRegionNames } from "./lib/utils.mjs"; + +const regions = await getRegions(); +const regionName = await setVariableFromEnvOrPrompt( + "OCI_REGION", + "OCI Region name", + async () => printRegionNames(regions) +); +const { key } = regions.find((r) => r.name === regionName); +const url = `${key}.ocir.io`; +console.log(url); +``` + +## Release Example + +```javascript +#!/usr/bin/env zx +import { + getVersion, + getNamespace, + printRegionNames, + setVariableFromEnvOrPrompt, +} from "./lib/utils.mjs"; +import { buildImage, tagImage, pushImage } from "./lib/container.mjs"; +import { buildWeb } from "./lib/npm.mjs"; +import { + buildJarGradle, + cleanGradle, + getVersionGradle, +} from "./lib/gradle.mjs"; +import { getRegions } from "./lib/oci.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +const { a, _ } = argv; +const [action] = _; + +const project = "project_name"; +const namespace = await getNamespace(); + +const regions = await getRegions(); +const regionName = await setVariableFromEnvOrPrompt( + "OCI_REGION", + "OCI Region name", + () => printRegionNames(regions) +); +const { key } = regions.find((r) => r.name === regionName); +const ocirUrl = `${key}.ocir.io`; + +if (action === "web") { + await releaseNpm("web"); + process.exit(0); +} + +if (action === "backend") { + await releaseGradle("backend"); + process.exit(0); +} + +if (a || action === "all") { + await releaseNpm("web"); + await releaseGradle("backend"); + process.exit(0); +} + +console.log("Usage:"); +console.log("\tnpx zx scripts/release.mjs all"); +console.log("\tnpx zx scripts/release.mjs -a"); +console.log("\tnpx zx scripts/release.mjs web"); +console.log("\tnpx zx scripts/release.mjs backend"); + +async function releaseNpm(service) { + await cd(service); + const currentVersion = await getVersion(); + if (service === "web") { + await buildWeb(); + } + const image_name = `${project}/${service}`; + await buildImage(`localhost/${image_name}`, currentVersion); + const local_image = `localhost/${image_name}:${currentVersion}`; + const remote_image = `${ocirUrl}/${namespace}/${image_name}:${currentVersion}`; + await tagImage(local_image, remote_image); + await pushImage(remote_image); + console.log(`Released: ${chalk.yellow(remote_image)}`); + await cd(".."); +} +async function releaseGradle(service) { + await cd(service); + await cleanGradle(); + await buildJarGradle(); + const currentVersion = await getVersionGradle(); + const image_name = `${project}/${service}`; + await buildImage(`localhost/${image_name}`, currentVersion); + const local_image = `localhost/${image_name}:${currentVersion}`; + const remote_image = `${ocirUrl}/${namespace}/${image_name}:${currentVersion}`; + await tagImage(local_image, remote_image); + await pushImage(remote_image); + console.log(`Released: ${chalk.yellow(remote_image)}`); + await cd(".."); +} +``` + +## Create RSA + +```javascript +#!/usr/bin/env zx + +import { getUserId, uploadApiKeyFile } from "./lib/oci.mjs"; +import { createRSAKeyPair } from "./lib/tls.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +async function createUserDetails() { + console.log("Generate RSA key pair..."); + + const userId = await getUserId(); + + properties = { + ...properties, + userId, + }; + + const rsaPath = "./.rsa"; + const prevKeyExists = await fs.pathExists(path.join(rsaPath, "rsa.pem")); + if (prevKeyExists) { + console.log(`${chalk.yellow("Existing RSA key pair ")} on ${rsaPath}.`); + } else { + await createRSAKeyPair(rsaPath); + const apikeyFingerprint = await uploadApiKeyFile( + userId, + path.join(rsaPath, "rsa_public.pem") + ); + properties = { ...properties, rsaPath, apikeyFingerprint }; + } + console.log(); +} +``` + +## Use .env.json + +```javascript +#!/usr/bin/env zx + +import { + getNamespace, + writeEnvJson, + generateRandomString, + readEnvJson, +} from "./lib/utils.mjs"; +import { getTenancyId } from "./lib/oci.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +let properties = await readEnvJson(); + +const namespace = await getNamespace(); +const tenancyId = await getTenancyId(); +properties = { ...properties, namespace, tenancyId }; +await writeEnvJson(properties); + +const generatedPassword = await generateRandomString(); +properties = { ...properties, generatedPassword }; + +await writeEnvJson(properties); +``` + +```javascript +#!/usr/bin/env zx + +import { readEnvJson } from "./lib/utils.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +const { namespace } = await readEnvJson(); +``` + +```javascript +#!/usr/bin/env zx + +import { setVariableFromEnvOrPrompt } from "./lib/utils.mjs"; +import { searchCompartmentIdByName } from "./lib/oci.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +const compartmentName = await setVariableFromEnvOrPrompt( + "COMPARTMENT_NAME", + "Compartment Name (root)" +); + +const compartmentId = await searchCompartmentIdByName( + compartmentName || "root" +); +``` + +## Read Terraform Output + +```javascript +#!/usr/bin/env zx + +import { getOutputValues } from "./lib/terraform.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +const tfOutput = await getOutputValues("./terraform"); + +console.log(tfOutput); +``` diff --git a/scripts/lib/container.mjs b/scripts/lib/container.mjs new file mode 100644 index 00000000..4d66a547 --- /dev/null +++ b/scripts/lib/container.mjs @@ -0,0 +1,74 @@ +#!/usr/bin/env zx +import { exitWithError } from "./utils.mjs"; + +export async function whichContainerEngine() { + try { + const dockerPath = await which("docker"); + return !dockerPath ? "podman" : "docker"; + } catch (err) { + return "podman"; + } +} + +const ce = await whichContainerEngine(); + +export async function checkPodmanMachineRunning() { + if (ce === "podman") { + const isMachineRunning = ( + await $`podman machine info --format {{.Host.MachineState}}` + ).stdout.trim(); + if (isMachineRunning === "Stopped") { + console.log( + `Run ${chalk.yellow("podman machine start")} before continue` + ); + exitWithError("Podman machine stopped"); + } else { + console.log(`${chalk.green("[ok]")} podman machine running`); + } + } +} + +export async function containerLogin(namespace, user, token, url) { + try { + const { stdout, stderr, exitCode } = + await $`${ce} login -u ${namespace}/${user} -p ${token} ${url}`; + if (exitCode == 0) { + console.log(`${chalk.yellow(url)}: ${chalk.green(stdout.trim())}`); + } else { + console.error(chalk.red(stderr.trim())); + } + } catch (error) { + console.error(chalk.red(error.stderr.trim())); + const yellowUserString = chalk.yellow(user); + exitWithError( + `Review the user ${yellowUserString} and token pair, and try again.` + ); + } +} + +export async function tagImage(local, remote) { + console.log(`${ce} tag ${local} ${remote}`); + try { + await $`${ce} tag ${local} ${remote}`; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function pushImage(remote) { + console.log(`${ce} push ${remote}`); + try { + await $`${ce} push ${remote}`; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function buildImage(name, version) { + console.log(`${ce} build . -t ${name}:${version}`); + try { + await $`${ce} build . -t ${name}:${version}`; + } catch (error) { + exitWithError(error.stderr); + } +} diff --git a/scripts/lib/crypto.mjs b/scripts/lib/crypto.mjs new file mode 100644 index 00000000..c7db7037 --- /dev/null +++ b/scripts/lib/crypto.mjs @@ -0,0 +1,60 @@ +#!/usr/bin/env zx +import { exitWithError } from "./utils.mjs"; + +export async function createSelfSignedCert(outputPath = ".") { + await $`mkdir -p ${outputPath}`; + const keyPath = path.normalize(path.join(outputPath, "tls.key")); + const certPath = path.normalize(path.join(outputPath, "tls.crt")); + try { + await $`openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${keyPath} -out ${certPath} -subj "/CN=nginxsvc/O=nginxsvc"`; + console.log(`Cert Key created: ${chalk.green(keyPath)}`); + console.log(`Cert created: ${chalk.green(certPath)}`); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function generateRandomString(length = 20) { + if (length < 12) { + exitWithError("Password length too short, >= 12 required"); + } + try { + const output = (await $`openssl rand -base64 ${length + 15}`).stdout.trim(); + if (output.length) { + const cleanPassword = output + .replaceAll("/", "") + .replaceAll("\\", "") + .replaceAll("=", ""); + return cleanPassword.slice(0, length); + } else { + exitWithError("random string generation failed"); + } + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function createRSAKeyPair(outputPath = ".") { + await $`mkdir -p ${outputPath}`; + const privateKeyPath = path.normalize(path.join(outputPath, "rsa.pem")); + const publicKeyPath = path.normalize(path.join(outputPath, "rsa_public.pem")); + try { + await $`openssl genrsa -out ${privateKeyPath} 2048`; + console.log(`RSA Private Key written to: ${chalk.yellow(privateKeyPath)}`); + await $`openssl rsa -in ${privateKeyPath} -outform PEM -pubout -out ${publicKeyPath}`; + console.log(`RSA Public Key written to: ${chalk.yellow(publicKeyPath)}`); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function createSSHKeyPair(sshPathParam) { + const defaultSSHPath = path.join(os.homedir(), ".ssh", "id_rsa"); + const sshPath = sshPathParam || defaultSSHPath; + try { + await $`ssh-keygen -t rsa -b 4096 -f ${sshPath} -q -N "" << c.current); + if (currentContext.name === contextName) { + console.log(chalk.green(`Context ${contextName} already in use.`)); + return; + } + console.log(chalk.yellow(`fn use context ${contextName}`)); + try { + const { stdout, exitCode, stderr } = await $`fn use context ${contextName}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log(chalk.green(`Context ${contextName} in use.`)); + return stdout.trim(); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function setFnProfileName(profileName = "DEFAULT") { + console.log(chalk.yellow(`fn update context oracle.profile ${profileName}`)); + try { + const { stdout, exitCode, stderr } = + await $`fn update context oracle.profile ${profileName}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log(chalk.green(`Profile ${profileName} set.`)); + return stdout.trim(); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function setFnCompartment(compartmentId) { + if (!compartmentId) { + exitWithError("Compartment OCID required"); + } + console.log( + chalk.yellow(`fn update context oracle.compartment-id ${compartmentId}`) + ); + try { + const { stdout, exitCode, stderr } = + await $`fn update context oracle.compartment-id ${compartmentId}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log(chalk.green(`Compartment ${compartmentId} set.`)); + return stdout.trim(); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function setFnApiUrl(regionKey) { + if (!regionKey) { + exitWithError("Region key required"); + } + console.log( + chalk.yellow( + `fn update context api-url https://functions.${regionKey}.oci.oraclecloud.com` + ) + ); + try { + const { stdout, exitCode, stderr } = + await $`fn update context api-url https://functions.${regionKey}.oci.oraclecloud.com`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log( + chalk.green( + `API URL https://functions.${regionKey}.oci.oraclecloud.com set.` + ) + ); + return stdout.trim(); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function setFnRegistry(regionKey, namespace, repoNamePrefix) { + if (!regionKey) { + exitWithError("Region key required"); + } + if (!namespace) { + exitWithError("Namespace required"); + } + if (!repoNamePrefix) { + exitWithError("Repo name Prefix required"); + } + console.log( + chalk.yellow( + `fn update context registry ${regionKey}.ocir.io/${namespace}/${repoNamePrefix}` + ) + ); + try { + const { stdout, exitCode, stderr } = + await $`fn update context registry ${regionKey}.ocir.io/${namespace}/${repoNamePrefix}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log( + chalk.green( + `Registry ${regionKey}.ocir.io/${namespace}/${repoNamePrefix} set.` + ) + ); + return stdout.trim(); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function setFnRegistryCompartment(compartmentId) { + if (!compartmentId) { + exitWithError("Compartment OCID required"); + } + console.log( + chalk.yellow( + `fn update context oracle.image-compartment-id ${compartmentId}` + ) + ); + try { + const { stdout, exitCode, stderr } = + await $`fn update context oracle.image-compartment-id ${compartmentId}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log(chalk.green(`Registry compartment ${compartmentId} set.`)); + return stdout.trim(); + } catch (error) { + exitWithError(error.stderr); + } +} diff --git a/scripts/lib/gradle.mjs b/scripts/lib/gradle.mjs new file mode 100644 index 00000000..40ca2843 --- /dev/null +++ b/scripts/lib/gradle.mjs @@ -0,0 +1,70 @@ +#!/usr/bin/env zx +import { exitWithError } from "./utils.mjs"; + +export async function bumpGradle(level = "patch") { + const oldVersion = await getVersionGradle(); + let [major, minor, patch] = oldVersion.split(".").map((n) => parseInt(n)); + let newVersion; + switch (level) { + case "patch": + newVersion = `${major}.${minor}.${patch + 1}`; + break; + case "minor": + newVersion = `${major}.${minor + 1}.${0}`; + break; + case "major": + newVersion = `${major + 1}.${0}.${0}`; + break; + default: + console.log("No version bump"); + break; + } + try { + const { exitCode, stderr } = + await $`sed -I -e '/^version/s/${oldVersion}/${newVersion}/g' build.gradle`; + if (exitCode !== 0) { + exitWithError(stderr); + } + return newVersion; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function getVersionGradle() { + try { + const { stdout, exitCode, stderr } = + await $`grep "version = " build.gradle`; + if (exitCode !== 0) { + exitWithError(stderr); + } + const version = stdout.trim().split("version = ")[1].replaceAll("'", ""); + return version; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function cleanGradle() { + try { + const { stdout, exitCode, stderr } = await $`./gradlew clean`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log(stdout.trim()); + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function buildJarGradle() { + try { + const { stdout, exitCode, stderr } = await $`./gradlew bootJar`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log(stdout.trim()); + } catch (error) { + exitWithError(error.stderr); + } +} diff --git a/scripts/lib/npm.mjs b/scripts/lib/npm.mjs new file mode 100644 index 00000000..c4cde29f --- /dev/null +++ b/scripts/lib/npm.mjs @@ -0,0 +1,46 @@ +#!/usr/bin/env zx +import { exitWithError } from "./utils.mjs"; + +export async function getNpmVersion() { + const { version } = await fs.readJson("./package.json"); + return version; +} + +export async function bump(level = "patch") { + try { + let newVersion; + switch (level) { + case "patch": + newVersion = (await $`npm version patch`).stdout.trim(); + console.log(`New version: ${newVersion}`); + break; + case "minor": + newVersion = (await $`npm version minor`).stdout.trim(); + console.log(`New version: ${newVersion}`); + break; + case "major": + newVersion = (await $`npm version major`).stdout.trim(); + console.log(`New version: ${newVersion}`); + break; + + default: + console.log("No version bump"); + break; + } + return newVersion; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function buildWeb() { + try { + console.log(`Install dependencies`); + await $`npm install`; + console.log(`Build static content`); + const output = (await $`npm run build`).stdout.trim(); + console.log(output); + } catch (error) { + exitWithError(error.stderr); + } +} diff --git a/scripts/lib/oci.mjs b/scripts/lib/oci.mjs new file mode 100644 index 00000000..e3e61cab --- /dev/null +++ b/scripts/lib/oci.mjs @@ -0,0 +1,386 @@ +#!/usr/bin/env zx +import { exitWithError } from "./utils.mjs"; +import { where, max } from "underscore"; + +export async function getRegions() { + try { + const tenancyId = await getTenancyId(); + const output = ( + await $`oci iam region-subscription list --tenancy-id ${tenancyId}` + ).stdout.trim(); + const { data } = JSON.parse(output); + return data + .filter((r) => r.status === "READY") + .map((r) => ({ + key: r["region-key"].toLowerCase(), + name: r["region-name"], + isHomeRegion: r["is-home-region"], + })); + } catch (error) { + exitWithError(`Error: get regions ${error.message}`); + } +} + +export async function getNamespace() { + const output = (await $`oci os ns get`).stdout.trim(); + const { data } = JSON.parse(output); + return data; +} + +export async function listAdbDatabases(compartmentId) { + try { + const { stdout, exitCode, stderr } = + await $`oci db autonomous-database list --all --compartment-id ${compartmentId}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + if (!stdout.length) return []; + return JSON.parse(stdout.trim()).data; + } catch (error) { + exitWithError(`Error: download wallet ${error.stderr}`); + } +} + +export async function downloadAdbWallet(adbId, walletFilePath, walletPassword) { + try { + const { exitCode, stderr } = + await $`oci db autonomous-database generate-wallet \ + --autonomous-database-id ${adbId} \ + --file ${walletFilePath} \ + --password ${walletPassword}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + console.log(`Wallet downloaded on ${chalk.green(walletFilePath)}`); + } catch (error) { + exitWithError(`Error: download wallet ${error.stderr}`); + } +} + +export async function getAvailableShapes(options = {}) { + try { + const output = ( + await $`oci compute shape list --compartment-id ${await getTenancyId()}` + ).stdout.trim(); + const { data } = JSON.parse(output); + const { type = "*", flex, includeOld = false } = options; + return data + .filter((shape) => { + if (shape.shape.includes("Standard1.")) return false; + return true; + }) + .filter((shape) => { + if (type === "*") return true; + if (type === "bm") return shape.shape.startsWith("BM."); + if (type === "vm") return shape.shape.startsWith("VM."); + }) + .filter((shape) => { + if (flex === undefined) return true; + return flex + ? shape.shape.endsWith(".Flex") + : !shape.shape.endsWith(".Flex"); + }) + .sort((s1, s2) => { + return s1.shape.localeCompare(s2.shape); + }); + } catch (error) { + exitWithError(`Error: get available shapes ${error.message}`); + } +} + +export async function getTenancyId() { + const tenancyIdEnv = process.env.OCI_TENANCY; + const tenancyId = tenancyIdEnv + ? tenancyIdEnv + : await question("OCI tenancy: "); + return tenancyId; +} + +export async function searchCompartmentIdByName(compartmentName) { + if (!compartmentName) { + exitWithError("Compartment name required"); + } + if (compartmentName === "root") { + return getTenancyId(); + } + try { + const { stdout, exitCode, stderr } = + await $`oci iam compartment list --compartment-id-in-subtree true --name ${compartmentName} --query "data[].id"`; + if (exitCode !== 0) { + exitWithError(stderr); + } + if (!stdout.length) { + exitWithError("Compartment name not found"); + } + const compartmentId = JSON.parse(stdout.trim())[0]; + return compartmentId; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function uploadApiKeyFile(userId, publicKeyPath) { + if (!userId) { + exitWithError("User ID required"); + } + if (!publicKeyPath) { + exitWithError("Public RSA key required"); + } + const rsaPublicKeyExists = await fs.pathExists(publicKeyPath); + if (!rsaPublicKeyExists) { + exitWithError(`RSA Public key ${publicKeyPath} does not exists`); + } + try { + const { stdout, exitCode, stderr } = + await $`oci iam user api-key upload --user-id ${userId} --key-file ${publicKeyPath}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + if (!stdout.length) { + exitWithError("Compartment name not found"); + } + const { fingerprint } = JSON.parse(stdout.trim()).data; + return fingerprint; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function getUserId() { + const userIdEnv = process.env.OCI_CS_USER_OCID; + if (userIdEnv) { + return userIdEnv; + } + const userEmail = await question("OCI User email: "); + const { stdout, exitCode, stderr } = await $`oci iam user list --all`; + if (exitCode !== 0) { + exitWithError(stderr); + } + if (!stdout.length) { + exitWithError("User name not found"); + } + const data = JSON.parse(stdout.trim()).data; + const userFound = data.find((user) => user.email === userEmail); + if (!userFound) { + exitWithError(`User ${userEmail} not found`); + } + return userFound.id; +} + +export async function createBucket(compartmentId, name) { + if (!compartmentId) { + exitWithError(`Compartment Id required to create bucket`); + } + if (!name) { + exitWithError(`Name required to create bucket`); + } + const namespace = await getNamespace(); + try { + const { stdout, exitCode, stderr } = await $`oci os bucket create \ + --namespace-name ${namespace} \ + --compartment-id ${compartmentId} \ + --name ${name}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + const { fingerprint } = JSON.parse(stdout.trim()).data; + return fingerprint; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function deleteBucket(name) { + if (!name) { + exitWithError(`Name required to create bucket`); + } + const namespace = await getNamespace(); + try { + const { exitCode, stderr } = await $`oci os bucket delete \ + --bucket-name ${name} \ + --namespace-name ${namespace} \ + --empty --force`; + if (exitCode !== 0) { + exitWithError(stderr); + } + return; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function putObject(bucketName, objectName, filePath) { + if (!bucketName) { + exitWithError(`Bucket name required to put an object`); + } + if (!objectName) { + exitWithError(`Object name required to put an object`); + } + if (!filePath) { + exitWithError(`File path required to put an object`); + } + const namespace = await getNamespace(); + try { + const { exitCode, stderr } = await $`oci os object put \ + --force --name ${objectName} \ + -bn ${bucketName} -ns ${namespace} \ + --file ${filePath}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + return; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function createPARObject(bucketName, objectName, expiration) { + if (!bucketName) { + exitWithError(`Bucket name required to create a PAR`); + } + if (!objectName) { + exitWithError(`Object name required to create a PAR`); + } + if (!expiration) { + exitWithError(`RFC 3339 expiration required to create a PAR`); + } + const namespace = await getNamespace(); + + try { + const { stdout, exitCode, stderr } = await $`oci os preauth-request create \ + --bucket-name ${bucketName} \ + --namespace-name ${namespace} \ + --name ${objectName}_par \ + --access-type ObjectRead \ + --time-expires "${expiration}" \ + --object-name ${objectName}`; + if (exitCode !== 0) { + exitWithError(stderr); + } + const fullPath = JSON.parse(stdout.trim()).data["full-path"]; + return fullPath; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function listPARs(bucketName) { + if (!bucketName) { + exitWithError(`Bucket name required to list PARs`); + } + const namespace = await getNamespace(); + try { + const { stdout, exitCode, stderr } = await $`oci os preauth-request list \ + --bucket-name ${bucketName} \ + --namespace-name ${namespace} \ + --all`; + if (exitCode !== 0) { + exitWithError(stderr); + } + if (!stdout.length) return []; + const pars = JSON.parse(stdout.trim()).data; + return pars; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function deletePAR(bucketName, id) { + if (!bucketName) { + exitWithError(`Bucket name required to delete a PAR`); + } + if (!id) { + exitWithError(`PAR id required to delete a PAR`); + } + const namespace = await getNamespace(); + try { + const { exitCode, stderr } = await $`oci os preauth-request delete \ + --bucket-name ${bucketName} \ + --namespace-name ${namespace} \ + --par-id ${id} --force`; + if (exitCode !== 0) { + exitWithError(stderr); + } + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function listBuckets(compartmentId) { + if (!compartmentId) { + exitWithError(`Compartment Id required to create bucket`); + } + const namespace = await getNamespace(); + try { + const { stdout, exitCode, stderr } = await $`oci os bucket list \ + --compartment-id ${compartmentId} \ + --namespace-name ${namespace} \ + --all`; + if (exitCode !== 0) { + exitWithError(stderr); + } + if (!stdout.length) return []; + const compartmentList = JSON.parse(stdout.trim()).data; + return compartmentList; + } catch (error) { + exitWithError(error.stderr); + } +} + +export async function getBucket(compartmentId, name) { + if (!compartmentId) { + exitWithError(`Compartment Id required to create bucket`); + } + if (!name) { + exitWithError(`Name required to create bucket`); + } + const listBucket = await listBuckets(compartmentId); + return listBucket.find((b) => b.name === name); +} + +export async function getLatestGenAIModels( + compartmentId, + regionName, + vendor = "cohere", + capability = "TEXT_GENERATION" +) { + if (!compartmentId) { + exitWithError(`Compartment Id required to get GenAI models`); + } + if (!regionName) { + exitWithError(`Region name required to get GenAI models`); + } + + try { + const { stdout, stderr, exitCode } = + await $`oci generative-ai model-collection list-models \ + --compartment-id ${compartmentId} \ + --region ${regionName}`; + + if (exitCode !== 0) { + exitWithError(stderr); + } + + if (!stdout.length) return {}; + const { data } = JSON.parse(stdout.trim()); + + const activeCohereModels = where(data.items, { + "lifecycle-state": "ACTIVE", + vendor: vendor, + }); + + const filteredByCapatility = activeCohereModels.filter( + ({ capabilities }) => { + if (capabilities.length !== 1) return false; + if (capabilities[0] !== capability) return false; + return true; + } + ); + + const latestVersion = max(filteredByCapatility, (item) => + parseFloat(item.version) + ); + + return latestVersion; + } catch (error) {} +} diff --git a/scripts/lib/terraform.mjs b/scripts/lib/terraform.mjs new file mode 100644 index 00000000..5c7e6e09 --- /dev/null +++ b/scripts/lib/terraform.mjs @@ -0,0 +1,23 @@ +#!/usr/bin/env zx +import { exitWithError } from "./utils.mjs"; + +export async function getOutputValues(terraformPath = "") { + const tfOutputPathExists = await fs.pathExists(terraformPath); + if (!tfOutputPathExists) exitWithError("Terraform path doesn't exist"); + + const { stdout: stdoutPwd } = await $`pwd`; + const currentPath = stdoutPwd.trim(); + await cd(path.normalize(terraformPath)); + + const { stdout } = await $`terraform output -json`; + const terraformOutput = JSON.parse(stdout); + + const values = {}; + for (const [key, content] of Object.entries(terraformOutput)) { + values[key] = content.value; + } + + await cd(currentPath); + + return values; +} diff --git a/scripts/lib/utils.mjs b/scripts/lib/utils.mjs new file mode 100644 index 00000000..5de87aa5 --- /dev/null +++ b/scripts/lib/utils.mjs @@ -0,0 +1,78 @@ +#!/usr/bin/env zx + +export async function readEnvJson() { + const envFilePath = ".env.json"; + const envFileExists = await fs.pathExists(envFilePath); + if (envFileExists) { + return fs.readJson(envFilePath); + } + return writeEnvJson({}); +} + +export async function writeEnvJson(properties) { + const envFilePath = ".env.json"; + await fs.writeJson(envFilePath, properties, { spaces: 2 }); + return properties; +} + +export async function validateBumpLevel(level) { + if (!["major", "minor", "patch"].includes(level)) { + exitWithError("Error: release version must be 'major', 'minor' or 'patch'"); + } + return level; +} + +export async function printRegionNames(regions) { + console.log("printRegionNames"); + const regionsByZone = regions.reduce((acc, cur) => { + const zone = cur.name.split("-")[0]; + if (acc[zone]) { + acc[zone].push(cur.name); + } else { + acc[zone] = [cur.name]; + } + return acc; + }, {}); + Object.keys(regionsByZone).forEach((zone) => + console.log(`\t${chalk.yellow(zone)}: ${regionsByZone[zone].join(", ")}`) + ); +} + +export async function getRegionByKey(code = "fra") { + const output = (await $`oci iam region list`).stdout.trim(); + const { data } = JSON.parse(output); + return data.find((r) => code.toUpperCase() === r.key); +} + +export async function setVariableFromEnvOrPrompt( + envKey, + questionText, + printChoices +) { + const envValue = process.env[envKey]; + if (envValue) { + return envValue; + } else { + if (printChoices) { + printChoices(); + } + const answer = await question(`${questionText}: `); + return answer; + } +} + +export async function exitWithError(errorMessage = "") { + console.error(chalk.red(errorMessage.trim())); + process.exit(1); +} + +export async function checkRequiredProgramsExist(programs) { + try { + for (let program of programs) { + await which(program); + console.log(`${chalk.green("[ok]")} ${program}`); + } + } catch (error) { + exitWithError(`Error: Required command ${error.message}`); + } +} diff --git a/scripts/package-lock.json b/scripts/package-lock.json new file mode 100644 index 00000000..a39ee580 --- /dev/null +++ b/scripts/package-lock.json @@ -0,0 +1,904 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scripts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "clear": "^0.1.0", + "configstore": "^6.0.0", + "inquirer": "^9.2.19", + "moment": "^2.30.1", + "mustache": "^4.2.0", + "underscore": "^1.13.6" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.1.tgz", + "integrity": "sha512-mtup3wVKia3ZwULPHcbs4Mor8Voi+iIXEWD7wCNbIO6lYR62oPCTQyrddi5OMYVXHzeCSoneZwJuS8sBvlEwDw==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==", + "engines": { + "node": "*" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inquirer": { + "version": "9.2.19", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.19.tgz", + "integrity": "sha512-WpxOT71HGsFya6/mj5PUue0sWwbpbiPfAR+332zLj/siB0QA1PZM8v3GepegFV1Op189UxHUCF6y8AySdtOMVA==", + "dependencies": { + "@inquirer/figures": "^1.0.1", + "@ljharb/through": "^2.3.13", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 00000000..4658e1a2 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,22 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "clear": "^0.1.0", + "configstore": "^6.0.0", + "inquirer": "^9.2.19", + "moment": "^2.30.1", + "mustache": "^4.2.0", + "underscore": "^1.13.6" + } +} diff --git a/scripts/release.mjs b/scripts/release.mjs new file mode 100644 index 00000000..5270e330 --- /dev/null +++ b/scripts/release.mjs @@ -0,0 +1,85 @@ +#!/usr/bin/env zx +import Configstore from "configstore"; +import clear from "clear"; +import { + buildJarGradle, + cleanGradle, + getVersionGradle, +} from "./lib/gradle.mjs"; +import { getNpmVersion } from "./lib/npm.mjs"; +import { + buildImage, + tagImage, + pushImage, + checkPodmanMachineRunning, + containerLogin, +} from "./lib/container.mjs"; +import { getOutputValues } from "./lib/terraform.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +clear(); +console.log("Release latest backend and web version..."); + +const projectName = "genai"; + +const config = new Configstore(projectName, { projectName }); + +const namespace = config.get("namespace"); +const regionKey = config.get("regionKey"); + +const pwdOutput = (await $`pwd`).stdout.trim(); +await cd(`${pwdOutput}/web`); +const webVersion = await getNpmVersion(); +config.set("webVersion", webVersion); +await cd(`${pwdOutput}/backend`); +const backendVersion = await getVersionGradle(); +config.set("backendVersion", backendVersion); +await cd(pwdOutput); + +await checkPodmanMachineRunning(); + +const ocirUrl = `${regionKey}.ocir.io`; + +// FIXME use OCI Vault for the token +const { ocir_user, ocir_user_token, ocir_user_email } = await getOutputValues( + "./deploy/terraform" +); +config.set("ocir_user", ocir_user); +config.set("ocir_user_email", ocir_user_email); +config.set("ocir_user_token", ocir_user_token); + +await containerLogin(namespace, ocir_user, ocir_user_token, ocirUrl); +await releaseWeb(); +await releaseBackend(); + +async function releaseWeb() { + const service = "web"; + await cd(service); + const imageName = `${projectName}/${service}`; + await buildImage(`localhost/${imageName}`, webVersion); + const localImage = `localhost/${imageName}:${webVersion}`; + const remoteImage = `${ocirUrl}/${namespace}/${imageName}:${webVersion}`; + await tagImage(localImage, remoteImage); + await pushImage(remoteImage); + console.log(`${chalk.green(remoteImage)} pushed`); + await cd(".."); +} + +async function releaseBackend() { + const service = "backend"; + await cd(service); + await cleanGradle(); + await buildJarGradle(); + const currentVersion = await getVersionGradle(); + const imageName = `${projectName}/${service}`; + await buildImage(`localhost/${imageName}`, currentVersion); + const localImage = `localhost/${imageName}:${currentVersion}`; + const remoteImage = `${ocirUrl}/${namespace}/${imageName}:${currentVersion}`; + await tagImage(localImage, remoteImage); + await pushImage(remoteImage); + console.log(`${chalk.green(remoteImage)} pushed`); + await cd(".."); +} diff --git a/scripts/setenv.mjs b/scripts/setenv.mjs new file mode 100644 index 00000000..cf6cee74 --- /dev/null +++ b/scripts/setenv.mjs @@ -0,0 +1,132 @@ +#!/usr/bin/env zx +import moment from "moment"; +import Configstore from "configstore"; +import inquirer from "inquirer"; +import clear from "clear"; +import { + getLatestGenAIModels, + getNamespace, + getRegions, + getTenancyId, + searchCompartmentIdByName, +} from "./lib/oci.mjs"; +import { createSSHKeyPair, createSelfSignedCert } from "./lib/crypto.mjs"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +clear(); +console.log("Set up environment..."); + +const projectName = "genai"; + +const config = new Configstore(projectName, { projectName }); + +await setTenancyEnv(); +await setNamespaceEnv(); +await setRegionEnv(); +await setCompartmentEnv(); +await createSSHKeys(projectName); +await createCerts(); +await setLatestGenAIModel(); + +console.log(`\nConfiguration file saved in: ${chalk.green(config.path)}`); + +async function setTenancyEnv() { + const tenancyId = await getTenancyId(); + config.set("tenancyId", tenancyId); +} + +async function setNamespaceEnv() { + const namespace = await getNamespace(); + config.set("namespace", namespace); +} + +async function setRegionEnv() { + const listSubscribedRegions = (await getRegions()).sort( + (r1, r2) => r1.isHomeRegion > r2.isHomeRegion + ); + + const listWithGenAISupportingRegions = listSubscribedRegions.filter( + (r) => r.key === "ord" + ); + + await inquirer + .prompt([ + { + type: "list", + name: "region", + message: "Select the region", + choices: listWithGenAISupportingRegions.map((r) => r.name), + filter(val) { + return listWithGenAISupportingRegions.find((r) => r.name === val); + }, + }, + ]) + .then((answers) => { + config.set("regionName", answers.region.name); + config.set("regionKey", answers.region.key); + }); +} + +async function setCompartmentEnv() { + await inquirer + .prompt([ + { + type: "input", + name: "compartmentName", + message: "Compartment Name", + default() { + return "root"; + }, + }, + ]) + .then(async (answers) => { + const compartmentName = answers.compartmentName; + const compartmentId = await searchCompartmentIdByName( + compartmentName || "root" + ); + config.set("compartmentName", compartmentName); + config.set("compartmentId", compartmentId); + }); +} + +async function createSSHKeys(name) { + const sshPathParam = path.join(os.homedir(), ".ssh", name); + const publicKeyContent = await createSSHKeyPair(sshPathParam); + config.set("privateKeyPath", sshPathParam); + config.set("publicKeyContent", publicKeyContent); + config.set("publicKeyPath", `${sshPathParam}.pub`); + console.log(`SSH key pair created: ${chalk.green(sshPathParam)}`); +} + +async function createCerts() { + const certPath = path.join(__dirname, "..", ".certs"); + await $`mkdir -p ${certPath}`; + await createSelfSignedCert(certPath); + config.set("certFullchain", path.join(certPath, "tls.crt")); + config.set("certPrivateKey", path.join(certPath, "tls.key")); +} + +async function setLatestGenAIModel() { + const latestVersionModel = await getLatestGenAIModels( + config.get("compartmentId"), + config.get("regionName"), + "cohere", + "TEXT_GENERATION" + ); + + const { id, vendor: vendorName, version, capabilities } = latestVersionModel; + const displayName = latestVersionModel["display-name"]; + const timeCreated = moment(latestVersionModel["time-created"]).fromNow(); + console.log( + `Using GenAI Model ${chalk.green(vendorName)}:${chalk.green( + version + )} (${chalk.green(displayName)}) with ${capabilities.join( + "," + )} created ${timeCreated}` + ); + + config.set("genAiModel", id); +} diff --git a/scripts/tfvars.mjs b/scripts/tfvars.mjs new file mode 100644 index 00000000..a0828473 --- /dev/null +++ b/scripts/tfvars.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env zx +import Mustache from "mustache"; +import Configstore from "configstore"; +import clear from "clear"; + +const shell = process.env.SHELL | "/bin/zsh"; +$.shell = shell; +$.verbose = false; + +clear(); +console.log("Create terraform.tfvars..."); + +const projectName = "genai"; + +const config = new Configstore(projectName, { projectName }); + +await generateTFVars(); + +async function generateTFVars() { + const compartmentId = config.get("compartmentId"); + const compartmentName = config.get("compartmentName"); + const regionName = config.get("regionName"); + const tenancyId = config.get("tenancyId"); + const genAiModel = config.get("genAiModel"); + const publicKeyContent = config.get("publicKeyContent"); + const sshPrivateKeyPath = config.get("privateKeyPath"); + const certFullchain = config.get("certFullchain"); + const certPrivateKey = config.get("certPrivateKey"); + // const backend = config.get("backend"); + // const backendAnsible = config.get("backendAnsible"); + // const web = config.get("web"); + // const webAnsible = config.get("webAnsible"); + + // const webArtifactUrl = web.fullPath; + // const backendArtifactUrl = backend.fullPath; + // const ansibleWebArtifactUrl = webAnsible.fullPath; + // const ansibleBackendArtifactUrl = backendAnsible.fullPath; + + const genaiEndpoint = `https://inference.generativeai.${regionName}.oci.oraclecloud.com`; + + const tfVarsPath = "deploy/terraform/terraform.tfvars"; + + const tfvarsTemplate = await fs.readFile(`${tfVarsPath}.mustache`, "utf-8"); + + const output = Mustache.render(tfvarsTemplate, { + tenancyId, + regionName, + compartmentId, + ssh_public_key: publicKeyContent, + ssh_private_key_path: sshPrivateKeyPath, + cert_fullchain: certFullchain, + cert_private_key: certPrivateKey, + // web_artifact_url: webArtifactUrl, + // backend_artifact_url: backendArtifactUrl, + // ansible_web_artifact_url: ansibleWebArtifactUrl, + // ansible_backend_artifact_url: ansibleBackendArtifactUrl, + genai_endpoint: genaiEndpoint, + genai_model_id: genAiModel, + }); + + console.log( + `Terraform will deploy resources in ${chalk.green( + regionName + )} in compartment ${ + compartmentName ? chalk.green(compartmentName) : chalk.green("root") + }` + ); + + await fs.writeFile(tfVarsPath, output); + + console.log(`File ${chalk.green(tfVarsPath)} created`); + + console.log(`1. ${chalk.yellow("cd deploy/terraform/")}`); + console.log(`2. ${chalk.yellow("terraform init")}`); + console.log(`3. ${chalk.yellow("terraform apply -auto-approve")}`); +} diff --git a/service/python/requirements.txt b/service/python/requirements.txt index 00ae6d4c..89c1471f 100644 --- a/service/python/requirements.txt +++ b/service/python/requirements.txt @@ -4,13 +4,14 @@ cached-property==1.5.2 certifi==2023.11.17 cffi==1.16.0 circuitbreaker==1.4.0 -cryptography==42.0.4 -oci==2.119.1 +cryptography==42.0.6 +oci==2.126.2 pycparser==2.21 -pyOpenSSL==23.3.0 +pyOpenSSL==24.1.0 python-dateutil==2.8.2 python-pkcs11==0.7.0 pytz==2023.3.post1 six==1.16.0 throttler==1.2.2 websockets==12.0 +pypdf==4.2.0 \ No newline at end of file diff --git a/service/python/server.py b/service/python/server.py index 03d1b9d9..242994a3 100644 --- a/service/python/server.py +++ b/service/python/server.py @@ -3,12 +3,19 @@ import json import oci from throttler import throttle +from pypdf import PdfReader +from io import BytesIO +from typing import Any, Dict, List +import re +from types import SimpleNamespace -#TODO: Update this section with your tenancy details -compartment_id = "ocid1.compartment.oc1.." +# TODO: Please update config profile name and use the compartmentId that has policies grant permissions for using Generative AI Service +compartment_id = "" CONFIG_PROFILE = "DEFAULT" -config = oci.config.from_file("~/.oci/config", CONFIG_PROFILE) -endpoint = "https://inference.generativeai..oci.oraclecloud.com" +config = oci.config.from_file('~/.oci/config', CONFIG_PROFILE) + +# Service endpoint +endpoint = "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com" generative_ai_inference_client = ( oci.generative_ai_inference.GenerativeAiInferenceClient( config=config, @@ -21,59 +28,107 @@ @throttle(rate_limit=15, period=65.0) async def generate_ai_response(prompts): prompt = "" - cohere_generate_text_request = ( + llm_inference_request = ( oci.generative_ai_inference.models.CohereLlmInferenceRequest() ) - cohere_generate_text_request.prompt = prompts - cohere_generate_text_request.is_stream = ( - False # SDK doesn't support streaming responses, feature is under development - ) - cohere_generate_text_request.max_tokens = 1000 - cohere_generate_text_request.temperature = 0.75 - cohere_generate_text_request.top_p = 0.7 - cohere_generate_text_request.frequency_penalty = 1.0 + llm_inference_request.prompt = prompts + llm_inference_request.max_tokens = 1000 + llm_inference_request.temperature = 0.75 + llm_inference_request.top_p = 0.7 + llm_inference_request.frequency_penalty = 1.0 generate_text_detail = oci.generative_ai_inference.models.GenerateTextDetails() - generate_text_detail.serving_mode = ( - oci.generative_ai_inference.models.OnDemandServingMode( - model_id="cohere.command" - ) - ) + generate_text_detail.serving_mode = oci.generative_ai_inference.models.DedicatedServingMode(endpoint_id="ocid1.generativeaiendpoint.oc1.us-chicago-1.amaaaaaaeras5xiavrsefrftfupp42lnniddgjnxuwbv5jypl64i7ktan65a") + generate_text_detail.compartment_id = compartment_id - generate_text_detail.inference_request = cohere_generate_text_request + generate_text_detail.inference_request = llm_inference_request if "" in compartment_id: print("ERROR:Please update your compartment id in target python file") quit() - generate_text_response = generative_ai_inference_client.generate_text( - generate_text_detail - ) - + generate_text_response = generative_ai_inference_client.generate_text(generate_text_detail) # Print result print("**************************Generate Texts Result**************************") print(vars(generate_text_response)) return generate_text_response +@throttle(rate_limit=15, period=65.0) +async def generate_ai_summary(summary_txt, prompt): + # You can also load the summary text from a file, or as a parameter in main + #with open('files/summarize_data.txt', 'r') as file: + # text_to_summarize = file.read() + + summarize_text_detail = oci.generative_ai_inference.models.SummarizeTextDetails() + summarize_text_detail.serving_mode = oci.generative_ai_inference.models.OnDemandServingMode(model_id="cohere.command") + summarize_text_detail.compartment_id = compartment_id + #summarize_text_detail.input = text_to_summarize + summarize_text_detail.input = summary_txt + summarize_text_detail.additional_command = prompt + summarize_text_detail.extractiveness = "AUTO" # HIGH, LOW + summarize_text_detail.format = "AUTO" # brackets, paragraph + summarize_text_detail.length = "LONG" # high, AUTO + summarize_text_detail.temperature = .25 # [0,1] + + if "" in compartment_id: + print("ERROR:Please update your compartment id in target python file") + quit() + + summarize_text_response = generative_ai_inference_client.summarize_text(summarize_text_detail) + + # Print result + #print("**************************Summarize Texts Result**************************") + #print(summarize_text_response.data) + + return summarize_text_response.data + +async def parse_pdf(file: BytesIO) -> List[str]: + pdf = PdfReader(file) + output = [] + for page in pdf.pages: + text = page.extract_text() + # Merge hyphenated words + text = re.sub(r"(\w+)-\n(\w+)", r"\1\2", text) + # Fix newlines in the middle of sentences + text = re.sub(r"(? + + + + + + OCI GenAI PoC + + +
    + + + diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 00000000..37750a50 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,4694 @@ +{ + "name": "web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", + "@fontsource/roboto": "^5.0.8", + "@mui/icons-material": "^5.15.4", + "@mui/material": "^5.15.4", + "@stomp/stompjs": "^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "uuid": "^9.0.1", + "ws": "^8.16.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.55.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "vite": "^5.0.8" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz", + "integrity": "sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", + "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz", + "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", + "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", + "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", + "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", + "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", + "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", + "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", + "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", + "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", + "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", + "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", + "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", + "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", + "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", + "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", + "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", + "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", + "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", + "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", + "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", + "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", + "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", + "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", + "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz", + "integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==", + "dependencies": { + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz", + "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", + "dependencies": { + "@floating-ui/core": "^1.5.3", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.5.tgz", + "integrity": "sha512-UsBK30Bg+s6+nsgblXtZmwHhgS2vmbuQK22qgt2pTQM6M3X6H1+cQcLXqgRY3ihVLcZJE6IvqDQozhsnIVqK/Q==", + "dependencies": { + "@floating-ui/dom": "^1.5.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, + "node_modules/@fontsource/roboto": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz", + "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", + "integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.31", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.31.tgz", + "integrity": "sha512-+uNbP3OHJuZVI00WyMg7xfLZotaEY7LgvYXDfONVJbrS+K9wyjCIPNfjy8r9XJn4fbHo/5ibiZqjWnU9LMNv+A==", + "dependencies": { + "@babel/runtime": "^7.23.7", + "@floating-ui/react-dom": "^2.0.5", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.4", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.4.tgz", + "integrity": "sha512-0OZN9O6hAtBpx70mMNFOPaAIol/ytwZYPY+z7Rf9dK3+1Xlzwvj5/IeShJKvtp76S1qJyhPuvZg0+BGqQaUnUw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.4.tgz", + "integrity": "sha512-q/Yk7aokN8qGMpR7bwoDpBSeaNe6Bv7vaY9yHYodP37c64TM6ime05ueb/wgksOVszrKkNXC67E/XYbRWOoUFA==", + "dependencies": { + "@babel/runtime": "^7.23.7" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.4.tgz", + "integrity": "sha512-T/LGRAC+M0c+D3+y67eHwIN5bSje0TxbcJCWR0esNvU11T0QwrX3jedXItPNBwMupF2F5VWCDHBVLlFnN3+ABA==", + "dependencies": { + "@babel/runtime": "^7.23.7", + "@mui/base": "5.0.0-beta.31", + "@mui/core-downloads-tracker": "^5.15.4", + "@mui/system": "^5.15.4", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.4", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/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/@mui/private-theming": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.4.tgz", + "integrity": "sha512-9N5myIMEEQTM5WYWPGvvYADzjFo12LgJ7S+2iTZkBNOcJpUxQYM1tvYjkHCDV+t1ocMOEgjR2EfJ9Dus30dBlg==", + "dependencies": { + "@babel/runtime": "^7.23.7", + "@mui/utils": "^5.15.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.4.tgz", + "integrity": "sha512-vtrZUXG5XI8CNiNLcxjIirW4dEbOloR+ikfm6ePBo7jXpJdpXjVzBWetrfE+5eI0cHkKWlTptnJ2voKV8pBRfw==", + "dependencies": { + "@babel/runtime": "^7.23.7", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.4.tgz", + "integrity": "sha512-KCwkHajGBXPs2TK1HJjIyab4NDk0cZoBDYN/TTlXVo1qBAmCjY0vjqrlsjeoG+wrwwcezXMLs/e6OGP66fPCog==", + "dependencies": { + "@babel/runtime": "^7.23.7", + "@mui/private-theming": "^5.15.4", + "@mui/styled-engine": "^5.15.4", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.4", + "clsx": "^2.1.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", + "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.4.tgz", + "integrity": "sha512-E2wLQGBcs3VR52CpMRjk46cGscC4cbf3Q2uyHNaAeL36yTTm+aVNbtsTCazXtjOP4BDd8lu6VtlTpVC8Rtl4mg==", + "dependencies": { + "@babel/runtime": "^7.23.7", + "@types/prop-types": "^15.7.11", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz", + "integrity": "sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz", + "integrity": "sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz", + "integrity": "sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz", + "integrity": "sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz", + "integrity": "sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz", + "integrity": "sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz", + "integrity": "sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz", + "integrity": "sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", + "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz", + "integrity": "sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz", + "integrity": "sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz", + "integrity": "sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz", + "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@stomp/stompjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz", + "integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw==" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "node_modules/@types/react": { + "version": "18.2.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", + "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", + "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001576", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", + "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.630", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.630.tgz", + "integrity": "sha512-osHqhtjojpCsACVnuD11xO5g9xaCyw7Qqn/C2KParkMv42i8jrJJgx3g7mkHfpxwhy9MnOJr8+pKOdZ7qzgizg==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", + "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", + "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.11", + "@esbuild/android-arm": "0.19.11", + "@esbuild/android-arm64": "0.19.11", + "@esbuild/android-x64": "0.19.11", + "@esbuild/darwin-arm64": "0.19.11", + "@esbuild/darwin-x64": "0.19.11", + "@esbuild/freebsd-arm64": "0.19.11", + "@esbuild/freebsd-x64": "0.19.11", + "@esbuild/linux-arm": "0.19.11", + "@esbuild/linux-arm64": "0.19.11", + "@esbuild/linux-ia32": "0.19.11", + "@esbuild/linux-loong64": "0.19.11", + "@esbuild/linux-mips64el": "0.19.11", + "@esbuild/linux-ppc64": "0.19.11", + "@esbuild/linux-riscv64": "0.19.11", + "@esbuild/linux-s390x": "0.19.11", + "@esbuild/linux-x64": "0.19.11", + "@esbuild/netbsd-x64": "0.19.11", + "@esbuild/openbsd-x64": "0.19.11", + "@esbuild/sunos-x64": "0.19.11", + "@esbuild/win32-arm64": "0.19.11", + "@esbuild/win32-ia32": "0.19.11", + "@esbuild/win32-x64": "0.19.11" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", + "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "dev": true, + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "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/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "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==" + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.5.tgz", + "integrity": "sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.5", + "@rollup/rollup-android-arm64": "4.9.5", + "@rollup/rollup-darwin-arm64": "4.9.5", + "@rollup/rollup-darwin-x64": "4.9.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.5", + "@rollup/rollup-linux-arm64-gnu": "4.9.5", + "@rollup/rollup-linux-arm64-musl": "4.9.5", + "@rollup/rollup-linux-riscv64-gnu": "4.9.5", + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@rollup/rollup-linux-x64-musl": "4.9.5", + "@rollup/rollup-win32-arm64-msvc": "4.9.5", + "@rollup/rollup-win32-ia32-msvc": "4.9.5", + "@rollup/rollup-win32-x64-msvc": "4.9.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", + "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz", + "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 00000000..9446a9c1 --- /dev/null +++ b/web/package.json @@ -0,0 +1,34 @@ +{ + "name": "web", + "private": true, + "version": "0.0.3", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", + "@fontsource/roboto": "^5.0.8", + "@mui/icons-material": "^5.15.4", + "@mui/material": "^5.15.4", + "@stomp/stompjs": "^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "uuid": "^9.0.1", + "ws": "^8.16.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.55.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "vite": "^5.0.8" + } +} \ No newline at end of file diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 00000000..18535fe9 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/src/App.jsx b/web/src/App.jsx new file mode 100644 index 00000000..68f4add4 --- /dev/null +++ b/web/src/App.jsx @@ -0,0 +1,29 @@ +import { Container, Typography } from "@mui/material"; +import { StompProvider } from "./stompHook"; +import Chat from "./Chat"; + +const protocol = window.location.protocol === "http:" ? "ws://" : "wss://"; +const hostname = + window.location.hostname === "localhost" + ? "localhost:8080" + : window.location.hostname; +const brokerURL = `${protocol}${hostname}/websocket`; + +function App() { + return ( + + + + OCI GenAI PoC + + + + + ); +} + +export default App; diff --git a/web/src/Chat.jsx b/web/src/Chat.jsx new file mode 100644 index 00000000..f509053d --- /dev/null +++ b/web/src/Chat.jsx @@ -0,0 +1,141 @@ +import { useState, useEffect } from "react"; +import { + FormControl, + InputLabel, + MenuItem, + Select, + Box, + CircularProgress, + Snackbar, + Divider, +} from "@mui/material"; +import PromptInput from "./PromptInput"; +import Conversation from "./Conversation"; +import { useStomp } from "./stompHook"; +import { v4 as uuidv4 } from "uuid"; + +const conversationId = uuidv4(); + +function Chat() { + const [conversation, setConversation] = useState([]); + const [promptValue, setPromptValue] = useState(""); + const [waiting, setWaiting] = useState(false); + const [showError, setShowError] = useState(false); + const [errorMessage, setErrorMessage] = useState(); + const [modelId, setModelId] = useState(); + const [models, setModels] = useState(); + const [updateModels, setUpdateModels] = useState(true); + const { subscribe, unsubscribe, send, isConnected } = useStomp(); + + useEffect(() => { + const fecthModels = async () => { + try { + const response = await fetch("/api/genai/models"); + const data = await response.json(); + setModels( + data.filter( + ({ capabilities }) => + capabilities.length === 1 && + capabilities.includes("TEXT_GENERATION") + ) + ); + } catch (error) { + setErrorMessage("Error fetching Generative AI Models from Backend"); + } + }; + + if (updateModels) { + setUpdateModels(false); + fecthModels(); + } + }, [updateModels]); + + useEffect(() => { + let timeoutId; + if (waiting) { + timeoutId = setTimeout(() => { + setWaiting(false); + setShowError(true); + setErrorMessage("Request timeout"); + }, 30000); + } else { + } + return () => (timeoutId ? clearTimeout(timeoutId) : null); + }, [waiting]); + + useEffect(() => { + if (isConnected) { + subscribe("/user/queue/answer", (message) => { + setWaiting(false); + if (message.errorMessage.length > 0) { + setErrorMessage(message.errorMessage); + setShowError(true); + } else { + setConversation((c) => [ + ...c, + { + id: c.length + 1, + user: "ai", + content: message.content, + }, + ]); + } + }); + } + + return () => { + unsubscribe("/user/queue/answer"); + }; + }, [isConnected]); + + useEffect(() => { + if (isConnected && promptValue.length) { + send("/genai/prompt", { conversationId, content: promptValue, modelId }); + setWaiting(true); + setPromptValue(""); + } + return () => {}; + }, [promptValue]); + + return ( + + + Model + + + + {conversation} + {waiting && } + + { + setErrorMessage(); + setShowError(false); + }} + message={errorMessage} + /> + + ); +} + +export default Chat; diff --git a/web/src/Conversation.jsx b/web/src/Conversation.jsx new file mode 100644 index 00000000..a8ae6a27 --- /dev/null +++ b/web/src/Conversation.jsx @@ -0,0 +1,39 @@ +import { Avatar, Box, Paper, Stack, Typography } from "@mui/material"; +import { deepOrange, deepPurple } from "@mui/material/colors"; + +function Conversation({ children: conversation }) { + if (!conversation.length) return; + return ( + + + {conversation.map(({ id, user, content }) => { + return ( + + {user === "ai" && } + {content} + {user !== "ai" && } + + ); + })} + + + ); +} + +function AIAvatar() { + return AI; +} + +function MeAvatar() { + return Me; +} + +export default Conversation; diff --git a/web/src/PromptInput.jsx b/web/src/PromptInput.jsx new file mode 100644 index 00000000..8976b66f --- /dev/null +++ b/web/src/PromptInput.jsx @@ -0,0 +1,43 @@ +import { Button, Stack, TextField } from "@mui/material"; +import { useState, useRef } from "react"; + +function PromptInput({ setConversation, setPromptValue, disabled }) { + const [inputValue, setInputValue] = useState(""); + const textRef = useRef(); + return ( + + setInputValue(e.target.value)} + /> + + + ); +} + +export default PromptInput; diff --git a/web/src/main.jsx b/web/src/main.jsx new file mode 100644 index 00000000..2278a2d2 --- /dev/null +++ b/web/src/main.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.jsx"; +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); diff --git a/web/src/stompHook/Provider.jsx b/web/src/stompHook/Provider.jsx new file mode 100644 index 00000000..4fc59e5d --- /dev/null +++ b/web/src/stompHook/Provider.jsx @@ -0,0 +1,36 @@ +import React, { createContext, useEffect, useState } from "react"; +import { Client } from "@stomp/stompjs"; + +const defaultValue = { + isConnected: false, + stompClient: null, + subscriptions: {}, + setSubscriptions: () => {}, +}; + +export const StompContext = createContext(defaultValue); + +export const StompProvider = ({ children, config, onConnected }) => { + const [stompClient, setStompClient] = useState(new Client(config)); + const [subscriptions, setSubscriptions] = useState({}); + + useEffect(() => { + stompClient?.activate(); + onConnected?.(stompClient); + return () => { + stompClient?.deactivate(); + }; + }, [stompClient]); + + return ( + + {children} + + ); +}; diff --git a/web/src/stompHook/hook.js b/web/src/stompHook/hook.js new file mode 100644 index 00000000..8afc1f11 --- /dev/null +++ b/web/src/stompHook/hook.js @@ -0,0 +1,51 @@ +import { useContext } from "react"; +import { StompContext } from "./Provider"; + +export const useStomp = () => { + const value = useContext(StompContext); + const { stompClient, subscriptions, setSubscriptions } = value; + + const send = (path, body, headers) => { + stompClient?.publish({ + destination: path, + headers, + body: JSON.stringify(body), + }); + }; + + const subscribe = (path, callback) => { + if (!stompClient) return; + if (subscriptions[path]) return; + + const subscription = stompClient.subscribe(path, (message) => { + const body = JSON.parse(message.body); + callback(body); + }); + setSubscriptions((prev) => { + return { ...prev, [path]: subscription }; + }); + }; + + const unsubscribe = (path) => { + const copy = { ...subscriptions }; + if (!copy[path]) return subscriptions; + copy[path].unsubscribe(); + delete copy[path]; + setSubscriptions((prev) => { + return { ...copy }; + }); + }; + + const disconnect = () => { + stompClient?.deactivate(); + }; + + return { + disconnect, + subscribe, + unsubscribe, + subscriptions, + send, + isConnected: !!stompClient?.connected, + }; +}; diff --git a/web/src/stompHook/index.js b/web/src/stompHook/index.js new file mode 100644 index 00000000..c9ff08a5 --- /dev/null +++ b/web/src/stompHook/index.js @@ -0,0 +1,2 @@ +export { StompProvider } from "./Provider"; +export { useStomp } from "./hook"; diff --git a/web/vite.config.js b/web/vite.config.js new file mode 100644 index 00000000..cf1d5638 --- /dev/null +++ b/web/vite.config.js @@ -0,0 +1,16 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + "/api": { + target: "http://localhost:8080", + changeOrigin: true, + secure: false, + }, + }, + }, +});