From 1ba1046e1a5915f7cdbf3b0fd0104676695157dd Mon Sep 17 00:00:00 2001 From: Freddie Vargus Date: Sun, 4 Feb 2024 20:45:07 -0500 Subject: [PATCH] Add Aya ui, backend, analytics app --- .github/pull_request_template.md | 23 + .github/workflows/eslint.yml | 34 + .github/workflows/lighthouse.yml | 38 + .github/workflows/run-pytest.yml | 66 + .github/workflows/test.yml | 34 + .gitignore | 52 + DEV.md | 257 + LICENSE | 201 + README.md | 14 +- analytics/app/analytics.py | 452 + analytics/app/app.py | 651 + analytics/app/app.yaml | 13 + analytics/app/constants.py | 378 + analytics/app/logo/aya_logo.png | Bin 0 -> 49376 bytes analytics/app/requirements.txt | 7 + analytics/app/sql_queries.py | 399 + analytics/app/utils.py | 104 + backend/.dockerignore | 2 + backend/.env.example | 36 + backend/.env.test | 33 + backend/Dockerfile | 22 + backend/alembic.ini | 107 + backend/alembic/README | 1 + backend/alembic/env.py | 123 + backend/alembic/script.py.mako | 24 + ...0330-cb5b5832ab95-create_initial_schema.py | 132 + ...518c1ce4ee0-modify_dataset_name_to_text.py | 24 + ...-1755-48082df0bbba-modify_language_code.py | 43 + ...50-5cbe4ab55392-add_language_codes_bulk.py | 205 + ...ask_num_and_language_id_column_for_task.py | 42 + ...ique_constraint_prompt_completion_tasks.py | 34 + ...157-eb13778e0573-add_country_codes_bulk.py | 43 + ...8a194fa402-unique_language_code_records.py | 32 + ...28-0209-5536f1b704eb-add_more_languages.py | 51 + ...3c4e62fadcf-add_more_missing_lang_codes.py | 60 + ...to_language_code_to_identify_rtl_or_ltr.py | 72 + ...bd6ca3-recreate_prompt_completion_index.py | 81 + ...814-b85910c50121-rename_task_submission.py | 38 + ...10691cae9e3-add_task_contribution_table.py | 41 + ...58-dbde0b456746-add_leaderboard_columns.py | 45 + ...ca6a9336-modify_leaderboard_constraints.py | 76 + ...f233a368f1c4-add_fields_to_leaderboards.py | 55 + ...c32963c-add_active_toggling_for_dataset.py | 47 + ...90905dca267b-separate_chinese_hans_hant.py | 131 + ...e4985d7daa7-deactivate_arb_latn_dataset.py | 56 + ...56-08d92ecbd824-mark_more_rtl_languages.py | 41 + ...6b48ad7a-add_more_user_survey_questions.py | 54 + ...-06-01-0405-cf118e4d101b-make_hausa_ltr.py | 26 + ...5ede-add_user_languages_to_leaderboards.py | 57 + ...68daab0b8ccb-store_discord_email_and_id.py | 38 + ...8c-deactivate_gem_wiki_cat_sum_datasets.py | 38 + ...b-add_edit_distance_to_task_audit_table.py | 29 + ...update_leaderboard_table_uq_constraints.py | 141 + ...12991-add_task_contribution_audit_table.py | 39 + ...-29-1305-233490849236-task_audit_review.py | 51 + ...75912fb74-rename_pedi_to_northern_sotho.py | 25 + ...-05-1855-aec96b6f1dd6-add_latin_support.py | 36 + ...1857-9fdf109afc92-fix_panjabi_direction.py | 26 + ...1dd7526c13d5-deactivate_xp3_flores_data.py | 25 + ...c68b55f6b7-add_audit_review_limit_table.py | 82 + ...583aaea36f-remove_translation_xp3_tasks.py | 39 + ...0-add_created_at_for_audit_review_limit.py | 41 + ...-add_blended_points_overall_leaderboard.py | 28 + ...1-add_quality_score_overall_leaderboard.py | 36 + ...-08-08-0547-4c8caa38e453-add_google_idp.py | 36 + ...fb1a5e91f4-add_more_dataset_table_enums.py | 28 + ...-08-17-0558-8aff6eda53a6-add_task_views.py | 94 + ...bc7a4fb61-add_contrib_details_to_task_3.py | 99 + ...73d47-add_dialect_column_to_users_table.py | 29 + ...40-ba32cbdb7ef4-drop_audit_review_limit.py | 26 + ...hange_dialect_column_to_list_of_strings.py | 39 + ...-30-2310-2ba53ea38f6c-add_iban_language.py | 36 + ...60808d189e69-update_sundanese_char_code.py | 26 + ...-17-0351-a7ea807723e4-add_sarawak_malay.py | 35 + ...-add_quality_and_aya_score_per_language.py | 57 + ...672360a33e72-identify_primary_101_langs.py | 150 + ...0429-5b80958da127-fix_primary_101_langs.py | 30 + backend/instruct_multilingual/__init__.py | 0 backend/instruct_multilingual/api/__init__.py | 0 .../instruct_multilingual/api/v1/__init__.py | 0 backend/instruct_multilingual/api/v1/api.py | 20 + backend/instruct_multilingual/api/v1/auth.py | 74 + .../api/v1/auth_discord.py | 285 + .../api/v1/auth_google.py | 230 + .../api/v1/leaderboards.py | 265 + backend/instruct_multilingual/api/v1/tasks.py | 329 + backend/instruct_multilingual/api/v1/users.py | 230 + backend/instruct_multilingual/config.py | 57 + backend/instruct_multilingual/db.py | 13 + backend/instruct_multilingual/exceptions.py | 27 + .../instruct_multilingual/models/__init__.py | 30 + .../models/country_code.py | 44 + .../instruct_multilingual/models/dataset.py | 53 + .../models/language_code.py | 51 + .../models/leaderboard.py | 260 + backend/instruct_multilingual/models/task.py | 72 + .../models/task_1_submission.py | 71 + .../models/task_3_submission.py | 81 + .../models/task_audit.py | 83 + .../models/task_audit_review.py | 70 + .../models/task_contribution.py | 53 + .../models/task_contribution_audit.py | 83 + .../models/task_contribution_audit_review.py | 70 + backend/instruct_multilingual/models/user.py | 94 + .../schemas/leaderboard.py | 66 + backend/instruct_multilingual/schemas/task.py | 158 + backend/instruct_multilingual/schemas/user.py | 130 + .../services/__init__.py | 12 + .../services/task_audit_service.py | 149 + .../services/task_contribution_service.py | 147 + .../services/task_review_service.py | 88 + .../services/task_service.py | 78 + .../services/user_contribution_service.py | 58 + .../instruct_multilingual/utils/__init__.py | 0 .../utils/webhook_utils.py | 59 + backend/jobs/__init__.py | 4 + backend/jobs/by_language/Dockerfile | 15 + backend/jobs/by_language/__init__.py | 0 .../leaderboard_by_language_job.py | 561 + backend/jobs/common.py | 132 + backend/jobs/daily/Dockerfile | 15 + backend/jobs/daily/__init__.py | 0 backend/jobs/daily/leaderboard_daily_job.py | 306 + backend/jobs/leaderboard_update_job.py | 56 + backend/jobs/overall/Dockerfile | 15 + backend/jobs/overall/__init__.py | 0 .../jobs/overall/leaderboard_overall_job.py | 500 + backend/jobs/weekly/Dockerfile | 15 + backend/jobs/weekly/__init__.py | 0 backend/jobs/weekly/leaderboard_weekly_job.py | 328 + backend/main.py | 95 + backend/poetry.lock | 3492 ++++ backend/pyproject.toml | 41 + backend/run.sh | 1 + backend/scripts/seed_db.py | 275 + backend/tests/api/test_auth.py | 36 + backend/tests/api/test_auth_discord.py | 8 + backend/tests/api/test_tasks.py | 387 + backend/tests/api_utils.py | 21 + backend/tests/conftest.py | 86 + backend/tests/db_utils.py | 108 + cloudbuild-backend-staging.yaml | 49 + cloudbuild-backend.yaml | 49 + cloudbuild-frontend-staging.yaml | 71 + cloudbuild-frontend.yaml | 71 + docker-compose.yml | 104 + docker/Dockerfile.postgres | 9 + frontend/.babelrc | 3 + frontend/.dockerignore | 3 + frontend/.env.example | 6 + frontend/.eslintrc | 7 + frontend/.prettierrc | 29 + frontend/Dockerfile | 25 + frontend/index.html | 32 + frontend/jest.config.mjs | 195 + frontend/lighthouserc.cjs | 10 + frontend/package-lock.json | 13719 ++++++++++++++++ frontend/package.json | 60 + frontend/postcss.config.cjs | 6 + .../public/JCA616_Aya_logo_White_AYA_logo.png | Bin 0 -> 45226 bytes .../public/JCA616_Aya_logo_blue_AYA_logo.png | Bin 0 -> 49376 bytes frontend/public/c4ai_logo.svg | 26 + frontend/public/forai.png | Bin 0 -> 3991 bytes frontend/public/google-logo.png | Bin 0 -> 97466 bytes frontend/public/google-white.svg | 3 + frontend/routes.js | 11 + frontend/server.js | 41 + frontend/src/App.jsx | 54 + .../src/__tests__/helpers/ageGroup.test.js | 118 + frontend/src/__tests__/helpers/auth.test.js | 95 + frontend/src/__tests__/helpers/config.test.js | 41 + .../src/__tests__/helpers/country.test.js | 137 + frontend/src/__tests__/helpers/gender.test.js | 105 + .../src/__tests__/helpers/language.test.js | 180 + frontend/src/components/About.jsx | 53 + frontend/src/components/AuthHandler.jsx | 22 + frontend/src/components/DialectInput.jsx | 38 + frontend/src/components/Footer.jsx | 58 + frontend/src/components/GetDetails.jsx | 228 + .../components/GetStartedDiscordButton.jsx | 47 + .../src/components/GetStartedGoogleButton.jsx | 51 + frontend/src/components/Header.jsx | 129 + frontend/src/components/Hero.jsx | 166 + frontend/src/components/HowItWorks.jsx | 192 + .../src/components/LeaderBoardDisplay.jsx | 499 + .../LeaderBoardOverallDetailsRow.jsx | 81 + .../src/components/LeaderBoardOverallRow.jsx | 103 + .../src/components/LeaderBoardPedestal.jsx | 16 + frontend/src/components/LeaderBoardRow.jsx | 65 + frontend/src/components/LoginCallback.jsx | 33 + .../src/components/MultiSelectDropdown.jsx | 39 + frontend/src/components/Navbar.jsx | 154 + frontend/src/components/PrivateRoute.jsx | 34 + .../src/components/SingleSelectDropdown.jsx | 36 + frontend/src/components/Task1.jsx | 633 + frontend/src/components/Task2.jsx | 259 + frontend/src/components/Task3.jsx | 806 + frontend/src/components/TaskDescriptions.jsx | 7 + frontend/src/components/TaskSwitcher.jsx | 48 + frontend/src/components/ThumbsUpDown.jsx | 40 + frontend/src/config/ga.jsx | 13 + frontend/src/css/_variables.scss | 7 + frontend/src/css/components/.gitkeep | 0 frontend/src/css/main.scss | 61 + frontend/src/css/pages/_app.scss | 6 + frontend/src/css/pages/_home.scss | 0 frontend/src/css/pages/_leaderboard.scss | 7 + frontend/src/css/pages/_workspace.scss | 4 + frontend/src/helpers/ageGroup.jsx | 44 + frontend/src/helpers/auth.jsx | 94 + frontend/src/helpers/config.jsx | 12 + frontend/src/helpers/country.jsx | 38 + frontend/src/helpers/ga.jsx | 48 + frontend/src/helpers/gender.jsx | 41 + frontend/src/helpers/language.jsx | 60 + frontend/src/helpers/leaderboard.jsx | 67 + frontend/src/helpers/radio.jsx | 17 + frontend/src/helpers/task.jsx | 98 + frontend/src/helpers/theme.jsx | 11 + frontend/src/helpers/user.jsx | 93 + frontend/src/img/c4ai_logo.png | Bin 0 -> 3991 bytes frontend/src/listeners/AnalyticsListener.jsx | 20 + frontend/src/main.jsx | 43 + frontend/src/pages/AddDetails.jsx | 13 + frontend/src/pages/Error401.jsx | 19 + frontend/src/pages/Error404.jsx | 18 + frontend/src/pages/GradioAnalytics.jsx | 18 + frontend/src/pages/Home.jsx | 21 + frontend/src/pages/LeaderBoard.jsx | 13 + frontend/src/pages/Settings.jsx | 181 + .../UserContributionPage/ContributionCard.jsx | 43 + .../ContributionPaginator.jsx | 127 + .../ContributionPaginatorSmall.jsx | 101 + .../UserContributionPage/TaskDetails.jsx | 291 + .../UserContributionPage/TaskSwitcher.jsx | 37 + .../ThumbsUpDownDisplay.jsx | 33 + .../src/pages/UserContributionPage/index.jsx | 206 + frontend/src/pages/Workspace.jsx | 106 + .../src/recoil/atoms/isAuthenticatedState.jsx | 6 + frontend/tailwind.config.cjs | 15 + frontend/vite.config.js | 56 + ops/create-jobs.sh | 92 + ops/deploy.sh | 54 + ops/tag-and-push.sh | 16 + team-scripts/README.md | 129 + team-scripts/add_dataset.py | 642 + team-scripts/requirements.txt | 13 + 247 files changed, 38132 insertions(+), 1 deletion(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/eslint.yml create mode 100644 .github/workflows/lighthouse.yml create mode 100644 .github/workflows/run-pytest.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 DEV.md create mode 100644 LICENSE create mode 100644 analytics/app/analytics.py create mode 100644 analytics/app/app.py create mode 100644 analytics/app/app.yaml create mode 100644 analytics/app/constants.py create mode 100644 analytics/app/logo/aya_logo.png create mode 100644 analytics/app/requirements.txt create mode 100644 analytics/app/sql_queries.py create mode 100644 analytics/app/utils.py create mode 100644 backend/.dockerignore create mode 100644 backend/.env.example create mode 100644 backend/.env.test create mode 100644 backend/Dockerfile create mode 100644 backend/alembic.ini create mode 100644 backend/alembic/README create mode 100644 backend/alembic/env.py create mode 100644 backend/alembic/script.py.mako create mode 100644 backend/alembic/versions/2023-03-28-0330-cb5b5832ab95-create_initial_schema.py create mode 100644 backend/alembic/versions/2023-03-31-1747-8518c1ce4ee0-modify_dataset_name_to_text.py create mode 100644 backend/alembic/versions/2023-03-31-1755-48082df0bbba-modify_language_code.py create mode 100644 backend/alembic/versions/2023-04-01-2050-5cbe4ab55392-add_language_codes_bulk.py create mode 100644 backend/alembic/versions/2023-04-07-0526-b5eb22ad2a73-add_task_num_and_language_id_column_for_task.py create mode 100644 backend/alembic/versions/2023-04-09-0404-1276d056240a-unique_constraint_prompt_completion_tasks.py create mode 100644 backend/alembic/versions/2023-04-17-0157-eb13778e0573-add_country_codes_bulk.py create mode 100644 backend/alembic/versions/2023-04-24-0059-518a194fa402-unique_language_code_records.py create mode 100644 backend/alembic/versions/2023-04-28-0209-5536f1b704eb-add_more_languages.py create mode 100644 backend/alembic/versions/2023-04-30-2152-c3c4e62fadcf-add_more_missing_lang_codes.py create mode 100644 backend/alembic/versions/2023-05-02-0319-d6e388430a44-add_column_to_language_code_to_identify_rtl_or_ltr.py create mode 100644 backend/alembic/versions/2023-05-05-0407-2bb74cbd6ca3-recreate_prompt_completion_index.py create mode 100644 backend/alembic/versions/2023-05-06-1814-b85910c50121-rename_task_submission.py create mode 100644 backend/alembic/versions/2023-05-06-1831-e10691cae9e3-add_task_contribution_table.py create mode 100644 backend/alembic/versions/2023-05-09-2158-dbde0b456746-add_leaderboard_columns.py create mode 100644 backend/alembic/versions/2023-05-14-2355-ff37ca6a9336-modify_leaderboard_constraints.py create mode 100644 backend/alembic/versions/2023-05-15-0134-f233a368f1c4-add_fields_to_leaderboards.py create mode 100644 backend/alembic/versions/2023-05-19-1524-849bcc32963c-add_active_toggling_for_dataset.py create mode 100644 backend/alembic/versions/2023-05-22-0235-90905dca267b-separate_chinese_hans_hant.py create mode 100644 backend/alembic/versions/2023-05-25-0353-7e4985d7daa7-deactivate_arb_latn_dataset.py create mode 100644 backend/alembic/versions/2023-05-26-0756-08d92ecbd824-mark_more_rtl_languages.py create mode 100644 backend/alembic/versions/2023-05-30-0340-785f6b48ad7a-add_more_user_survey_questions.py create mode 100644 backend/alembic/versions/2023-06-01-0405-cf118e4d101b-make_hausa_ltr.py create mode 100644 backend/alembic/versions/2023-06-02-0401-38bb36535ede-add_user_languages_to_leaderboards.py create mode 100644 backend/alembic/versions/2023-06-16-1358-68daab0b8ccb-store_discord_email_and_id.py create mode 100644 backend/alembic/versions/2023-06-19-1154-53f48b99dd8c-deactivate_gem_wiki_cat_sum_datasets.py create mode 100644 backend/alembic/versions/2023-06-21-2151-b9d22315640b-add_edit_distance_to_task_audit_table.py create mode 100644 backend/alembic/versions/2023-06-24-2202-0bf3df1a1e43-update_leaderboard_table_uq_constraints.py create mode 100644 backend/alembic/versions/2023-06-26-1423-3380a3512991-add_task_contribution_audit_table.py create mode 100644 backend/alembic/versions/2023-06-29-1305-233490849236-task_audit_review.py create mode 100644 backend/alembic/versions/2023-07-01-1250-35875912fb74-rename_pedi_to_northern_sotho.py create mode 100644 backend/alembic/versions/2023-07-05-1855-aec96b6f1dd6-add_latin_support.py create mode 100644 backend/alembic/versions/2023-07-05-1857-9fdf109afc92-fix_panjabi_direction.py create mode 100644 backend/alembic/versions/2023-07-06-0355-1dd7526c13d5-deactivate_xp3_flores_data.py create mode 100644 backend/alembic/versions/2023-07-06-0418-97c68b55f6b7-add_audit_review_limit_table.py create mode 100644 backend/alembic/versions/2023-07-06-1248-c2583aaea36f-remove_translation_xp3_tasks.py create mode 100644 backend/alembic/versions/2023-07-06-1327-5c6ab0fe5920-add_created_at_for_audit_review_limit.py create mode 100644 backend/alembic/versions/2023-07-30-2300-036364ef81bd-add_blended_points_overall_leaderboard.py create mode 100644 backend/alembic/versions/2023-08-07-1810-8f4f6a9c6a31-add_quality_score_overall_leaderboard.py create mode 100644 backend/alembic/versions/2023-08-08-0547-4c8caa38e453-add_google_idp.py create mode 100644 backend/alembic/versions/2023-08-17-0259-b9fb1a5e91f4-add_more_dataset_table_enums.py create mode 100644 backend/alembic/versions/2023-08-17-0558-8aff6eda53a6-add_task_views.py create mode 100644 backend/alembic/versions/2023-08-23-0958-b5cbc7a4fb61-add_contrib_details_to_task_3.py create mode 100644 backend/alembic/versions/2023-08-25-2238-e7409f573d47-add_dialect_column_to_users_table.py create mode 100644 backend/alembic/versions/2023-08-27-2240-ba32cbdb7ef4-drop_audit_review_limit.py create mode 100644 backend/alembic/versions/2023-08-29-2317-476d8e005a09-change_dialect_column_to_list_of_strings.py create mode 100644 backend/alembic/versions/2023-08-30-2310-2ba53ea38f6c-add_iban_language.py create mode 100644 backend/alembic/versions/2023-09-13-1527-60808d189e69-update_sundanese_char_code.py create mode 100644 backend/alembic/versions/2023-09-17-0351-a7ea807723e4-add_sarawak_malay.py create mode 100644 backend/alembic/versions/2023-09-20-0316-b389845cf201-add_quality_and_aya_score_per_language.py create mode 100644 backend/alembic/versions/2023-11-18-0420-672360a33e72-identify_primary_101_langs.py create mode 100644 backend/alembic/versions/2023-12-24-0429-5b80958da127-fix_primary_101_langs.py create mode 100644 backend/instruct_multilingual/__init__.py create mode 100644 backend/instruct_multilingual/api/__init__.py create mode 100644 backend/instruct_multilingual/api/v1/__init__.py create mode 100644 backend/instruct_multilingual/api/v1/api.py create mode 100644 backend/instruct_multilingual/api/v1/auth.py create mode 100644 backend/instruct_multilingual/api/v1/auth_discord.py create mode 100644 backend/instruct_multilingual/api/v1/auth_google.py create mode 100644 backend/instruct_multilingual/api/v1/leaderboards.py create mode 100644 backend/instruct_multilingual/api/v1/tasks.py create mode 100644 backend/instruct_multilingual/api/v1/users.py create mode 100644 backend/instruct_multilingual/config.py create mode 100644 backend/instruct_multilingual/db.py create mode 100644 backend/instruct_multilingual/exceptions.py create mode 100644 backend/instruct_multilingual/models/__init__.py create mode 100644 backend/instruct_multilingual/models/country_code.py create mode 100644 backend/instruct_multilingual/models/dataset.py create mode 100644 backend/instruct_multilingual/models/language_code.py create mode 100644 backend/instruct_multilingual/models/leaderboard.py create mode 100644 backend/instruct_multilingual/models/task.py create mode 100644 backend/instruct_multilingual/models/task_1_submission.py create mode 100644 backend/instruct_multilingual/models/task_3_submission.py create mode 100644 backend/instruct_multilingual/models/task_audit.py create mode 100644 backend/instruct_multilingual/models/task_audit_review.py create mode 100644 backend/instruct_multilingual/models/task_contribution.py create mode 100644 backend/instruct_multilingual/models/task_contribution_audit.py create mode 100644 backend/instruct_multilingual/models/task_contribution_audit_review.py create mode 100644 backend/instruct_multilingual/models/user.py create mode 100644 backend/instruct_multilingual/schemas/leaderboard.py create mode 100644 backend/instruct_multilingual/schemas/task.py create mode 100644 backend/instruct_multilingual/schemas/user.py create mode 100644 backend/instruct_multilingual/services/__init__.py create mode 100644 backend/instruct_multilingual/services/task_audit_service.py create mode 100644 backend/instruct_multilingual/services/task_contribution_service.py create mode 100644 backend/instruct_multilingual/services/task_review_service.py create mode 100644 backend/instruct_multilingual/services/task_service.py create mode 100644 backend/instruct_multilingual/services/user_contribution_service.py create mode 100644 backend/instruct_multilingual/utils/__init__.py create mode 100644 backend/instruct_multilingual/utils/webhook_utils.py create mode 100644 backend/jobs/__init__.py create mode 100644 backend/jobs/by_language/Dockerfile create mode 100644 backend/jobs/by_language/__init__.py create mode 100644 backend/jobs/by_language/leaderboard_by_language_job.py create mode 100644 backend/jobs/common.py create mode 100644 backend/jobs/daily/Dockerfile create mode 100644 backend/jobs/daily/__init__.py create mode 100644 backend/jobs/daily/leaderboard_daily_job.py create mode 100644 backend/jobs/leaderboard_update_job.py create mode 100644 backend/jobs/overall/Dockerfile create mode 100644 backend/jobs/overall/__init__.py create mode 100644 backend/jobs/overall/leaderboard_overall_job.py create mode 100644 backend/jobs/weekly/Dockerfile create mode 100644 backend/jobs/weekly/__init__.py create mode 100644 backend/jobs/weekly/leaderboard_weekly_job.py create mode 100644 backend/main.py create mode 100644 backend/poetry.lock create mode 100644 backend/pyproject.toml create mode 100755 backend/run.sh create mode 100755 backend/scripts/seed_db.py create mode 100644 backend/tests/api/test_auth.py create mode 100644 backend/tests/api/test_auth_discord.py create mode 100644 backend/tests/api/test_tasks.py create mode 100644 backend/tests/api_utils.py create mode 100644 backend/tests/conftest.py create mode 100644 backend/tests/db_utils.py create mode 100644 cloudbuild-backend-staging.yaml create mode 100644 cloudbuild-backend.yaml create mode 100644 cloudbuild-frontend-staging.yaml create mode 100644 cloudbuild-frontend.yaml create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile.postgres create mode 100644 frontend/.babelrc create mode 100644 frontend/.dockerignore create mode 100644 frontend/.env.example create mode 100644 frontend/.eslintrc create mode 100644 frontend/.prettierrc create mode 100644 frontend/Dockerfile create mode 100644 frontend/index.html create mode 100644 frontend/jest.config.mjs create mode 100644 frontend/lighthouserc.cjs create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.cjs create mode 100644 frontend/public/JCA616_Aya_logo_White_AYA_logo.png create mode 100644 frontend/public/JCA616_Aya_logo_blue_AYA_logo.png create mode 100644 frontend/public/c4ai_logo.svg create mode 100644 frontend/public/forai.png create mode 100644 frontend/public/google-logo.png create mode 100644 frontend/public/google-white.svg create mode 100644 frontend/routes.js create mode 100644 frontend/server.js create mode 100644 frontend/src/App.jsx create mode 100644 frontend/src/__tests__/helpers/ageGroup.test.js create mode 100644 frontend/src/__tests__/helpers/auth.test.js create mode 100644 frontend/src/__tests__/helpers/config.test.js create mode 100644 frontend/src/__tests__/helpers/country.test.js create mode 100644 frontend/src/__tests__/helpers/gender.test.js create mode 100644 frontend/src/__tests__/helpers/language.test.js create mode 100644 frontend/src/components/About.jsx create mode 100644 frontend/src/components/AuthHandler.jsx create mode 100644 frontend/src/components/DialectInput.jsx create mode 100644 frontend/src/components/Footer.jsx create mode 100644 frontend/src/components/GetDetails.jsx create mode 100644 frontend/src/components/GetStartedDiscordButton.jsx create mode 100644 frontend/src/components/GetStartedGoogleButton.jsx create mode 100644 frontend/src/components/Header.jsx create mode 100644 frontend/src/components/Hero.jsx create mode 100644 frontend/src/components/HowItWorks.jsx create mode 100644 frontend/src/components/LeaderBoardDisplay.jsx create mode 100644 frontend/src/components/LeaderBoardOverallDetailsRow.jsx create mode 100644 frontend/src/components/LeaderBoardOverallRow.jsx create mode 100644 frontend/src/components/LeaderBoardPedestal.jsx create mode 100644 frontend/src/components/LeaderBoardRow.jsx create mode 100644 frontend/src/components/LoginCallback.jsx create mode 100644 frontend/src/components/MultiSelectDropdown.jsx create mode 100644 frontend/src/components/Navbar.jsx create mode 100644 frontend/src/components/PrivateRoute.jsx create mode 100644 frontend/src/components/SingleSelectDropdown.jsx create mode 100644 frontend/src/components/Task1.jsx create mode 100644 frontend/src/components/Task2.jsx create mode 100644 frontend/src/components/Task3.jsx create mode 100644 frontend/src/components/TaskDescriptions.jsx create mode 100644 frontend/src/components/TaskSwitcher.jsx create mode 100644 frontend/src/components/ThumbsUpDown.jsx create mode 100644 frontend/src/config/ga.jsx create mode 100644 frontend/src/css/_variables.scss create mode 100644 frontend/src/css/components/.gitkeep create mode 100644 frontend/src/css/main.scss create mode 100644 frontend/src/css/pages/_app.scss create mode 100644 frontend/src/css/pages/_home.scss create mode 100644 frontend/src/css/pages/_leaderboard.scss create mode 100644 frontend/src/css/pages/_workspace.scss create mode 100644 frontend/src/helpers/ageGroup.jsx create mode 100644 frontend/src/helpers/auth.jsx create mode 100644 frontend/src/helpers/config.jsx create mode 100644 frontend/src/helpers/country.jsx create mode 100644 frontend/src/helpers/ga.jsx create mode 100644 frontend/src/helpers/gender.jsx create mode 100644 frontend/src/helpers/language.jsx create mode 100644 frontend/src/helpers/leaderboard.jsx create mode 100644 frontend/src/helpers/radio.jsx create mode 100644 frontend/src/helpers/task.jsx create mode 100644 frontend/src/helpers/theme.jsx create mode 100644 frontend/src/helpers/user.jsx create mode 100644 frontend/src/img/c4ai_logo.png create mode 100644 frontend/src/listeners/AnalyticsListener.jsx create mode 100644 frontend/src/main.jsx create mode 100644 frontend/src/pages/AddDetails.jsx create mode 100644 frontend/src/pages/Error401.jsx create mode 100644 frontend/src/pages/Error404.jsx create mode 100644 frontend/src/pages/GradioAnalytics.jsx create mode 100644 frontend/src/pages/Home.jsx create mode 100644 frontend/src/pages/LeaderBoard.jsx create mode 100644 frontend/src/pages/Settings.jsx create mode 100644 frontend/src/pages/UserContributionPage/ContributionCard.jsx create mode 100644 frontend/src/pages/UserContributionPage/ContributionPaginator.jsx create mode 100644 frontend/src/pages/UserContributionPage/ContributionPaginatorSmall.jsx create mode 100644 frontend/src/pages/UserContributionPage/TaskDetails.jsx create mode 100644 frontend/src/pages/UserContributionPage/TaskSwitcher.jsx create mode 100644 frontend/src/pages/UserContributionPage/ThumbsUpDownDisplay.jsx create mode 100644 frontend/src/pages/UserContributionPage/index.jsx create mode 100644 frontend/src/pages/Workspace.jsx create mode 100644 frontend/src/recoil/atoms/isAuthenticatedState.jsx create mode 100644 frontend/tailwind.config.cjs create mode 100644 frontend/vite.config.js create mode 100755 ops/create-jobs.sh create mode 100755 ops/deploy.sh create mode 100755 ops/tag-and-push.sh create mode 100644 team-scripts/README.md create mode 100644 team-scripts/add_dataset.py create mode 100644 team-scripts/requirements.txt diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d69066b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,23 @@ +# What does this PR do? + + + +## Where are the changes made? + +- [ ] Frontend +- [ ] Backend +- [ ] Misc (ops scripts, actions, analytics, etc) + +## Related Issue(s) + + + +## Screenshots (for UI changes) + + + +## These changes have been tested: + +- [ ] Manually +- [ ] Via automated tests +- [ ] Both \ No newline at end of file diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 0000000..94f0ad1 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,34 @@ +name: ESLint + +on: + push: + branches: + - main + paths: + - frontend/ + pull_request: + branches: + - main + paths: + - frontend/** + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: 19 + + - name: Install dependencies + run: npm ci + working-directory: frontend + + - name: Run ESLint + run: npm run lint + working-directory: frontend diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 0000000..3b85686 --- /dev/null +++ b/.github/workflows/lighthouse.yml @@ -0,0 +1,38 @@ +name: Lighthouse + +on: + push: + branches: + - main + pull_request: + branches: + - main + paths: + - 'frontend/**' + +jobs: + lighthouse: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Node.js environment + uses: actions/setup-node@v2 + with: + node-version: 19 + + - name: Install dependencies + run: npm ci + working-directory: frontend + + - name: Build frontend + run: CI=false npm run build + working-directory: frontend + + - name: Run Lighthouse CI + run: | + npm install -g @lhci/cli + lhci autorun + working-directory: frontend diff --git a/.github/workflows/run-pytest.yml b/.github/workflows/run-pytest.yml new file mode 100644 index 0000000..a092507 --- /dev/null +++ b/.github/workflows/run-pytest.yml @@ -0,0 +1,66 @@ +name: Backend unit tests + +on: + push: + branches: [ "staging", "production"] + pull_request: + paths: + - backend/** + - docker/Dockerfile.postgres + +defaults: + run: + working-directory: ./backend + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: backendapp + POSTGRES_PASSWORD: password + POSTGRES_DB: instruct_multilingual + ports: + - 5432:5432 + # set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 2 + + steps: + - uses: actions/checkout@v3 + + - name: build and run postgres docker container + uses: docker/build-push-action@v2 + with: + context: ./docker/ + file: ./docker/Dockerfile.postgres + push: false + tags: database:latest + + - name: set up python 3.10 + id: pythonsetup + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: install dependencies + if: steps.pythonsetup.outcome == 'success' + id: dependencies + run: | + pip install -U pip + pip install poetry + poetry install --with dev + + - name: run pytest unit tests + if: steps.dependencies.outcome == 'success' + run: | + ENVIRONMENT=test poetry run pytest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b97e351 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: NPM Tests + +on: + push: + branches: + - main + paths: + - frontend/ + pull_request: + branches: + - main + paths: + - frontend/** + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Node.js environment + uses: actions/setup-node@v2 + with: + node-version: 19 + + - name: Install dependencies + run: npm ci + working-directory: frontend + + - name: Run tests + run: npm test + working-directory: frontend diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65d354e --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.csv +*.ipynb_checkpoints/ + +# python auto generated files +*__pycache__ +*.pyc +*.pyo + +# env files +.env +*.env.local +*.env.staging* +*.env.prod* +*.env.team-instruct-multilingual-app.prod +*.env.analytics-instruct-multilingual-app.prod +.python-version + +# Project +discord_credentials.json + +# ssl connections files +*.pem + +# Coverage reports +coverage + +# Lighthouse +.lighthouseci diff --git a/DEV.md b/DEV.md new file mode 100644 index 0000000..80a37e5 --- /dev/null +++ b/DEV.md @@ -0,0 +1,257 @@ +# Developer Setup + +- [Dependencies](#dependencies) + - [Install Docker](#install-docker) +- [Frontend](#frontend) + - [How to setup](#how-to-setup) +- [Backend](#backend) + - [Set up local environment variables](#set-up-local-environment-variables) + - [Setup the application and database](#setup-the-application-and-database) + - [Adding alembic migrations](#adding-alembic-migrations) + - [Interacting with the database container](#interacting-with-the-database-container) + - [Modifying leaderboard jobs](#modifying-leaderboard-jobs) + - [Run unit tests](#run-unit-tests) + - [Run in staging mode](#run-in-staging-mode) + - [Run in production mode](#run-in-production-mode) +- [Deploying services to GCP Cloud Run (production)](#deploying-services-to-gcp-cloud-run-production) +- [Aya Analytics App](#aya-analytics-app) + - [Set up local environment variables](#set-up-local-environment-variables-1) + - [Install Requirements](#install-requirements) + - [Run the application](#run-the-application) + + +# Dependencies + +## Install Docker + +Install Docker Desktop: https://docs.docker.com/get-docker/ + +# Frontend + +## How to setup + +1. Set up local environment variables + + For local dev, create a `.env` file at the root of the `frontend/` directory using the `.env.example` file: + + ```console + cp .env.example .env + ``` + +2. Install the dependencies. + + ``` + npm install + ``` + +3. Run. + ``` + npm run develop + ``` + +- View the front page: [http://127.0.0.1:80](http://127.0.0.1:80) + +# Backend + +## Set up local environment variables + +For local dev, create a `.env.local` file at the root of the `backend/` directory using the `.env.example` file: + +```console +cp .env.example .env.local +``` + +## Setup the application and database + +**NOTE:** You must pass an `ENVIRONMENT` variable to ensure you're running in the right env. + +1. Run the app using `docker compose`: +```console +ENVIRONMENT=local docker compose up backend database --build +``` +OR +```console +./run.sh +``` + - View the API docs: [http://127.0.0.1:8080/docs](http://127.0.0.1:8080/docs) +2. Run all the alembic migrations to load the schema: +```console +ENVIRONMENT=local docker compose exec backend alembic upgrade head +``` +3. Seed the local database: +```console +ENVIRONMENT=local docker compose exec backend python -m scripts.seed_db +``` + - This will add a fake user with a language code `spa` and country code `ES`, then create some tasks the user can edit / submit. + + +### Adding alembic migrations +[Alembic Docs](https://alembic.sqlalchemy.org/en/latest/tutorial.html) + +To add a migration run: + +```console +alembic revision -m "some_revision_name" +``` + +Make sure you add an `upgrade()` and `downgrade()` step. Once your migration is added, rebuild the `backend container`, and test that we can upgrade and downgrade a couple times without the DB getting into a broken state: + +```console +ENVIRONMENT=local docker compose exec backend alembic upgrade head +ENVIRONMENT=local docker compose exec backend alembic downgrade -1 +``` + +To check the history of migrations run: + +```console +ENVIRONMENT=local docker compose exec backend alembic history +``` + +### Interacting with the database container + +The `docker compose` command will spin up the backend along with a postgres container that you can interact with. + +To connect to the DB inside of docker, you can run: + +```console +ENVIRONMENT=local docker compose exec database psql instruct_multilingual -U backendapp +``` + +and interactively query data in there such as: + +```console +select * from language_code; +``` + +### Modifying leaderboard jobs + +We currently have some Cloud Run Jobs that run on a scheduled basis via Cloud Scheduler. Each of these jobs runs in their own container, and live in the directory `backend/jobs/`, with each type of job in their own subdirectory like `daily/` and their own Dockerfile with a command they run. + +They use the `leaderboard_update_job.py` script and have the same dependencies as the FastAPI server. + +**Deploying jobs** + +To deploy and modify the schedule of the jobs, you can use the ops script: + +```console +ops/create-jobs.sh +``` + +Which will tell you the options you can use. You must pass a job type and a cron expression as a schedule. + +**Running jobs locally** + +You can run the leaderboard jobs locally with: + +``` +ENVIRONMENT=local docker compose up _leaderboard_job --build +``` + +replacing `` with the available options (see the `docker-compose.yaml` file). + +## Run unit tests + +Currently, we have a `.env.test` file that gets read in by unit tests. To run the unit tests, you'll need a local postgres database *or* run unit tests in the GitHub Actions workflow. + +**Recommended:** +Run the dockerized postgresql instance using: + +``` +docker compose up database +``` + +then run unit tests, which will use the dockerized server and a database called `testdb` + +``` +ENVIRONMENT=test pytest +``` + +## Run in staging mode + +For connecting to the staging database, credentials should be retrieved from GCP Secret Manager, or via a secure message channel. + +1. Create a file called `.env.staging` in the root of the `backend/` project: + +```console +cp .env.example .env.staging +``` + +2. Retrieve the staging database URL from GCP Secret Manager +3. Retrieve the staging discord keys from GCP Secret Manager +4. Replace the values in the `.env.staging` file with the staging values +5. Remove `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` fields +6. Start the server with `ENVIRONMENT=staging`: +```console +ENVIRONMENT=staging docker compose up backend --build +``` + +## Run in production mode + +**NOTE:** Only run in production mode for read-operations and debugging. + +For connecting to the production database, credentials should be retrieved from GCP Secret Manager, or via a secure message channel. + +1. Create a file called `.env.production` in the root of the `backend/` project: + +```console +cp .env.example .env.production +``` + +2. Retrieve the production database URL from GCP Secret Manager +3. Retrieve the production discord keys from GCP Secret Manager +4. Replace the values in the `.env.production` file with the production values +5. Remove `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` fields +6. Start the server with `ENVIRONMENT=production`: +```console +ENVIRONMENT=production docker compose up backend --build +``` + - This will connect to the production database so you don't need to run the local database container + +# Deploying services to GCP Cloud Run (production) + +To deploy the frontend / backend to GCP Cloud Run (production) there are a few involved steps. Namely: + +- Building images +- Pushing to Container Registry +- Deploying the latest image to Cloud Run + +This is automated via GCP Cloud Build. `cloudbuild-frontend.yaml` will automatically do the above steps if there are changes to the `frontend/` directory, and `cloudbuild-backend.yaml` will do the same if there are changes to the `backend/` directory. + +# Aya Analytics App + +## Set up local environment variables + +For local dev, create a `.env.analytics-instruct-multilingual-app.local` file at the root of the `analytics/` directory and add the following environment variables to it: + +```console +INSTANCE_CONNECTION_NAME="" # Cloud SQL instance connection name +DB_USER="" #user name to access DB +DB_PASS="" #password for DB +DB_NAME="" #name of the DB +C4AI_PROJECT_ID="" #C4AI GCP project ID +GRADIO_SERVER_NAME="0.0.0.0" +GRADIO_SERVER_PORT=8080 +APP_ENVIRONMENT="local" +``` + +## Install Requirements + +Create a python virtual environment and install the necessary libraries using `requirements.txt` file + +```console +pip install -r requirements.txt +``` + +## Run the application + +Run the app using `app.py`: +```console +python app.py +``` + +OR + +You can also run the app in reload mode using below command: +```console +gradio app.py +``` \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cda9b6b --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Cohere For AI + + 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 + + http://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. diff --git a/README.md b/README.md index a4f91d7..d4a78bd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # aya-annotations-ui -Web UI used for Data Annotations in Aya +Web UI & Backend used for Data Annotations in Aya + +## What is Aya? + +Aya is an open science project that aims to build a state of art multilingual generative language model; that harnesses the collective wisdom and contributions of people from all over the world. + +## Get Started + +If you'd like to contribute data to Aya, go to [https://aya.for.ai](https://aya.for.ai) and sign up. + +## Developer Guide + +See `DEV.md` \ No newline at end of file diff --git a/analytics/app/analytics.py b/analytics/app/analytics.py new file mode 100644 index 0000000..51d442b --- /dev/null +++ b/analytics/app/analytics.py @@ -0,0 +1,452 @@ +import pandas as pd +from typing import Dict, Optional, List +from collections import Counter +from utils import connect_to_db_with_connector, convert_country_to_continent +from constants import ( + NUM_SUBMISSIONS_COL_NAME, + LANGUAGE_REGION_DICT, + TASK_1_NAME, + AFRICA_REGION, + ASIA_REGION, + EUROPE_REGION, + LATAM_REGION, + OTHERS_REGION, + ALL_LANG_COL_NAME, + ALL_REGION_COL_NAME +) +from sql_queries import ( + TASK_1_LANGUAGE_LEADERBOARD_QUERY, + TASK_2_LANGUAGE_LEADERBOARD_QUERY, + TASK_1_TOP_CONTRIBUTORS_QUERY, + TASK_2_TOP_CONTRIBUTORS_QUERY, + ALL_USER_TASK_QUERY, + USERS_AUDITING_CONTRIBUTED_TASKS_QUERY, + USERS_AUDITING_GENERATED_TASKS_QUERY, + USERS_CONTRIBUTING_NEW_TASKS_QUERY, +) + +connection = connect_to_db_with_connector() + + +def get_date_filtered_task_leaderboards(submission_date: str, task_name) -> pd.DataFrame: + + DATE_FILTERED_TASK_1_LANG_LEADERBOARD = f""" + SELECT + user_id, + username, + language, + country, + SUM(submission_count) AS num_submissions + FROM ( + SELECT + user_id, + username, + language, + country, + submission_count + FROM ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + cc.name AS country, + COALESCE(SUM(1), 0) AS submission_count + FROM + task_audit ta + INNER JOIN "user" u ON ta.submitted_by = u.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + INNER JOIN "country_code" cc ON u.country_code = cc.id + WHERE + DATE_TRUNC('day', ta.created_at) <= '{submission_date}' + GROUP BY + u.id, + lc.name, + u.username, + cc.name + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + cc.name AS country, + COALESCE(SUM(1), 0) AS submission_count + FROM + task_contribution_audit tca + INNER JOIN "user" u ON tca.submitted_by = u.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + INNER JOIN "country_code" cc ON u.country_code = cc.id + WHERE + DATE_TRUNC('day', tca.created_at) <= '{submission_date}' + GROUP BY + u.id, + lc.name, + u.username, + cc.name + + ) AS combined_results + ) AS final_results + GROUP BY + user_id, + username, + language, + country + ORDER BY + num_submissions DESC; + """ + + + DATE_FILTERED_TASK_2_LANG_LEADERBOARD = f""" + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + lc.code AS language_code, + cc.name AS country, + COALESCE(SUM(1), 0) AS num_submissions, + u.image_url AS image_url + + FROM + task_contribution tc + INNER JOIN "user" u ON tc.submitted_by = u.id + INNER JOIN language_code lc ON tc.language_id = lc.id + INNER JOIN "country_code" cc ON u.country_code = cc.id + WHERE + DATE_TRUNC('day', tc.created_at) <= '{submission_date}' + GROUP BY + u.id, + lc.name, + u.username, + u.image_url, + lc.code, + cc.name + """ + + filter_lang_leaderboard_query = ( + DATE_FILTERED_TASK_1_LANG_LEADERBOARD + if task_name == TASK_1_NAME + else DATE_FILTERED_TASK_2_LANG_LEADERBOARD + ) + + return filter_lang_leaderboard_query + +def calculate_growth_percentage(submissions_start_date, submissions_end_date): + if submissions_start_date == 0 and submissions_end_date == 0: + growth_percent = 0 + elif submissions_start_date == 0 and submissions_end_date != 0: + growth_percent = float('inf') + else: + growth_percent = round((((submissions_end_date - submissions_start_date) / submissions_start_date) * 100), 2) + + return growth_percent + + +def calculate_lang_growth(start_date: str, end_date: str, task_name: str, region_filter: str, lang_filter: str): + + start_date_leaderboard_query = get_date_filtered_task_leaderboards(start_date, task_name) + + start_date_leaderboard = calculate_region_specific_scores(task_name, region_filter, lang_filter, leaderboard_query=start_date_leaderboard_query) + start_date_leaderboard = start_date_leaderboard.drop(['REGION'], axis =1) + start_date_leaderboard = start_date_leaderboard.rename(columns={NUM_SUBMISSIONS_COL_NAME: "NUM_SUBMISSIONS_START_DATE"}) + + end_date_leaderboard_query = get_date_filtered_task_leaderboards(end_date, task_name) + + end_date_leaderboard = calculate_region_specific_scores(task_name, region_filter, lang_filter, leaderboard_query=end_date_leaderboard_query) + end_date_leaderboard = end_date_leaderboard.drop(['REGION'], axis =1) + end_date_leaderboard = end_date_leaderboard.rename(columns={NUM_SUBMISSIONS_COL_NAME: "NUM_SUBMISSIONS_END_DATE"}) + + growth_leaderboard = pd.merge(start_date_leaderboard, end_date_leaderboard, how="outer", on=["LANGUAGE"]) + + growth_leaderboard = growth_leaderboard.fillna(0) + + growth_leaderboard['NUM_SUBMSSIONS_ADDED'] = growth_leaderboard.apply(lambda x: (x["NUM_SUBMISSIONS_END_DATE"] - x["NUM_SUBMISSIONS_START_DATE"]), axis=1) + + growth_leaderboard['GROWTH_PERCENTAGE'] = growth_leaderboard.apply(lambda x: calculate_growth_percentage(x["NUM_SUBMISSIONS_START_DATE"], x["NUM_SUBMISSIONS_END_DATE"]), axis=1) + + growth_leaderboard = growth_leaderboard.sort_values( + by=['GROWTH_PERCENTAGE'], ascending=False + ) + + return growth_leaderboard + + + +def get_task_specific_language_leaderboard(lang_leaderboard_query: str) -> pd.DataFrame: + """Get the language leaderboard for a given task (e.g. Rate Model Performance) + + Args: + lang_leaderboard_query (str): The query to be used to calculate the overall language leaderboard for a task. + + Returns: + pd.DataFrame: Dataframe containing overall language leaderboard for a task. + """ + lang_leaderboard = pd.read_sql(lang_leaderboard_query, connection) + + # create 'continent' column based on 'country' of contributors + lang_leaderboard["continent"] = lang_leaderboard["country"].apply( + lambda country_name: convert_country_to_continent(country_name) + ) + return lang_leaderboard + + +def get_task_specific_language_scores(task_name: str, leaderboard_query: str = None) -> Dict: + """Helper function to calculate the total submissions made for all languages for a particular task. + + Args: + task_name (str): Name of the task (e.g 'Rate Model Performance' or 'Contribute your Languge') + + Returns: + Dict: A dict mapping languages to the number of submissions received for them. + """ + if leaderboard_query is None: + language_leaderboard_query = ( + TASK_1_LANGUAGE_LEADERBOARD_QUERY + if task_name == TASK_1_NAME + else TASK_2_LANGUAGE_LEADERBOARD_QUERY + ) + else: + language_leaderboard_query = leaderboard_query + + # call helper function to calculate overall language leaderboard for a given task + language_leaderboard = get_task_specific_language_leaderboard( + language_leaderboard_query + ) + + unique_languages = list(dict(Counter(language_leaderboard["language"])).keys()) + language_score_map = {} + + for lang in unique_languages: + current_lang_df = language_leaderboard[language_leaderboard["language"] == lang] + + # split submissions for 'Spanish' & 'Portuguese' based on region of contributors (Europe & LatAm) + if lang == "Spanish" or lang == "Portuguese": + europe_col_name = lang + " (Europe)" + latam_col_name = lang + " (LatAm)" + language_score_map[europe_col_name] = current_lang_df[ + current_lang_df["continent"] == "Europe" + ]["num_submissions"].sum() + language_score_map[latam_col_name] = current_lang_df[ + current_lang_df["continent"] != "Europe" + ]["num_submissions"].sum() + + # similarly split submissions for standard arabic based on region of contributors (Asia & Africa) + elif lang == "Standard Arabic": + asia_col_name = lang + " (Asia)" + africa_col_name = lang + " (Africa)" + africa_arabic_df = current_lang_df[current_lang_df["continent"] == "Africa"] + asian_arabic_df = current_lang_df[current_lang_df["continent"] != "Africa"] + language_score_map[africa_col_name] = africa_arabic_df[ + "num_submissions" + ].sum() + language_score_map[asia_col_name] = asian_arabic_df["num_submissions"].sum() + + else: + language_score_map[lang] = current_lang_df["num_submissions"].sum() + + return language_score_map + + +def calculate_region_specific_scores( + task_name: str, region_name: str = "ALL REGIONS", language_name: str = "All Languages", leaderboard_query: str = None, +) -> pd.DataFrame: + """Calculate the total submissions made for all languages of a given region for a particular task. + + Args: + task_name (str): Name of the task (e.g 'Rate Model Performance' or 'Contribute your Languge') + region_name (str): Name of the region (continent) for we need to calculate submissions + Returns: + pd.DataFrame: DataFrame containing languages with and their total submissions. + """ + language_score_map = {} + + # calculate total numbers of submissions for all languages for which contributions were received. + language_score_map = get_task_specific_language_scores(task_name, leaderboard_query) + + def map_language_to_region(language): + region_name = "ADDITIONAL" + for region, _ in LANGUAGE_REGION_DICT.items(): + region_specific_languages = LANGUAGE_REGION_DICT[region] + + if language in region_specific_languages: + region_name = region + break + return region_name + + # update `language_score_map` with names of languages that have no contributions + lang_with_no_contributions = [lang for region,_ in LANGUAGE_REGION_DICT.items() for lang in LANGUAGE_REGION_DICT[region] if lang not in language_score_map.keys()] + + for language in lang_with_no_contributions: + language_score_map[language] = 0 + + region_lang_scores = { + "LANGUAGE": language_score_map.keys(), + NUM_SUBMISSIONS_COL_NAME: language_score_map.values(), + } + region_lang_df = pd.DataFrame(region_lang_scores).sort_values( + by=[NUM_SUBMISSIONS_COL_NAME], ascending=False + ) + + # add a 'REGION' column to the dataframe specifying which region does a language fall under for Aya + region_lang_df["REGION"] = region_lang_df["LANGUAGE"].apply( + lambda language_name: map_language_to_region(language_name) + ) + + # filter out submissions for languages related to a particular region based on `region_name` + if region_name == "ALL REGIONS": + region_lang_df = region_lang_df[region_lang_df["REGION"] != "ADDITIONAL"] + else: + region_lang_df = region_lang_df[region_lang_df["REGION"] == region_name] + + # filter out submissions for a particular language if specified + if language_name != "All Languages": + region_lang_df = region_lang_df[ + region_lang_df["LANGUAGE"].str.contains(language_name) + ] + + return region_lang_df + + +def calculate_all_region_scores(task_name: str) -> pd.DataFrame: + """Calculate total region specific submissions for a given task. + + Args: + task_name (str): Name of the task (e.g 'Rate Model Performance' or 'Contribute your Languge') + Returns: + pd.DataFrame: Dataframe containing total submissions made by a region for a task. + """ + + region_score_map = { + EUROPE_REGION: 0, + ASIA_REGION: 0, + AFRICA_REGION: 0, + LATAM_REGION: 0, + OTHERS_REGION: 0, + } + + language_score_map = get_task_specific_language_scores(task_name) + + for language, score in language_score_map.items(): + for region in LANGUAGE_REGION_DICT.keys(): + regional_languages = LANGUAGE_REGION_DICT[region] + if language in regional_languages: + region_score_map[region] += score + break + + # create DataFrame which will be used for plotting results + final_scores = { + ALL_REGION_COL_NAME: region_score_map.keys(), + NUM_SUBMISSIONS_COL_NAME: region_score_map.values(), + } + regional_df = pd.DataFrame(final_scores).sort_values( + by=[NUM_SUBMISSIONS_COL_NAME], ascending=False + ) + return regional_df + + +def get_highest_performing_lang_scores(task_name: str) -> pd.DataFrame: + """Find the total submissions made for the highest performing top 10 languages for a given task. + + Args: + task_name (str): Name of the task (e.g 'Rate Model Performance' or 'Contribute your Languge') + Returns: + pd.DataFrame: Dataframe containing total submissions made for top 10 languages for a task. + """ + language_scores = get_task_specific_language_scores(task_name) + final_lang_scores = { + ALL_LANG_COL_NAME: language_scores.keys(), + NUM_SUBMISSIONS_COL_NAME: language_scores.values(), + } + overall_lang_df = ( + pd.DataFrame(final_lang_scores) + .sort_values(by=[NUM_SUBMISSIONS_COL_NAME], ascending=False) + .head(10) + ) + return overall_lang_df + + +def get_languages_with_most_contributors(task_name: str) -> pd.DataFrame: + """Find the top 10 languages for a given task based on number of contributors. + + Args: + task_name (str): Name of the task (e.g 'Rate Model Performance' or 'Contribute your Languge') + Returns: + pd.DataFrame: Dataframe containing names of top 10 languages based on no. of contributors + """ + query = ( + TASK_1_TOP_CONTRIBUTORS_QUERY + if task_name == TASK_1_NAME + else TASK_2_TOP_CONTRIBUTORS_QUERY + ) + lang_contributor_df = pd.read_sql(query, connection) + lang_with_most_contributors = dict( + Counter(lang_contributor_df["language"]).most_common(10) + ) + + # create DataFrame which will be used for plotting results + lang_contributors = { + ALL_LANG_COL_NAME: lang_with_most_contributors.keys(), + "NUMBER_OF_CONTRIBUTORS": lang_with_most_contributors.values(), + } + overall_lang_contributor_df = pd.DataFrame(lang_contributors).sort_values( + by=["NUMBER_OF_CONTRIBUTORS"], ascending=False + ) + return overall_lang_contributor_df + + +def get_top_contributors(task_name: str) -> pd.DataFrame: + """Find the overall top 10 contributors for a given task. + + Args: + task_name (str): Name of the task (e.g 'Rate Model Performance' or 'Contribute your Languge') + + Returns: + pd.DataFrame: Dataframe containing submission details of top 10 users based on no. of submissions for a task. + """ + contributor_query = ( + TASK_1_TOP_CONTRIBUTORS_QUERY + if task_name == TASK_1_NAME + else TASK_2_TOP_CONTRIBUTORS_QUERY + ) + + top_contributor_scores = pd.read_sql(contributor_query, connection) + + return top_contributor_scores.sort_values( + by=["number_of_unique_submissions"], ascending=False + ).head(10) + + +def get_task_and_user_general_stats() -> int: + """Get general stats about tasks & users including: + 1. Total number of submissions made for all tasks along + 2. Number of users who made submissions for all tasks + + Returns: + pd.DataFrame: + """ + + general_stats = pd.read_sql(ALL_USER_TASK_QUERY, connection) + + return general_stats + + +def get_user_submissions(): + generated_audit_submissions = pd.read_sql( + USERS_AUDITING_GENERATED_TASKS_QUERY, connection + ) + + contributed_audit_submissions = pd.read_sql( + USERS_AUDITING_CONTRIBUTED_TASKS_QUERY, connection + ) + + contributing_new_submissions = pd.read_sql( + USERS_CONTRIBUTING_NEW_TASKS_QUERY, connection + ) + + return ( + generated_audit_submissions, + contributed_audit_submissions, + contributing_new_submissions, + ) diff --git a/analytics/app/app.py b/analytics/app/app.py new file mode 100644 index 0000000..5b00967 --- /dev/null +++ b/analytics/app/app.py @@ -0,0 +1,651 @@ +import os +import gradio as gr +from constants import ( + NUM_SUBMISSIONS_COL_NAME, + TASK_1_NAME, + TASK_2_NAME, + ALL_LANG_COL_NAME, + TOP_LANG_PLOT, + TOP_CONTRIBUTOR_PLOT, + TOP_LANG_CONTRIBUTOR_PLOT, + ASIA_REGION, + EUROPE_REGION, + AFRICA_REGION, + LATAM_REGION, + OTHERS_REGION, + ENV_FILE_PATH, + DB_LANGUAGE_LIST, + MT5_LANGUAGE_LIST, +) +from analytics import ( + calculate_lang_growth, + calculate_growth_percentage, + calculate_all_region_scores, + calculate_region_specific_scores, + get_highest_performing_lang_scores, + get_top_contributors, + get_languages_with_most_contributors, + get_task_and_user_general_stats, + get_user_submissions, +) +import pandas as pd +from dotenv import load_dotenv +from datetime import datetime + +load_dotenv(ENV_FILE_PATH) + +gradio_server_name = os.environ["GRADIO_SERVER_NAME"] +gradio_server_port = os.environ["GRADIO_SERVER_PORT"] + +today_date = datetime.today() + + +def create_bar_plot(plot_data, xlabel, ylabel, plot_title): + return gr.BarPlot.update( + plot_data, + x=xlabel, + y=ylabel, + title=plot_title, + tooltip=[xlabel, ylabel], + height=300, + width=300, + vertical=False, + ) + + +def get_all_region_data(): + task_1_data = calculate_all_region_scores(TASK_1_NAME) + task_2_data = calculate_all_region_scores(TASK_2_NAME) + return task_1_data, task_2_data + + +def create_barplot_for_regional_data(): + xlabel = "REGION" + task_1_plot_title = "Task 1 - Regional Leaderboard" + task_2_plot_title = "Task 2 - Regional Leaderboard" + + # fetch data to create barplots + task_1_data, task_2_data = get_all_region_data() + + # create title for plots + if task_1_plot_title is None and task_2_plot_title is None: + task_1_plot_title = f"Task 1: {xlabel.lower()} - Contribution Breakdown" + task_2_plot_title = f"Task 2: {xlabel.lower()}- Contribution Breakdown" + + ylabel = NUM_SUBMISSIONS_COL_NAME + + # create Task 1 plot + task_1_region_plot = create_bar_plot(task_1_data, xlabel, ylabel, task_1_plot_title) + # Create Task 2 Plot + task_2_region_plot = create_bar_plot(task_2_data, xlabel, ylabel, task_2_plot_title) + return task_1_region_plot, task_2_region_plot + + +def get_region_based_task_analytics(region_filter, language_filter): + task1_sum_reset, task2_sum_reset = 0, 0 + task_1_data = calculate_region_specific_scores( + TASK_1_NAME, region_filter, language_filter + ) + task_2_data = calculate_region_specific_scores( + TASK_2_NAME, region_filter, language_filter + ) + return task_1_data, task_2_data, task1_sum_reset, task2_sum_reset + + +def plot_highest_performing_languages(task_lang_df, task_name): + xlabel = ALL_LANG_COL_NAME + ylabel = NUM_SUBMISSIONS_COL_NAME + task_type = "Task 1" if task_name == TASK_1_NAME else "Task 2" + plot_title = "Overall Top 10 Languages Scoreboard for " + task_type + overall_top_lang_plot = create_bar_plot(task_lang_df, xlabel, ylabel, plot_title) + return overall_top_lang_plot + + +def plot_highest_performing_contributors(task_lang_df, task_name): + xlabel = "username" + ylabel = "number_of_unique_submissions" + task_type = "Task 1" if task_name == TASK_1_NAME else "Task 2" + plot_title = "Overall Top 10 Contributors Scoreboard for " + task_type + overall_top_lang_plot = create_bar_plot(task_lang_df, xlabel, ylabel, plot_title) + return overall_top_lang_plot + + +def plot_languages_with_most_contributors(task_lang_df, task_name): + xlabel = "LANGUAGE" + ylabel = "NUMBER_OF_CONTRIBUTORS" + task_type = "Task 1" if task_name == TASK_1_NAME else "Task 2" + plot_title = "Languages with most no.of contributors for " + task_type + overall_top_lang_plot = create_bar_plot(task_lang_df, xlabel, ylabel, plot_title) + return overall_top_lang_plot + + +def create_overall_performance_plots(plot_type: str): + task_1_df, task_2_df = get_overall_task_analytics(plot_type) + if plot_type == TOP_LANG_PLOT: + task_1_top_lang_plot = plot_highest_performing_languages(task_1_df, TASK_1_NAME) + task_2_top_lang_plot = plot_highest_performing_languages(task_2_df, TASK_2_NAME) + return task_1_top_lang_plot, task_2_top_lang_plot + + elif plot_type == TOP_CONTRIBUTOR_PLOT: + task_1_top_contributor_plot = plot_highest_performing_contributors( + task_1_df, TASK_1_NAME + ) + task_2_top_contributor_plot = plot_highest_performing_contributors( + task_2_df, TASK_2_NAME + ) + return task_1_top_contributor_plot, task_2_top_contributor_plot + + elif plot_type == TOP_LANG_CONTRIBUTOR_PLOT: + task_1_lang_contributor_plot = plot_languages_with_most_contributors( + task_1_df, TASK_1_NAME + ) + task_2_lang_contributor_plot = plot_languages_with_most_contributors( + task_2_df, TASK_2_NAME + ) + return task_1_lang_contributor_plot, task_2_lang_contributor_plot + + +def get_overall_task_analytics(plot_type): + if plot_type == TOP_LANG_PLOT: + task_1_top_lang_df = get_highest_performing_lang_scores(TASK_1_NAME) + task_2_top_lang_df = get_highest_performing_lang_scores(TASK_2_NAME) + return task_1_top_lang_df, task_2_top_lang_df + + elif plot_type == TOP_CONTRIBUTOR_PLOT: + task_1_top_contributor_df = get_top_contributors(TASK_1_NAME) + task_2_top_contributor_df = get_top_contributors(TASK_2_NAME) + return task_1_top_contributor_df, task_2_top_contributor_df + + elif plot_type == TOP_LANG_CONTRIBUTOR_PLOT: + task_1_lang_contributor_df = get_languages_with_most_contributors(TASK_1_NAME) + task_2_lang_contributor_df = get_languages_with_most_contributors(TASK_2_NAME) + return task_1_lang_contributor_df, task_2_lang_contributor_df + + +def compute_general_stats(): + general_stats = get_task_and_user_general_stats() + + # total number of users who have registered using Aya annotation tool + num_all_users = general_stats["total_registered_users"].iloc[0] + + # number of users who have been auditing generated tasks under 'Task 1' + num_users_auditing_generated_tasks = general_stats[ + "num_users_audited_generated_tasks" + ].iloc[0] + # number of users who have been auditing contributed tasks under 'Task 1' + num_users_auditing_contributed_tasks = general_stats[ + "num_users_auditing_contributed_tasks" + ].iloc[0] + # total number of users contributing for task 1 + num_task_1_users = general_stats["task_1_user_count"].iloc[0] + # number of users who have been adding new contributions via 'Task 2' + num_task_2_users = general_stats["num_users_contributing_new_tasks"].iloc[0] + + # How many tasks have been submitted of the type ('generated_audit') + num_audited_generated_tasks = general_stats["num_audited_generated_tasks"].iloc[0] + # How many tasks have been submitted of the type ('contributed_audit') + num_audited_contributed_tasks = general_stats["num_audited_contributed_tasks"].iloc[ + 0 + ] + # Total submissions made for task 1 + num_task_1_tasks = num_audited_contributed_tasks + num_audited_generated_tasks + # How many tasks have been submitted overall for Task 2 + num_task_2_tasks = general_stats["num_contributed_new_tasks"].iloc[0] + + return ( + num_all_users, + num_task_1_users, + num_users_auditing_contributed_tasks, + num_users_auditing_generated_tasks, + num_task_2_users, + num_task_1_tasks, + num_audited_generated_tasks, + num_audited_contributed_tasks, + num_task_2_tasks, + ) + + +def filter_user_submissions(language_name): + ( + audited_generated_task_df, + audited_contributed_task_df, + contributed_new_task_df, + ) = get_user_submissions() + + filtered_audited_generated_task_df = audited_generated_task_df[ + audited_generated_task_df["language"] == language_name + ] + filtered_audited_contributed_task_df = audited_contributed_task_df[ + audited_contributed_task_df["language"] == language_name + ] + filtered_contributed_new_task_df = contributed_new_task_df[ + contributed_new_task_df["language"] == language_name + ] + + return ( + filtered_audited_generated_task_df, + filtered_audited_contributed_task_df, + filtered_contributed_new_task_df, + ) + + +def compute_task_submissions(task_df): + num_submissions = task_df["NUMBER OF UNIQUE SUBMISSIONS"].sum() + return num_submissions + + +def compute_total_growth(task_growth_df): + num_submissions_start_date = task_growth_df["NUM_SUBMISSIONS_START_DATE"].sum() + num_submissions_end_date = task_growth_df["NUM_SUBMISSIONS_END_DATE"].sum() + total_growth = calculate_growth_percentage(num_submissions_start_date, num_submissions_end_date) + return num_submissions_start_date, num_submissions_end_date, total_growth + +def calculate_growth(start_date_day, start_date_month, start_date_year, end_date_day, end_date_month, end_date_year, + task_choice, region_choice, lang_choice): + + start_date = f"{start_date_year}-{start_date_month}-{start_date_day}" + end_date = f"{end_date_year}-{end_date_month}-{end_date_day}" + + task_growth_df = calculate_lang_growth(start_date, end_date, task_choice, region_choice, lang_choice) + + return task_growth_df + + +with gr.Blocks(theme="shivi/calm_seafoam") as demo: + with gr.Row(variant="panel"): + with gr.Column(): + gr.HTML( + """Aya logo
""" + ) + with gr.Column(): + gr.HTML( + """

Aya: An Open Science Initiative to Accelerate Multilingual AI Progress

""" + ) + gr.Markdown( + "Aya is an open science project that aims to build a multilingual language model via instruction tuning that harnesses the collective wisdom and contributions of people from all over the world. With Aya, we want to improve available multilingual generative models and accelerate progress for languages across the world. Contributing to Aya is open to anyone who is passionate about advancing the field of NLP and is committed to promoting open science. By joining Aya, you become part of a global movement dedicated to democratizing access to language technology." + ) + + with gr.TabItem("Regional Analytics"): + with gr.TabItem("Graphical View") as regional_analytics_graph_view: + with gr.Row(): + with gr.Column(): + plot = gr.BarPlot(show_label=False).style(container=True) + with gr.Column(): + plot2 = gr.BarPlot(show_label=False).style(container=True) + + with gr.TabItem("Tabular View") as regional_analytics_tabular_view: + with gr.Row(): + with gr.Column(variant="Panel"): + all_region_task1_df = gr.Dataframe( + datatype=["str", "number"], label="TASK 1 BREAKDOWN" + ) + with gr.Column(variant="Panel"): + all_region_task2_df = gr.Dataframe( + datatype=["str", "number"], label="TASK 2 BREAKDOWN" + ) + + with gr.TabItem("Language Analytics") as language_analytics: + with gr.TabItem("Overall Task Submissions") as language_tabular_view: + with gr.Row(): + with gr.Column(): + language_choice = gr.Dropdown( + choices=MT5_LANGUAGE_LIST, + value="All Languages", + label="Filter by Language", + ) + + with gr.Column(variant="Panel"): + region_filter = gr.Dropdown( + choices=[ + "ALL REGIONS", + ASIA_REGION, + EUROPE_REGION, + AFRICA_REGION, + LATAM_REGION, + OTHERS_REGION, + "ADDITIONAL" + ], + value="ALL REGIONS", + label="Filter by Region", + ) + with gr.Row(): + with gr.Column(variant="Panel"): + task1_df = gr.Dataframe( + datatype=["str", "number"], + label="TASK 1 BREAKDOWN", + interactive=False, + ) + with gr.Column(variant="Panel"): + task2_df = gr.Dataframe( + datatype=["str", "number"], + label="TASK 2 BREAKDOWN", + interactive=False, + ) + with gr.Row(): + with gr.Column(): + task1_sum = gr.Number(label="Total no. of submissions for Task 1") + task1_sum_btn = gr.Button(value="Compute Total Submissions") + + with gr.Column(): + task2_sum = gr.Number(label="Total no. of submissions for Task 2") + task2_sum_btn = gr.Button(value="Compute Total Submissions") + + with gr.TabItem("Contributor Submissions") as user_submissions: + with gr.Row(): + language_filter = gr.Dropdown( + choices=DB_LANGUAGE_LIST, + value="English", + label="Choose Language Filter", + ) + with gr.Row(): + with gr.Column(variant="panel"): + gr.Textbox(value="Task 1: Contributor Submissions", show_label=False) + with gr.Column(variant="compact"): + audited_generated_task_df = gr.Dataframe( + datatype=["str", "number"], + label="No. of submissions made by users who audited generated tasks", + ) + with gr.Column(variant="compact"): + audited_contributed_task_df = gr.Dataframe( + datatype=["str", "number"], + label="No. of submissions made by users who audited contributed tasks", + ) + + with gr.Column(variant="compact"): + gr.Textbox(value="Task 2: Contributor Submissions", show_label=False) + contributed_new_task_df = gr.Dataframe( + datatype=["str", "number"], + label="No. of submissions made by users who contributed new tasks", + ) + + + with gr.TabItem("Growth Analytics") as growth_analytics: + with gr.Row(): + with gr.Column(): + gr.Textbox(placeholder="Enter the dates between which you want to calculate growth in submissions", interactive=False, show_label=False) + + with gr.Row(): + with gr.Column(): + gr.Textbox(placeholder="Start Date", interactive=False, show_label=False) + with gr.Row(): + start_date_day = gr.Dropdown(choices=[day for day in range(1, 32)], label="Day", value='28') + start_date_month = gr.Dropdown(choices=[month for month in range(1, 13)], label="Month", value='11') + start_date_year = gr.Textbox(label="Year", value=2023) + + with gr.Row(): + with gr.Column(): + gr.Textbox(placeholder="End Date", interactive=False, show_label=False) + with gr.Row(): + end_date_day = gr.Dropdown(choices=[day for day in range(1, 32)], label="Day", value=str(today_date.day)) + end_date_month = gr.Dropdown(choices=[month for month in range(1, 13)], label="Month", value=str(today_date.month)) + end_date_year = gr.Textbox(label="Year", value=str(today_date.year)) + + + with gr.Row(): + + with gr.Column(): + task_choice = gr.Dropdown( + choices=[TASK_1_NAME, TASK_2_NAME], + value=TASK_1_NAME, + label="Choose Task", + ) + + with gr.Column(): + lang_choice = gr.Dropdown( + choices=MT5_LANGUAGE_LIST, + value="All Languages", + label="Filter by Language", + ) + + with gr.Column(variant="Panel"): + region_filter_2 = gr.Dropdown( + choices=[ + "ALL REGIONS", + ASIA_REGION, + EUROPE_REGION, + AFRICA_REGION, + LATAM_REGION, + OTHERS_REGION, + "ADDITIONAL" + ], + value="ALL REGIONS", + label="Filter by Region", + ) + with gr.Column(variant="panel"): + compute_lang_growth = gr.Button(show_label=False, value="Calculate Growth") + + with gr.Row(): + with gr.Column(variant="Panel"): + task_growth_df = gr.Dataframe( + datatype=["str", "number"], + label="GROWTH ACROSS LANGUAGES", + interactive=False, + ) + + with gr.Row(): + with gr.Column(): + start_date_sum = gr.Number(label="Total no. of submissions on Start Date") + with gr.Column(): + end_date_sum = gr.Number(label="Total no. of submissions on End Date") + with gr.Column(): + total_growth_percent = gr.Number(label="Total Growth Percentage") + with gr.Column(): + total_growth_btn = gr.Button(value="Calculate Total Growth") + + + with gr.TabItem("Overall Analytics") as overall_analytics: + with gr.TabItem("Graphical View"): + with gr.Column(): + with gr.Column(): + lang_plot_type = gr.Dropdown( + choices=[ + TOP_LANG_PLOT, + TOP_CONTRIBUTOR_PLOT, + TOP_LANG_CONTRIBUTOR_PLOT, + ], + value=TOP_LANG_PLOT, + label="Choose Type of Leaderboard", + ) + with gr.Row(): + with gr.Column(): + overall_analytics_task1_plot = gr.BarPlot( + show_label=False + ).style(container=True) + with gr.Column(): + overall_analytics_task2_plot = gr.BarPlot( + show_label=False + ).style(container=True) + + with gr.TabItem("Tabular View") as overall_analytics_tab_view: + with gr.Column(): + with gr.Column(): + leaderboard_type = gr.Dropdown( + choices=[ + TOP_LANG_PLOT, + TOP_CONTRIBUTOR_PLOT, + TOP_LANG_CONTRIBUTOR_PLOT, + ], + value=TOP_LANG_PLOT, + label="Choose Type of Leaderboard", + ) + with gr.Row(): + with gr.Column(variant="Panel"): + overall_analytics_task1_df = gr.Dataframe( + datatype=["str", "number"], label="TASK 1 BREAKDOWN" + ) + with gr.Column(variant="Panel"): + overall_analytics_task2_df = gr.Dataframe( + datatype=["str", "number"], label="TASK 2 BREAKDOWN" + ) + + with gr.TabItem("General Stats") as general_stats: + with gr.TabItem("User & Task Stats") as user_task_view: + with gr.Row(): + total_users = gr.Number(label="Total no. of registered users") + + with gr.Row(variant="panel"): + with gr.Column(): + task_1_users = gr.Number( + label="Total no. of users contributing for Task 1" + ) + with gr.Column(): + task_1_generated_users = gr.Number( + label="Total no. of users who audited generated tasks for Task 1" + ) + task_1_contributed_users = gr.Number( + label="Total no. of users who audited contributed tasks for Task 1" + ) + with gr.Row(): + task_2_users = gr.Number( + label="Total no. of users contributing for Task 2" + ) + + with gr.Row(variant="panel"): + with gr.Column(): + audited_tasks = gr.Number( + label="Total no. of submissions for Task 1" + ) + with gr.Column(): + total_audited_gen = gr.Number( + label="Total no. of audited generated tasks for Task 1" + ) + total_audited_con = gr.Number( + label="Total no. of audited contributed tasks for Task 1" + ) + with gr.Row(): + contributed_tasks = gr.Number( + label="Total no. of submissions for Task 2" + ) + + # On app load + demo.load(fn=create_barplot_for_regional_data, outputs=[plot, plot2]) + + # Regional Analytics + regional_analytics_graph_view.select( + create_barplot_for_regional_data, outputs=[plot, plot2] + ) + regional_analytics_tabular_view.select( + get_all_region_data, outputs=[all_region_task1_df, all_region_task2_df] + ) + + # Language Analytics + language_analytics.select( + get_region_based_task_analytics, + inputs=[region_filter, language_choice], + outputs=[task1_df, task2_df, task1_sum, task2_sum], + ) + + # Task submissions view + language_tabular_view.select( + get_region_based_task_analytics, + inputs=[region_filter, language_choice], + outputs=[task1_df, task2_df, task1_sum, task2_sum], + ) + language_choice.change( + get_region_based_task_analytics, + inputs=[region_filter, language_choice], + outputs=[task1_df, task2_df, task1_sum, task2_sum], + ) + region_filter.change( + get_region_based_task_analytics, + inputs=[region_filter, language_choice], + outputs=[task1_df, task2_df, task1_sum, task2_sum], + ) + task1_sum_btn.click( + compute_task_submissions, inputs=[task1_df], outputs=[task1_sum] + ) + task2_sum_btn.click( + compute_task_submissions, inputs=[task2_df], outputs=[task2_sum] + ) + + # User submissions view + language_filter.change( + filter_user_submissions, + inputs=[language_filter], + outputs=[ + audited_generated_task_df, + audited_contributed_task_df, + contributed_new_task_df, + ], + ) + user_submissions.select( + filter_user_submissions, + inputs=[language_filter], + outputs=[ + audited_generated_task_df, + audited_contributed_task_df, + contributed_new_task_df, + ], + ) + + #Growth Analytics + + compute_lang_growth.click( + calculate_growth, inputs=[start_date_day, start_date_month, start_date_year, end_date_day, end_date_month, end_date_year, + task_choice, region_filter_2, lang_choice], outputs=[task_growth_df] + ) + + total_growth_btn.click( + compute_total_growth, inputs=[task_growth_df], outputs=[start_date_sum, end_date_sum, total_growth_percent] + ) + + # Overall Analytics + overall_analytics.select( + create_overall_performance_plots, + inputs=lang_plot_type, + outputs=[overall_analytics_task1_plot, overall_analytics_task2_plot], + ) + # Overall Analytics - Graphical View + lang_plot_type.change( + create_overall_performance_plots, + inputs=lang_plot_type, + outputs=[overall_analytics_task1_plot, overall_analytics_task2_plot], + ) + + # Overall Analytics - Tabular View + overall_analytics_tab_view.select( + get_overall_task_analytics, + inputs=leaderboard_type, + outputs=[overall_analytics_task1_df, overall_analytics_task2_df], + ) + leaderboard_type.change( + get_overall_task_analytics, + inputs=leaderboard_type, + outputs=[overall_analytics_task1_df, overall_analytics_task2_df], + ) + + # General Stats + user_task_view.select( + compute_general_stats, + outputs=[ + total_users, + task_1_users, + task_1_contributed_users, + task_1_generated_users, + task_2_users, + audited_tasks, + total_audited_gen, + total_audited_con, + contributed_tasks, + ], + ) + general_stats.select( + compute_general_stats, + outputs=[ + total_users, + task_1_users, + task_1_contributed_users, + task_1_generated_users, + task_2_users, + audited_tasks, + total_audited_gen, + total_audited_con, + contributed_tasks, + ], + ) + + +if __name__ == "__main__": + demo.launch( + server_name=gradio_server_name, server_port=int(gradio_server_port), ssl_verify=False + ) diff --git a/analytics/app/app.yaml b/analytics/app/app.yaml new file mode 100644 index 0000000..0996c11 --- /dev/null +++ b/analytics/app/app.yaml @@ -0,0 +1,13 @@ +runtime: python311 + +env_variables: + INSTANCE_CONNECTION_NAME: "AYA_READ_REPLICA_DB_CONN_NAME" + DB_USER: "AYA_READ_REPLICA_DB_USER" + DB_PASS: "AYA_READ_REPLICA_DB_PASS" + DB_NAME: "AYA_READ_REPLICA_DB_NAME" + C4AI_PROJECT_ID: "" #Add C4AI GCP Project ID here before app deployment + GRADIO_SERVER_NAME: "0.0.0.0" + GRADIO_SERVER_PORT: 8080 + APP_ENVIRONMENT: "prod" + +entrypoint: python app.py \ No newline at end of file diff --git a/analytics/app/constants.py b/analytics/app/constants.py new file mode 100644 index 0000000..7dc7419 --- /dev/null +++ b/analytics/app/constants.py @@ -0,0 +1,378 @@ +ENV_FILE_PATH = "../.env.analytics-instruct-multilingual-app.local" +PRODUCTION_MODE = "prod" +LOCAL_MODE = "local" + +TASK_1_NAME = "Rate Model Performance" +TASK_2_NAME = "Contribute Your Language" + +NUM_SUBMISSIONS_COL_NAME = "NUMBER OF UNIQUE SUBMISSIONS" +ALL_LANG_COL_NAME = "LANGUAGE" +ALL_REGION_COL_NAME = "REGION" + +ALL_REGIONS_PLOT = "Overall Regional Leaderboard" + +TOP_LANG_PLOT = "Overall Top 10 Languages" +TOP_CONTRIBUTOR_PLOT = "Overall Top 10 Contributors" +TOP_LANG_CONTRIBUTOR_PLOT = "Overall Top 10 Languages with most Contributors" + +AFRICA_REGION = "AFRICA" +ASIA_REGION = "ASIA" +EUROPE_REGION = "EUROPE" +LATAM_REGION = "LATIN AMERICA" +OTHERS_REGION = "OTHERS" + +LANG_NAME_ARABIC = "Arabic" +LANG_NAME_CHINESE = "Chinese" + +LANGUAGE_REGION_DICT = { + "AFRICA": [ + "Standard Arabic (Africa)", + "Egyptian Arabic", + "Tunisian Arabic", + "Moroccan Arabic", + "Plateau Malagasy", + "Shona", + "Yoruba", + "Zulu", + "Afrikaans", + "Amharic", + "Nyanja", + "Hausa", + "Igbo", + "Somali", + "Southern Sotho", + "Northern Sotho", + "Swahili (individual language)", + "Xhosa", + "Wolof", + ], + "ASIA": [ + "Mesopotamian Arabic", + "Ta'izzi-Adeni Arabic", + "South Levantine Arabic", + "North Levantine Arabic", + "Standard Arabic (Asia)", + "Najdi Arabic", + "Hebrew", + "Burmese", + "Simplified Chinese", + "Traditional Chinese", + "Hmong", + "Indonesian", + "Kazakh", + "Northern Kurdish", + "Central Kurdish", + "Kyrgyz", + "Lao", + "Standard Malay", + "Nepali (individual language)", + "Panjabi", + "Sundanese", + "Thai", + "Urdu", + "Northern Uzbek", + "Vietnamese", + "Armenian", + "South Azerbaijani", + "North Azerbaijani", + "Bengali", + "Filipino", + "Hindi", + "Japanese", + "Javanese", + "Tamil", + "Cebuano", + "Gujarati", + "Kannada", + "Central Khmer", + "Korean", + "Malayalam", + "Marathi", + "Halh Mongolian", + "Southern Pashto", + "Iranian Persian", + "Sindhi", + "Sinhala", + "Tajik", + "Telugu", + "Turkish", + ], + "EUROPE": [ + "Eastern Yiddish", + "Tosk Albanian", + "Basque", + "Belarusian", + "Bulgarian", + "Catalan", + "Corsican", + "Czech", + "Danish", + "Dutch", + "Esperanto", + "Estonian", + "Finnish", + "French", + "Galician", + "Georgian", + "German", + "Modern Greek (1453-)", + "Latin", + "Luxembourgish", + "Hungarian", + "Icelandic", + "Irish", + "Italian", + "Standard Latvian", + "Lithuanian", + "Macedonian", + "Maltese", + "Norwegian Nynorsk", + "Norwegian Bokmål", + "Polish", + "Romanian", + "Scottish Gaelic", + "Serbian", + "Slovak", + "Slovenian", + "Swedish", + "Ukrainian", + "Welsh", + "Western Frisian", + "Russian", + "Portuguese (Europe)", + "Spanish (Europe)", + ], + "LATIN AMERICA": [ + "Portuguese (LatAm)", + "Spanish (LatAm)", + "Haitian", + "Hawaiian", + ], + "OTHERS": ["English","Maori","Samoan"], +} + + +DB_LANGUAGE_LIST = [ + "Standard Arabic", + "Egyptian Arabic", + "Tunisian Arabic", + "Moroccan Arabic", + "Plateau Malagasy", + "Shona", + "Yoruba", + "Zulu", + "Afrikaans", + "Amharic", + "Nyanja", + "Hausa", + "Igbo", + "Somali", + "Southern Sotho", + "Northern Sotho", + "Swahili (individual language)", + "Xhosa", + "Wolof", + "Mesopotamian Arabic", + "Ta'izzi-Adeni Arabic", + "South Levantine Arabic", + "North Levantine Arabic", + "Najdi Arabic", + "Hebrew", + "Burmese", + "Simplified Chinese", + "Traditional Chinese", + "Hmong", + "Indonesian", + "Kazakh", + "Northern Kurdish", + "Central Kurdish", + "Kyrgyz", + "Lao", + "Standard Malay", + "Nepali (individual language)", + "Panjabi", + "Sundanese", + "Thai", + "Urdu", + "Northern Uzbek", + "Vietnamese", + "Armenian", + "South Azerbaijani", + "North Azerbaijani", + "Bengali", + "Filipino", + "Hindi", + "Japanese", + "Javanese", + "Tamil", + "Cebuano", + "Gujarati", + "Kannada", + "Central Khmer", + "Korean", + "Malayalam", + "Marathi", + "Halh Mongolian", + "Southern Pashto", + "Iranian Persian", + "Sindhi", + "Sinhala", + "Tajik", + "Telugu", + "Turkish", + "Eastern Yiddish", + "Tosk Albanian", + "Basque", + "Belarusian", + "Bulgarian", + "Catalan", + "Corsican", + "Czech", + "Danish", + "Dutch", + "Esperanto", + "Estonian", + "Finnish", + "French", + "Galician", + "Georgian", + "German", + "Modern Greek (1453-)", + "Latin", + "Luxembourgish", + "Hungarian", + "Icelandic", + "Irish", + "Italian", + "Standard Latvian", + "Lithuanian", + "Macedonian", + "Maltese", + "Norwegian Nynorsk", + "Norwegian Bokmål", + "Polish", + "Romanian", + "Scottish Gaelic", + "Serbian", + "Slovak", + "Slovenian", + "Swedish", + "Ukrainian", + "Welsh", + "Western Frisian", + "Russian", + "Portuguese", + "Spanish", + "Haitian", + "Hawaiian", + "English", + "Maori", + "Samoan" +] + + +MT5_LANGUAGE_LIST = [ + "All Languages", + "Arabic", + "Malagasy", + "Shona", + "Yoruba", + "Zulu", + "Afrikaans", + "Amharic", + "Nyanja", + "Hausa", + "Igbo", + "Somali", + "Sotho", + "Swahili", + "Xhosa", + "Wolof", + "Hebrew", + "Burmese", + "Chinese", + "Hmong", + "Indonesian", + "Kazakh", + "Kurdish", + "Kyrgyz", + "Lao", + "Malay", + "Nepali", + "Panjabi", + "Sundanese", + "Thai", + "Urdu", + "Uzbek", + "Vietnamese", + "Armenian", + "Azerbaijani", + "Bengali", + "Filipino", + "Hindi", + "Japanese", + "Javanese", + "Tamil", + "Cebuano", + "Gujarati", + "Kannada", + "Khmer", + "Korean", + "Malayalam", + "Marathi", + "Mongolian", + "Pashto", + "Persian", + "Sindhi", + "Sinhala", + "Tajik", + "Telugu", + "Turkish", + "Yiddish", + "Tosk Albanian", + "Basque", + "Belarusian", + "Bulgarian", + "Catalan", + "Corsican", + "Czech", + "Danish", + "Dutch", + "Esperanto", + "Estonian", + "Finnish", + "French", + "Galician", + "Georgian", + "German", + "Greek", + "Latin", + "Luxembourgish", + "Hungarian", + "Icelandic", + "Irish", + "Italian", + "Standard Latvian", + "Lithuanian", + "Macedonian", + "Maltese", + "Norwegian Nynorsk", + "Norwegian Bokmål", + "Polish", + "Romanian", + "Scottish Gaelic", + "Serbian", + "Slovak", + "Slovenian", + "Swedish", + "Ukrainian", + "Welsh", + "Western Frisian", + "Russian", + "Portuguese", + "Spanish", + "Haitian", + "Hawaiian", + "Maori", + "Samoan" +] diff --git a/analytics/app/logo/aya_logo.png b/analytics/app/logo/aya_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..661d31ca1732f07fc3082d8ff16c040212cd513b GIT binary patch literal 49376 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

ngX9sGV_zJs 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 z%0D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 str: + """Get the value of a secret based on secret id & version id stored in GCP Secrets Manager. + + Args: + secret_id (str): Name of the secret or ID in GCP Secrets Manager + version_id (str, optional): The version number of the secret. Defaults to "latest". + + Returns: + str: Return the value of the secret corresponding to the given secret id. + """ + + # define name of the secret + name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}" + # access the value of the given `secret version` + response = gcp_secret_mgr_client.access_secret_version(name=name) + # decode value of the response of `client.access_secret_version` + secret_value = response.payload.data.decode("UTF-8") + return secret_value + + +def connect_to_db_with_connector() -> sa.engine.base.Engine: + """ + Initializes a connection pool for a Cloud SQL instance of Postgres. + + Uses the Cloud SQL Python Connector package. + + Returns: + sa.engine.base.Engine : SQLAlchemy engine object using which we connect to the Cloud SQL DB + """ + + if app_environment == PRODUCTION_MODE: + # get environment variable values from GCP Secret Manager + db_user = get_secret_value(secret_id=os.environ["DB_USER"]) + db_pass = get_secret_value(secret_id=os.environ["DB_PASS"]) + db_name = get_secret_value(secret_id=os.environ["DB_NAME"], version_id="1") + instance_connection_name = get_secret_value( + secret_id=os.environ["INSTANCE_CONNECTION_NAME"] + ) + else: + print("load local environment variables") + db_user = os.environ["DB_USER"] + db_pass = os.environ["DB_PASS"] + db_name = os.environ["DB_NAME"] + instance_connection_name = os.environ["INSTANCE_CONNECTION_NAME"] + + ip_type = IPTypes.PUBLIC + + # initialize Cloud SQL Python Connector object + connector = Connector() + + # use Cloud SQL Python Connector object to connect to Cloud SQL instance + def getconn() -> pg8000.dbapi.Connection: + conn: pg8000.dbapi.Connection = connector.connect( + instance_connection_name, + "pg8000", + user=db_user, + password=db_pass, + db=db_name, + ip_type=ip_type, + ) + return conn + + # create engine for connecting to PostgreSQL DB using `pg8000` driver + pool = sa.create_engine( + "postgresql+pg8000://", + creator=getconn, + ) + return pool + + +def convert_country_to_continent(country_name: str) -> str: + """Get the continent name based on the given `country_name` + + Args: + country_name (str): The name of the country of a user + + Returns: + str: The name of the continent of the user + """ + country_alpha2 = pc.country_name_to_country_alpha2(country_name) + country_continent_code = pc.country_alpha2_to_continent_code(country_alpha2) + country_continent_name = pc.convert_continent_code_to_continent_name( + country_continent_code + ) + return country_continent_name diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..e04276f --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,2 @@ +.idea +venv diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..ce4d019 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,36 @@ +ENVIRONMENT=local + +DBUSER=backendapp +DBPASSWORD=password +DBHOSTNAME=postgresdb +DBPORT=5432 +DBNAME=instruct_multilingual +INSTRUCT_MULTILINGUAL_APP_DB_URI=postgresql+psycopg2://${DBUSER}:${DBPASSWORD}@${DBHOSTNAME}:${DBPORT}/${DBNAME} + +JWT_SECRET="SECRET_KEY" +JWT_ALGORITHM=HS256 +JWT_EXPIRATION_TIME=86400 + +DISCORD_API_BASE_URL=https://discord.com/api/v9 +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= +DISCORD_REDIRECT_URI=http://localhost:8080/api/v1/auth/discord/callback +DISCORD_WEBHOOK_URL= + +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GOOGLE_REDIRECT_URI=http://localhost:8080/api/v1/auth/google/callback + +FRONTEND_SCHEME=http +FRONTEND_HOST=localhost +FRONTEND_PORT=80 +FRONTEND_URL=${FRONTEND_SCHEME}://${FRONTEND_HOST}:${FRONTEND_PORT} +FOR_AI_URL=http://localhost:4000 + +APP_NAME=instruct-multilingual +RUN_MIGRATIONS_ON_STARTUP=true + +# for local development only to connect to the dockerized postgres instance +POSTGRES_DB=${DBNAME} +POSTGRES_USER=${DBUSER} +POSTGRES_PASSWORD=${DBPASSWORD} diff --git a/backend/.env.test b/backend/.env.test new file mode 100644 index 0000000..e09cbb1 --- /dev/null +++ b/backend/.env.test @@ -0,0 +1,33 @@ +ENVIRONMENT=local + +DBUSER=backendapp +DBPASSWORD=password +DBHOSTNAME=localhost +DBPORT=5432 +DBNAME=testdb +INSTRUCT_MULTILINGUAL_APP_DB_URI=postgresql+psycopg2://${DBUSER}:${DBPASSWORD}@${DBHOSTNAME}:${DBPORT}/${DBNAME} + +JWT_SECRET="SECRET_KEY" +JWT_ALGORITHM=HS256 +JWT_EXPIRATION_TIME=86400 + +DISCORD_API_BASE_URL=https://discord.com/api/v9 +DISCORD_CLIENT_ID=blah +DISCORD_CLIENT_SECRET=blah +DISCORD_REDIRECT_URI=http://backend:8080/api/v1/auth/callback/ +DISCORD_SCOPES=identify guilds email + +GOOGLE_CLIENT_ID=blah +GOOGLE_CLIENT_SECRET=blah +GOOGLE_REDIRECT_URI=http://localhost:8080/api/v1/auth/google/callback + +FRONTEND_URL=http://localhost:4000 +FOR_AI_URL=http://localhost:4000 + +APP_NAME=test-app-instruct-multilingual +RUN_MIGRATIONS_ON_STARTUP=true + +# for local development only to connect to the dockerized postgres instance +POSTGRES_DB=${DBNAME} +POSTGRES_USER=${DBUSER} +POSTGRES_PASSWORD=${DBPASSWORD} \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..774e8f1 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,22 @@ +# Start with the official Python 3.10 image +FROM python:3.10-slim-bullseye + +# Update and install dependencies +RUN apt-get update && apt-get install -y gcc + +# Set the working directory to /app +WORKDIR /backend + +# Copy the backend code into the container +COPY ./ /backend/ + +# Install the dependencies using poetry +RUN pip install poetry +RUN poetry config virtualenvs.create false +RUN poetry install + +# Expose +EXPOSE 8080 + +# Start the application +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 0000000..dcdd1e0 --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,107 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d%%(minute).2d-%%(rev)s-%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +timezone = UTC + +# max length of characters to apply to the +# "slug" field +truncate_slug_length = 50 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/alembic/README b/backend/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/backend/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/backend/alembic/env.py b/backend/alembic/env.py new file mode 100644 index 0000000..b0758f6 --- /dev/null +++ b/backend/alembic/env.py @@ -0,0 +1,123 @@ +import os + +from pathlib import Path +from logging.config import fileConfig + +import sqlalchemy as sa + +from dotenv import load_dotenv +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +ENVIRONMENT = os.environ.get("ENVIRONMENT") + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +if config.attributes.get('configure_logger', True): + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +valid_environments = [ + "test", + "local", + "staging", + "production", +] +if not any([ENVIRONMENT == env for env in valid_environments]): + raise ValueError( + f"Invalid environment set: {ENVIRONMENT}. " + f"Valid environments are: {', '.join(valid_environments)}" + ) + +dotenv_filepath = Path(__file__).parent.parent / f".env.{ENVIRONMENT}" +load_dotenv(dotenv_filepath) + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + environment = os.environ["ENVIRONMENT"] + + if environment == "production": + print("Running migrations in PRODUCTION mode. Ensure this operation is correct.") + elif environment == "staging": + print("Running migrations in staging mode. Ensure this operation is correct.") + elif environment == "local": + print("Running migrations in local mode.") + + db_uri = os.environ["INSTRUCT_MULTILINGUAL_APP_DB_URI"] + + context.configure( + url=db_uri, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + if ENVIRONMENT == "production": + print("Running migrations in PRODUCTION mode. Ensure this operation is correct.") + elif ENVIRONMENT == "local": + print("Running migrations in local mode.") + elif ENVIRONMENT == "test": + print("Running migrations in test mode.") + + db_uri = os.environ["INSTRUCT_MULTILINGUAL_APP_DB_URI"] + + if db_uri is None: + raise ValueError( + "No database URI set. Check your .env.local or .env.prod file." + ) + + connectable = sa.create_engine( + db_uri, + client_encoding="utf8", + pool_size=1, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako new file mode 100644 index 0000000..55df286 --- /dev/null +++ b/backend/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/backend/alembic/versions/2023-03-28-0330-cb5b5832ab95-create_initial_schema.py b/backend/alembic/versions/2023-03-28-0330-cb5b5832ab95-create_initial_schema.py new file mode 100644 index 0000000..ec97e8c --- /dev/null +++ b/backend/alembic/versions/2023-03-28-0330-cb5b5832ab95-create_initial_schema.py @@ -0,0 +1,132 @@ +"""create_initial_schema + +Revision ID: cb5b5832ab95 +Revises: +Create Date: 2023-03-28 03:30:04.921955+00:00 + +""" +import uuid + +from alembic import op + +import sqlalchemy as sa + +from sqlalchemy.dialects.postgresql import UUID + + +revision = 'cb5b5832ab95' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + 'language_code', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('code', sa.VARCHAR(2), nullable=False, unique=True), + sa.Column('name', sa.Text, nullable=False, unique=True), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + op.create_table( + 'country_code', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('code', sa.VARCHAR(2), nullable=False, unique=True), + sa.Column('name', sa.Text, nullable=False, unique=True), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + op.create_table( + 'dataset', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('name', sa.VARCHAR(64), nullable=False, unique=True), + sa.Column('language_id', UUID(as_uuid=True), sa.ForeignKey('language_code.id'), nullable=False), + sa.Column('translated', sa.Boolean, nullable=False), + sa.Column('templated', sa.Boolean, nullable=False), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + op.create_table( + 'task', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('prompt', sa.Text, nullable=False, unique=True), + sa.Column('completion', sa.Text, nullable=False, unique=True), + sa.Column('task_type', sa.Enum('audit_translation', 'audit_xp3', name='task_type'), nullable=False), + sa.Column('dataset_id', UUID(as_uuid=True), sa.ForeignKey('dataset.id'), nullable=False), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + op.create_table( + 'user', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('username', sa.VARCHAR(64), nullable=False, unique=True), + sa.Column('image_url', sa.VARCHAR(256), nullable=False), + sa.Column('country_code', UUID(as_uuid=True), sa.ForeignKey('country_code.id'), nullable=True), + sa.Column('language_codes', sa.ARRAY(UUID(as_uuid=True)), nullable=True), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + op.create_table( + 'task_submission', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('task_id', UUID(as_uuid=True), sa.ForeignKey('task.id'), nullable=False), + sa.Column('submitted_by', UUID(as_uuid=True), sa.ForeignKey('user.id'), nullable=False), + sa.Column('submitted_prompt', sa.Text, nullable=False), + sa.Column('submitted_completion', sa.Text, nullable=False), + sa.Column('prompt_edited', sa.Boolean, nullable=False), + sa.Column('completion_edited', sa.Boolean, nullable=False), + sa.Column('prompt_rating', sa.Integer, nullable=True), + sa.Column('completion_rating', sa.Integer, nullable=True), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + op.create_table( + 'leaderboard_daily', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('username', sa.VARCHAR(64), nullable=False), + sa.Column('points', sa.Integer, nullable=False), + sa.Column('day', sa.Date, nullable=False), + ) + + op.create_table( + 'leaderboard_weekly', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('username', sa.VARCHAR(64), nullable=False), + sa.Column('points', sa.Integer, nullable=False), + sa.Column('week_of', sa.Date, nullable=False), + ) + + op.create_table( + 'leaderboard_by_language', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('username', sa.VARCHAR(64), nullable=False), + sa.Column('points', sa.Integer, nullable=False), + sa.Column('language', sa.Text, nullable=False), + ) + + op.create_table( + 'leaderboard_overall', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('username', sa.VARCHAR(64), nullable=False), + sa.Column('points', sa.Integer, nullable=False), + ) + + + +def downgrade() -> None: + op.drop_table('leaderboard_daily') + op.drop_table('leaderboard_weekly') + op.drop_table('leaderboard_by_language') + op.drop_table('leaderboard_overall') + + op.drop_table('task_submission') + + op.drop_table('user') + op.drop_table('task') + + op.drop_table('country_code') + op.drop_table('dataset') + op.drop_table('language_code') + + op.execute('DROP TYPE task_type;') \ No newline at end of file diff --git a/backend/alembic/versions/2023-03-31-1747-8518c1ce4ee0-modify_dataset_name_to_text.py b/backend/alembic/versions/2023-03-31-1747-8518c1ce4ee0-modify_dataset_name_to_text.py new file mode 100644 index 0000000..d7581e6 --- /dev/null +++ b/backend/alembic/versions/2023-03-31-1747-8518c1ce4ee0-modify_dataset_name_to_text.py @@ -0,0 +1,24 @@ +"""modify_dataset_name_to_text + +Revision ID: 8518c1ce4ee0 +Revises: cb5b5832ab95 +Create Date: 2023-03-31 17:47:52.767578+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8518c1ce4ee0' +down_revision = 'cb5b5832ab95' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute("ALTER TABLE dataset ALTER COLUMN name TYPE TEXT") + + +def downgrade() -> None: + op.execute("ALTER TABLE dataset ALTER COLUMN name TYPE VARCHAR(64)") diff --git a/backend/alembic/versions/2023-03-31-1755-48082df0bbba-modify_language_code.py b/backend/alembic/versions/2023-03-31-1755-48082df0bbba-modify_language_code.py new file mode 100644 index 0000000..2c6fa33 --- /dev/null +++ b/backend/alembic/versions/2023-03-31-1755-48082df0bbba-modify_language_code.py @@ -0,0 +1,43 @@ +"""modify_language_code + +Revision ID: 48082df0bbba +Revises: 8518c1ce4ee0 +Create Date: 2023-03-31 17:55:13.258231+00:00 + +""" +from alembic import op +import pycountry +import sqlalchemy as sa + + + +# revision identifiers, used by Alembic. +revision = '48082df0bbba' +down_revision = '8518c1ce4ee0' +branch_labels = None +depends_on = None + +def upgrade() -> None: + op.execute("ALTER TABLE language_code ALTER COLUMN code TYPE VARCHAR(3)") + + # drop the unique constraint on the code and name columns individually + op.drop_constraint('language_code_code_key', 'language_code', type_='unique') + op.drop_constraint('language_code_name_key', 'language_code', type_='unique') + + # add character_code column for characters like 'Latn' or 'Cyrl' + op.add_column( + 'language_code', + sa.Column( + 'character_code', + sa.VARCHAR(5), + nullable=False, + ) + ) + + +def downgrade() -> None: + op.execute("ALTER TABLE language_code ALTER COLUMN code TYPE VARCHAR(2)") + op.drop_column('language_code', 'character_code') + op.create_unique_constraint('language_code_code_key', 'language_code', ['code']) + op.create_unique_constraint('language_code_name_key', 'language_code', ['name']) + diff --git a/backend/alembic/versions/2023-04-01-2050-5cbe4ab55392-add_language_codes_bulk.py b/backend/alembic/versions/2023-04-01-2050-5cbe4ab55392-add_language_codes_bulk.py new file mode 100644 index 0000000..d0d0101 --- /dev/null +++ b/backend/alembic/versions/2023-04-01-2050-5cbe4ab55392-add_language_codes_bulk.py @@ -0,0 +1,205 @@ +"""add_language_codes_bulk + +Revision ID: 5cbe4ab55392 +Revises: 48082df0bbba +Create Date: 2023-04-01 20:50:55.964835+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5cbe4ab55392' +down_revision = '48082df0bbba' +branch_labels = None +depends_on = None + +# initial set of 101 languages +desired_languages_with_codes = [ + ('ace', 'Arab', 'Achinese'), + ('ace', 'Latn', 'Achinese'), + ('acm', 'Arab', 'Mesopotamian Arabic'), + ('acq', 'Arab', "Ta'izzi-Adeni Arabic"), + ('aeb', 'Arab', 'Tunisian Arabic'), + ('afr', 'Latn', 'Afrikaans'), + ('ajp', 'Arab', 'South Levantine Arabic'), + ('aka', 'Latn', 'Akan'), + ('amh', 'Ethi', 'Amharic'), + ('apc', 'Arab', 'North Levantine Arabic'), + ('arb', 'Arab', 'Standard Arabic'), + ('arb', 'Latn', 'Standard Arabic'), + ('ars', 'Arab', 'Najdi Arabic'), + ('ary', 'Arab', 'Moroccan Arabic'), + ('arz', 'Arab', 'Egyptian Arabic'), + ('asm', 'Beng', 'Assamese'), + ('ast', 'Latn', 'Asturian'), + ('awa', 'Deva', 'Awadhi'), + ('ayr', 'Latn', 'Central Aymara'), + ('azb', 'Arab', 'South Azerbaijani'), + ('azj', 'Latn', 'North Azerbaijani'), + ('bak', 'Cyrl', 'Bashkir'), + ('bam', 'Latn', 'Bambara'), + ('ban', 'Latn', 'Balinese'), + ('bel', 'Cyrl', 'Belarusian'), + ('bem', 'Latn', 'Bemba (Zambia)'), + ('ben', 'Beng', 'Bengali'), + ('bho', 'Deva', 'Bhojpuri'), + ('bjn', 'Arab', 'Banjar'), + ('bjn', 'Latn', 'Banjar'), + ('bod', 'Tibt', 'Tibetan'), + ('bos', 'Latn', 'Bosnian'), + ('bug', 'Latn', 'Buginese'), + ('bul', 'Cyrl', 'Bulgarian'), + ('cat', 'Latn', 'Catalan'), + ('ceb', 'Latn', 'Cebuano'), + ('ces', 'Latn', 'Czech'), + ('cjk', 'Latn', 'Chokwe'), + ('ckb', 'Arab', 'Central Kurdish'), + ('crh', 'Latn', 'Crimean Tatar'), + ('cym', 'Latn', 'Welsh'), + ('dan', 'Latn', 'Danish'), + ('deu', 'Latn', 'German'), + ('dik', 'Latn', 'Southwestern Dinka'), + ('dyu', 'Latn', 'Dyula'), + ('dzo', 'Tibt', 'Dzongkha'), + ('ell', 'Grek', 'Modern Greek (1453-)'), + ('eng', 'Latn', 'English'), + ('epo', 'Latn', 'Esperanto'), + ('est', 'Latn', 'Estonian'), + ('eus', 'Latn', 'Basque'), + ('ewe', 'Latn', 'Ewe'), + ('fao', 'Latn', 'Faroese'), + ('fij', 'Latn', 'Fijian'), + ('fin', 'Latn', 'Finnish'), + ('fon', 'Latn', 'Fon'), + ('fra', 'Latn', 'French'), + ('fur', 'Latn', 'Friulian'), + ('fuv', 'Latn', 'Nigerian Fulfulde'), + ('gaz', 'Latn', 'West Central Oromo'), + ('gla', 'Latn', 'Scottish Gaelic'), + ('gle', 'Latn', 'Irish'), + ('glg', 'Latn', 'Galician'), + ('grn', 'Latn', 'Guarani'), + ('guj', 'Gujr', 'Gujarati'), + ('hat', 'Latn', 'Haitian'), + ('hau', 'Latn', 'Hausa'), + ('heb', 'Hebr', 'Hebrew'), + ('hin', 'Deva', 'Hindi'), + ('hne', 'Deva', 'Chhattisgarhi'), + ('hrv', 'Latn', 'Croatian'), + ('hun', 'Latn', 'Hungarian'), + ('hye', 'Armn', 'Armenian'), + ('ibo', 'Latn', 'Igbo'), + ('ilo', 'Latn', 'Iloko'), + ('ind', 'Latn', 'Indonesian'), + ('isl', 'Latn', 'Icelandic'), + ('ita', 'Latn', 'Italian'), + ('jav', 'Latn', 'Javanese'), + ('jpn', 'Jpan', 'Japanese'), + ('kab', 'Latn', 'Kabyle'), + ('kac', 'Latn', 'Kachin'), + ('kam', 'Latn', 'Kamba (Kenya)'), + ('kan', 'Knda', 'Kannada'), + ('kas', 'Arab', 'Kashmiri'), + ('kas', 'Deva', 'Kashmiri'), + ('kat', 'Geor', 'Georgian'), + ('kaz', 'Cyrl', 'Kazakh'), + ('kbp', 'Latn', 'Kabiyè'), + ('kea', 'Latn', 'Kabuverdianu'), + ('khk', 'Cyrl', 'Halh Mongolian'), + ('khm', 'Khmr', 'Central Khmer'), + ('kik', 'Latn', 'Kikuyu'), + ('kin', 'Latn', 'Kinyarwanda'), + ('kir', 'Cyrl', 'Kirghiz'), + ('kmb', 'Latn', 'Kimbundu'), + ('kmr', 'Latn', 'Northern Kurdish'), + ('knc', 'Arab', 'Central Kanuri'), + ('knc', 'Latn', 'Central Kanuri'), + ('kon', 'Latn', 'Kongo'), + ('kor', 'Hang', 'Korean'), + ('lao', 'Laoo', 'Lao'), + ('lij', 'Latn', 'Ligurian'), + ('lim', 'Latn', 'Limburgan'), + ('lin', 'Latn', 'Lingala'), + ('lit', 'Latn', 'Lithuanian'), + ('lmo', 'Latn', 'Lombard'), + ('ltg', 'Latn', 'Latgalian'), + ('ltz', 'Latn', 'Luxembourgish'), + ('lua', 'Latn', 'Luba-Lulua'), + ('lug', 'Latn', 'Ganda'), + ('luo', 'Latn', 'Luo (Kenya and Tanzania)'), + ('lus', 'Latn', 'Lushai'), + ('lvs', 'Latn', 'Standard Latvian'), + ('mag', 'Deva', 'Magahi'), + ('mal', 'Mlym', 'Malayalam'), + ('mar', 'Deva', 'Marathi'), + ('mkd', 'Cyrl', 'Macedonian'), + ('mlt', 'Latn', 'Maltese'), + ('mri', 'Latn', 'Maori'), + ('mya', 'Mymr', 'Burmese'), + ('nld', 'Latn', 'Dutch'), + ('nob', 'Latn', 'Norwegian Bokmål'), + ('npi', 'Deva', 'Nepali (individual language)'), + ('nso', 'Latn', 'Pedi'), + ('nya', 'Latn', 'Nyanja'), + ('oci', 'Latn', 'Occitan (post 1500)'), + ('ory', 'Orya', 'Odia'), + ('pan', 'Guru', 'Panjabi'), + ('pbt', 'Arab', 'Southern Pashto'), + ('pes', 'Arab', 'Iranian Persian'), + ('pol', 'Latn', 'Polish'), + ('por', 'Latn', 'Portuguese'), + ('ron', 'Latn', 'Romanian'), + ('rus', 'Cyrl', 'Russian'), + ('slk', 'Latn', 'Slovak'), + ('sna', 'Latn', 'Shona'), + ('snd', 'Arab', 'Sindhi'), + ('som', 'Latn', 'Somali'), + ('spa', 'Latn', 'Spanish'), + ('srp', 'Cyrl', 'Serbian'), + ('swe', 'Latn', 'Swedish'), + ('swh', 'Latn', 'Swahili (individual language)'), + ('tam', 'Taml', 'Tamil'), + ('tel', 'Telu', 'Telugu'), + ('tgk', 'Cyrl', 'Tajik'), + ('tgl', 'Latn', 'Tagalog'), + ('tha', 'Thai', 'Thai'), + ('tur', 'Latn', 'Turkish'), + ('ukr', 'Cyrl', 'Ukrainian'), + ('umb', 'Latn', 'Umbundu'), + ('urd', 'Arab', 'Urdu'), + ('uzn', 'Latn', 'Northern Uzbek'), + ('vie', 'Latn', 'Vietnamese'), + ('wol', 'Latn', 'Wolof'), + ('xho', 'Latn', 'Xhosa'), + ('yor', 'Latn', 'Yoruba'), + ('zho', 'Hant', 'Chinese'), + ('zsm', 'Latn', 'Standard Malay'), + ('zul', 'Latn', 'Zulu'), +] + +# rewrite the above languages with codes as a dictionary with name and code keys +data_to_insert = [] +for code, character_code, name in desired_languages_with_codes: + data_to_insert.append({ + 'name': name, + 'code': code, + 'character_code': character_code, + }) + +def upgrade() -> None: + # get metadata from current connection + meta = sa.MetaData(bind=op.get_bind()) + + # pass in tuple with tables we want to reflect, otherwise whole database will get reflected + meta.reflect(only=('language_code',)) + + # define table representation + language_code_tbl = sa.Table('language_code', meta) + # insert each 3 letter code and language to the db + op.bulk_insert(language_code_tbl, data_to_insert) + + +def downgrade() -> None: + op.execute('DELETE FROM language_code') diff --git a/backend/alembic/versions/2023-04-07-0526-b5eb22ad2a73-add_task_num_and_language_id_column_for_task.py b/backend/alembic/versions/2023-04-07-0526-b5eb22ad2a73-add_task_num_and_language_id_column_for_task.py new file mode 100644 index 0000000..c07d59d --- /dev/null +++ b/backend/alembic/versions/2023-04-07-0526-b5eb22ad2a73-add_task_num_and_language_id_column_for_task.py @@ -0,0 +1,42 @@ +"""add_task_num_and_language_id_column_for_task + +Revision ID: b5eb22ad2a73 +Revises: 5cbe4ab55392 +Create Date: 2023-04-07 05:26:13.664241+00:00 + +""" +from alembic import op +import sqlalchemy as sa + +from sqlalchemy.dialects.postgresql import UUID + + +# revision identifiers, used by Alembic. +revision = 'b5eb22ad2a73' +down_revision = '5cbe4ab55392' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add a language_id column to the task table + # this column is used to keep track of the language + # of the task and is a foreign key to the language_code table + op.add_column('task', sa.Column('language_id', UUID(as_uuid=True), nullable=True)) + op.create_foreign_key('fk_task_language_id', 'task', 'language_code', ['language_id'], ['id']) + + # a new column is added to the task table + # with the name task_num and of the type identity. + # this column is used to keep track of the number of tasks + # and so we can use it to select random tasks from the database + # without having to use the random() function. + op.execute("ALTER TABLE task ADD COLUMN task_num INT GENERATED ALWAYS AS IDENTITY") + + # create an index on the task_num column for faster lookups + op.execute("CREATE INDEX IF NOT EXISTS task_num_index ON task (task_num, language_id)") + + +def downgrade() -> None: + op.execute("ALTER TABLE task DROP COLUMN task_num") + op.execute("DROP INDEX IF EXISTS task_num_language_index") + op.drop_column('task', 'language_id') diff --git a/backend/alembic/versions/2023-04-09-0404-1276d056240a-unique_constraint_prompt_completion_tasks.py b/backend/alembic/versions/2023-04-09-0404-1276d056240a-unique_constraint_prompt_completion_tasks.py new file mode 100644 index 0000000..e569544 --- /dev/null +++ b/backend/alembic/versions/2023-04-09-0404-1276d056240a-unique_constraint_prompt_completion_tasks.py @@ -0,0 +1,34 @@ +"""unique_constraint_prompt_completion_tasks + +Revision ID: 1276d056240a +Revises: b5eb22ad2a73 +Create Date: 2023-04-09 04:04:16.921687+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1276d056240a' +down_revision = 'b5eb22ad2a73' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # drop the old constraint for prompt and completion columns on the task table + op.drop_constraint('task_prompt_key', 'task', type_='unique') + op.drop_constraint('task_completion_key', 'task', type_='unique') + + # create a new constraint for prompt and completion columns on the task table + op.create_unique_constraint('uq_task_prompt_completion_key', 'task', ['prompt', 'completion']) + + +def downgrade() -> None: + # drop the new constraint for prompt and completion columns on the task table + op.drop_constraint('uq_task_prompt_completion_key', 'task', type_='unique') + + # create a new constraint for prompt and completion columns on the task table + op.create_unique_constraint('task_prompt_key', 'task', ['prompt']) + op.create_unique_constraint('task_completion_key', 'task', ['completion']) diff --git a/backend/alembic/versions/2023-04-17-0157-eb13778e0573-add_country_codes_bulk.py b/backend/alembic/versions/2023-04-17-0157-eb13778e0573-add_country_codes_bulk.py new file mode 100644 index 0000000..e7dbf4f --- /dev/null +++ b/backend/alembic/versions/2023-04-17-0157-eb13778e0573-add_country_codes_bulk.py @@ -0,0 +1,43 @@ +"""populate_country_codes + +Revision ID: eb13778e0573 +Revises: 1276d056240a +Create Date: 2023-04-17 01:57:05.638839+00:00 + +""" +from alembic import op +import sqlalchemy as sa + +import pycountry + +# revision identifiers, used by Alembic. +revision = "eb13778e0573" +down_revision = "1276d056240a" +branch_labels = None +depends_on = None + +data = [(c.alpha_2, c.name) for c in pycountry.countries] +data_to_insert = [ + { + "code": code, + "name": name, + } + for code, name in data +] + + +def upgrade() -> None: + # get metadata from current connection + meta = sa.MetaData(bind=op.get_bind()) + + # pass in tuple with tables we want to reflect, otherwise whole database will get reflected + meta.reflect(only=("country_code",)) + + # define table representation + country_code_tbl = sa.Table("country_code", meta) + + op.bulk_insert(country_code_tbl, data_to_insert) + + +def downgrade() -> None: + op.execute("DELETE FROM country_code") diff --git a/backend/alembic/versions/2023-04-24-0059-518a194fa402-unique_language_code_records.py b/backend/alembic/versions/2023-04-24-0059-518a194fa402-unique_language_code_records.py new file mode 100644 index 0000000..73b2fe6 --- /dev/null +++ b/backend/alembic/versions/2023-04-24-0059-518a194fa402-unique_language_code_records.py @@ -0,0 +1,32 @@ +"""unique_language_code_records + +Revision ID: 518a194fa402 +Revises: eb13778e0573 +Create Date: 2023-04-24 00:59:42.578691+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '518a194fa402' +down_revision = 'eb13778e0573' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ensure that the language_code table has unique records + op.create_unique_constraint( + constraint_name="unique_language_code_code_name_character_code", + table_name="language_code", + columns=["code", "name", "character_code"], + ) + + +def downgrade() -> None: + op.drop_constraint( + constraint_name="unique_language_code_code_name_character_code", + table_name="language_code", + ) diff --git a/backend/alembic/versions/2023-04-28-0209-5536f1b704eb-add_more_languages.py b/backend/alembic/versions/2023-04-28-0209-5536f1b704eb-add_more_languages.py new file mode 100644 index 0000000..60fca1b --- /dev/null +++ b/backend/alembic/versions/2023-04-28-0209-5536f1b704eb-add_more_languages.py @@ -0,0 +1,51 @@ +"""add_more_languages + +Revision ID: 5536f1b704eb +Revises: 518a194fa402 +Create Date: 2023-04-28 02:09:42.326327+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5536f1b704eb' +down_revision = '518a194fa402' +branch_labels = None +depends_on = None + +# initial set of 101 languages +desired_languages_with_codes = [ + ('haw', 'Latn', 'Hawaiian'), + ('cos', 'Latn', 'Corsican'), + ('hmn', 'Latn', 'Hmong'), + ('fry', 'Latn', 'Western Frisian'), + ('fil', 'Latn', 'Filipino'), +] + +# rewrite the above languages with codes as a dictionary with name and code keys +data_to_insert = [] +for code, character_code, name in desired_languages_with_codes: + data_to_insert.append({ + 'name': name, + 'code': code, + 'character_code': character_code, + }) + +def upgrade() -> None: + # get metadata from current connection + meta = sa.MetaData(bind=op.get_bind()) + + # pass in tuple with tables we want to reflect, otherwise whole database will get reflected + meta.reflect(only=('language_code',)) + + # define table representation + language_code_tbl = sa.Table('language_code', meta) + + # insert each 3 letter code and language to the db + op.bulk_insert(language_code_tbl, data_to_insert) + + +def downgrade() -> None: + op.execute("DELETE FROM language_code WHERE code IN ('haw', 'cos', 'hmn', 'fry', 'fil')") diff --git a/backend/alembic/versions/2023-04-30-2152-c3c4e62fadcf-add_more_missing_lang_codes.py b/backend/alembic/versions/2023-04-30-2152-c3c4e62fadcf-add_more_missing_lang_codes.py new file mode 100644 index 0000000..8965d44 --- /dev/null +++ b/backend/alembic/versions/2023-04-30-2152-c3c4e62fadcf-add_more_missing_lang_codes.py @@ -0,0 +1,60 @@ +"""add_more_missing_lang_codes + +Revision ID: c3c4e62fadcf +Revises: 5536f1b704eb +Create Date: 2023-04-30 21:52:14.611385+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c3c4e62fadcf' +down_revision = '5536f1b704eb' +branch_labels = None +depends_on = None + +desired_languages_with_codes = [ + ('sun', 'Arab', 'Sundanese'), + ('taq', 'Latn', 'Tamasheq'), + ('als', 'Latn', 'Tosk Albanian'), + ('min', 'Latn', 'Minangkabau'), + ('mni', 'Beng', 'Manipuri'), + ('nno', 'Latn', 'Norwegian Nynorsk'), + ('plt', 'Latn', 'Plateau Malagasy'), + ('sin', 'Sinh', 'Sinhala'), + ('slv', 'Latn', 'Slovenian'), + ('smo', 'Latn', 'Samoan'), + ('sot', 'Latn', 'Southern Sotho'), + ('ydd', 'Hebr', 'Eastern Yiddish'), + ('yue', 'Hant', 'Cantonese'), +] + +# rewrite the above languages with codes as a dictionary with name and code keys +data_to_insert = [] +for code, character_code, name in desired_languages_with_codes: + data_to_insert.append({ + 'name': name, + 'code': code, + 'character_code': character_code, + }) + +def upgrade() -> None: + # get metadata from current connection + meta = sa.MetaData(bind=op.get_bind()) + + # pass in tuple with tables we want to reflect, otherwise whole database will get reflected + meta.reflect(only=('language_code',)) + + # define table representation + language_code_tbl = sa.Table('language_code', meta) + + # insert each 3 letter code and language to the db + op.bulk_insert(language_code_tbl, data_to_insert) + + +def downgrade() -> None: + op.execute("DELETE FROM language_code WHERE code IN ('sun', 'taq', 'als', 'min', 'mni', 'nno', 'plt', 'sin', 'slv', 'smo', 'sot', 'ydd', 'yue')") + + diff --git a/backend/alembic/versions/2023-05-02-0319-d6e388430a44-add_column_to_language_code_to_identify_rtl_or_ltr.py b/backend/alembic/versions/2023-05-02-0319-d6e388430a44-add_column_to_language_code_to_identify_rtl_or_ltr.py new file mode 100644 index 0000000..07aa225 --- /dev/null +++ b/backend/alembic/versions/2023-05-02-0319-d6e388430a44-add_column_to_language_code_to_identify_rtl_or_ltr.py @@ -0,0 +1,72 @@ +"""Add column to language_code to identify rtl or ltr + +Revision ID: d6e388430a44 +Revises: c3c4e62fadcf +Create Date: 2023-05-02 03:19:03.458931+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd6e388430a44' +down_revision = 'c3c4e62fadcf' +branch_labels = None +depends_on = None + +# list of 3-letter language codes that are rtl +# https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code +RTL_LANGUAGES = [ + 'acm', # Mesopotamian Arabic + 'acq', # Ta'izzi-Adeni Arabic + 'ajp', # South Levantine Arabic + 'apc', # North Levantine Arabic + 'arb', # Standard Arabic + 'arz', # Egyptian Arabic + 'kmr', # Northern Kurdish + 'hau', # Hausa + 'heb', # Hebrew + 'kas', # Kashmiri + 'snd', # Sindhi + 'urd', # Urdu + 'ydd', # Eastern Yiddish +] + +def upgrade() -> None: + # create the enum type if it doesn't already exist + op.execute( + "CREATE TYPE direction AS ENUM ('ltr', 'rtl')" + ) + + # add a column to the language_code table to identify rtl or ltr + op.add_column( + "language_code", + sa.Column( + "direction", + sa.Enum("ltr", "rtl", name="direction"), + nullable=True, + ), + ) + + # set the following languages to rtl + op.execute( + "UPDATE language_code SET direction = 'rtl' WHERE code IN ('{}')".format( + "', '".join(RTL_LANGUAGES) + ) + ) + + # set the remaining languages to ltr + op.execute( + "UPDATE language_code SET direction = 'ltr' WHERE direction IS NULL" + ) + + +def downgrade() -> None: + # drop the column from the language_code table + op.drop_column("language_code", "direction") + + # drop the enum type + op.execute( + "DROP TYPE direction" + ) diff --git a/backend/alembic/versions/2023-05-05-0407-2bb74cbd6ca3-recreate_prompt_completion_index.py b/backend/alembic/versions/2023-05-05-0407-2bb74cbd6ca3-recreate_prompt_completion_index.py new file mode 100644 index 0000000..0acee0f --- /dev/null +++ b/backend/alembic/versions/2023-05-05-0407-2bb74cbd6ca3-recreate_prompt_completion_index.py @@ -0,0 +1,81 @@ +"""recreate_prompt_completion_index + +Revision ID: 2bb74cbd6ca3 +Revises: d6e388430a44 +Create Date: 2023-05-05 04:07:56.496927+00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import text + + +# revision identifiers, used by Alembic. +revision = '2bb74cbd6ca3' +down_revision = 'd6e388430a44' +branch_labels = None +depends_on = None + + +def upgrade(): + # create the key_hash column + op.add_column('task', sa.Column('key_hash', sa.String(length=64), nullable=True)) + + # set the key_hash column to the first 16 characters of the sha256 hash of the prompt and completion + op.execute('UPDATE task SET key_hash = SUBSTRING(encode(digest(CONCAT(prompt, completion, dataset_id, language_id), \'sha256\'), \'hex\'), 1, 16)') + + # ensure that the key_hash column is not nullable + op.alter_column('task', 'key_hash', nullable=False) + + # ensure that the key_hash column is unique + op.create_index('uq_task_prompt_completion_key_hash', 'task', ['key_hash'], unique=True) + + # ensure that the prompt and completion unique constraint and index is dropped + op.drop_constraint('uq_task_prompt_completion_key', 'task', type_='unique') + + # Define a SQL function to calculate the SHA256 hash of the `prompt` and `completion` columns + sql = text(""" + CREATE OR REPLACE FUNCTION calculate_hash_key() RETURNS TRIGGER AS $$ + BEGIN + NEW.key_hash := + SUBSTRING( + encode( + digest(CONCAT(NEW.prompt, NEW.completion), 'sha256'), + 'hex' + ), + 1, + 16 + ); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + """) + + # Execute the SQL function + conn = op.get_bind() + conn.execute(sql) + + # Create a database trigger that calls the `calculate_hash_key` function on every insert or update operation + op.execute(""" + CREATE TRIGGER task_hash_key_trigger + BEFORE INSERT OR UPDATE ON task + FOR EACH ROW + EXECUTE FUNCTION calculate_hash_key(); + """) + +def downgrade(): + # add the unique constraint on the prompt and completion columns, which is what we had before + # and automatically creates an index on those columns + op.create_unique_constraint('uq_task_prompt_completion_key', 'task', ['prompt', 'completion']) + + # drop the unique constraint on the key_hash column + op.drop_index('uq_task_prompt_completion_key_hash', table_name='task') + + # Drop the database trigger that calls the `calculate_hash_key` function + op.execute('DROP TRIGGER task_hash_key_trigger ON task') + + # Drop the `calculate_hash_key` function + op.execute("DROP FUNCTION calculate_hash_key()") + + # drop the key_hash column + op.drop_column('task', 'key_hash') \ No newline at end of file diff --git a/backend/alembic/versions/2023-05-06-1814-b85910c50121-rename_task_submission.py b/backend/alembic/versions/2023-05-06-1814-b85910c50121-rename_task_submission.py new file mode 100644 index 0000000..1eb03a4 --- /dev/null +++ b/backend/alembic/versions/2023-05-06-1814-b85910c50121-rename_task_submission.py @@ -0,0 +1,38 @@ +"""rename_task_submission + +Revision ID: b85910c50121 +Revises: 2bb74cbd6ca3 +Create Date: 2023-05-06 18:14:25.826112+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b85910c50121' +down_revision = '2bb74cbd6ca3' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # rename the task submission table to task audit + op.rename_table("task_submission", "task_audit") + + # rename the index on the task submission table to task audit + op.execute("ALTER INDEX task_submission_pkey RENAME TO task_audit_pkey") + + # rename the foreign key on the task submission table to task audit + op.execute("ALTER TABLE task_audit RENAME CONSTRAINT task_submission_submitted_by_fkey TO task_audit_submitted_by_fkey") + + +def downgrade() -> None: + # rename the task audit table to task submission + op.rename_table("task_audit", "task_submission") + + # rename the index on the task audit table to task submission + op.execute("ALTER INDEX task_audit_pkey RENAME TO task_submission_pkey") + + # rename the foreign key on the task audit table to task submission + op.execute("ALTER TABLE task_submission RENAME CONSTRAINT task_audit_submitted_by_fkey TO task_submission_submitted_by_fkey") diff --git a/backend/alembic/versions/2023-05-06-1831-e10691cae9e3-add_task_contribution_table.py b/backend/alembic/versions/2023-05-06-1831-e10691cae9e3-add_task_contribution_table.py new file mode 100644 index 0000000..cf6b045 --- /dev/null +++ b/backend/alembic/versions/2023-05-06-1831-e10691cae9e3-add_task_contribution_table.py @@ -0,0 +1,41 @@ +"""add_task_contribution_table + +Revision ID: e10691cae9e3 +Revises: b85910c50121 +Create Date: 2023-05-06 18:31:42.210642+00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID + + +# revision identifiers, used by Alembic. +revision = 'e10691cae9e3' +down_revision = 'b85910c50121' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # create the task contribution table + op.create_table( + "task_contribution", + sa.Column( + "id", + UUID(as_uuid=True), + primary_key=True, + server_default=sa.text("uuid_generate_v4()"), + nullable=False, + ), + sa.Column("submitted_by", UUID(as_uuid=True), sa.ForeignKey("user.id"), nullable=False), + sa.Column("submitted_prompt", sa.Text, nullable=False), + sa.Column("submitted_completion", sa.Text, nullable=False), + sa.Column("language_id", UUID(as_uuid=True), sa.ForeignKey("language_code.id"), nullable=False), + sa.Column("created_at", sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + +def downgrade() -> None: + # drop the task contribution table + op.drop_table("task_contribution") diff --git a/backend/alembic/versions/2023-05-09-2158-dbde0b456746-add_leaderboard_columns.py b/backend/alembic/versions/2023-05-09-2158-dbde0b456746-add_leaderboard_columns.py new file mode 100644 index 0000000..1c950f5 --- /dev/null +++ b/backend/alembic/versions/2023-05-09-2158-dbde0b456746-add_leaderboard_columns.py @@ -0,0 +1,45 @@ +"""add_leaderboard_columns + +Revision ID: dbde0b456746 +Revises: e10691cae9e3 +Create Date: 2023-05-09 21:58:26.091588+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'dbde0b456746' +down_revision = 'e10691cae9e3' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add the rank and image_url columns to the leaderboard tables + op.add_column('leaderboard_daily', sa.Column('rank', sa.Integer(), nullable=True)) + op.add_column('leaderboard_daily', sa.Column('image_url', sa.Text(), nullable=True)) + + op.add_column('leaderboard_weekly', sa.Column('rank', sa.Integer(), nullable=True)) + op.add_column('leaderboard_weekly', sa.Column('image_url', sa.Text(), nullable=True)) + + op.add_column('leaderboard_by_language', sa.Column('rank', sa.Integer(), nullable=True)) + op.add_column('leaderboard_by_language', sa.Column('image_url', sa.Text(), nullable=True)) + + op.add_column('leaderboard_overall', sa.Column('rank', sa.Integer(), nullable=True)) + op.add_column('leaderboard_overall', sa.Column('image_url', sa.Text(), nullable=True)) + +def downgrade() -> None: + # remove the rank and image_url columns from the leaderboard tables + op.drop_column('leaderboard_daily', 'rank') + op.drop_column('leaderboard_daily', 'image_url') + + op.drop_column('leaderboard_weekly', 'rank') + op.drop_column('leaderboard_weekly', 'image_url') + + op.drop_column('leaderboard_by_language', 'rank') + op.drop_column('leaderboard_by_language', 'image_url') + + op.drop_column('leaderboard_overall', 'rank') + op.drop_column('leaderboard_overall', 'image_url') diff --git a/backend/alembic/versions/2023-05-14-2355-ff37ca6a9336-modify_leaderboard_constraints.py b/backend/alembic/versions/2023-05-14-2355-ff37ca6a9336-modify_leaderboard_constraints.py new file mode 100644 index 0000000..ff1e07f --- /dev/null +++ b/backend/alembic/versions/2023-05-14-2355-ff37ca6a9336-modify_leaderboard_constraints.py @@ -0,0 +1,76 @@ +"""modify_leaderboard_constraints + +Revision ID: ff37ca6a9336 +Revises: dbde0b456746 +Create Date: 2023-05-14 23:55:25.717988+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ff37ca6a9336' +down_revision = 'dbde0b456746' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # create a unique constraint on username and day for leaderboard_daily + op.create_unique_constraint( + 'uq_leaderboard_daily_username_day', + 'leaderboard_daily', + ['username', 'day'], + ) + + # create a unique constraint on username and week for leaderboard_weekly + op.create_unique_constraint( + 'uq_leaderboard_weekly_username_week', + 'leaderboard_weekly', + ['username', 'week_of'], + ) + + # create a unique constraint on username and language for leaderboard_by_language + op.create_unique_constraint( + 'uq_leaderboard_by_language_username_language', + 'leaderboard_by_language', + ['username', 'language'], + ) + + # create a unique constraint on username for leaderboard_overall + op.create_unique_constraint( + 'uq_leaderboard_overall_username', + 'leaderboard_overall', + ['username'], + ) + + +def downgrade() -> None: + # drop the unique constraint on username and day for leaderboard_daily + op.drop_constraint( + 'uq_leaderboard_daily_username_day', + 'leaderboard_daily', + type_='unique', + ) + + # drop the unique constraint on username and week for leaderboard_weekly + op.drop_constraint( + 'uq_leaderboard_weekly_username_week', + 'leaderboard_weekly', + type_='unique', + ) + + # drop the unique constraint on username and language for leaderboard_by_language + op.drop_constraint( + 'uq_leaderboard_by_language_username_language', + 'leaderboard_by_language', + type_='unique', + ) + + # drop the unique constraint on username for leaderboard_overall + op.drop_constraint( + 'uq_leaderboard_overall_username', + 'leaderboard_overall', + type_='unique', + ) diff --git a/backend/alembic/versions/2023-05-15-0134-f233a368f1c4-add_fields_to_leaderboards.py b/backend/alembic/versions/2023-05-15-0134-f233a368f1c4-add_fields_to_leaderboards.py new file mode 100644 index 0000000..18e56bf --- /dev/null +++ b/backend/alembic/versions/2023-05-15-0134-f233a368f1c4-add_fields_to_leaderboards.py @@ -0,0 +1,55 @@ +"""add_fields_to_leaderboards + +Revision ID: f233a368f1c4 +Revises: ff37ca6a9336 +Create Date: 2023-05-15 01:34:00.478188+00:00 + +""" +from alembic import op +import sqlalchemy as sa + +from sqlalchemy.dialects.postgresql import UUID + + +# revision identifiers, used by Alembic. +revision = 'f233a368f1c4' +down_revision = 'ff37ca6a9336' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add the user_id column to leaderboard_daily, leaderboard_weekly, leaderboard_by_language, and leaderboard_overall + op.add_column( + 'leaderboard_daily', + sa.Column('user_id', UUID(as_uuid=True), nullable=False), + ) + op.add_column( + 'leaderboard_weekly', + sa.Column('user_id', UUID(as_uuid=True), nullable=False), + ) + op.add_column( + 'leaderboard_by_language', + sa.Column('user_id', UUID(as_uuid=True), nullable=False), + ) + op.add_column( + 'leaderboard_overall', + sa.Column('user_id', UUID(as_uuid=True), nullable=False), + ) + + # add the language_code column to leaderboard_by_language + op.add_column( + 'leaderboard_by_language', + sa.Column('language_code', sa.String(), nullable=False), + ) + + +def downgrade() -> None: + # drop the user_id column from leaderboard_daily, leaderboard_weekly, leaderboard_by_language, and leaderboard_overall + op.drop_column('leaderboard_daily', 'user_id') + op.drop_column('leaderboard_weekly', 'user_id') + op.drop_column('leaderboard_by_language', 'user_id') + op.drop_column('leaderboard_overall', 'user_id') + + # drop the language_code column from leaderboard_by_language + op.drop_column('leaderboard_by_language', 'language_code') diff --git a/backend/alembic/versions/2023-05-19-1524-849bcc32963c-add_active_toggling_for_dataset.py b/backend/alembic/versions/2023-05-19-1524-849bcc32963c-add_active_toggling_for_dataset.py new file mode 100644 index 0000000..4fc5e0c --- /dev/null +++ b/backend/alembic/versions/2023-05-19-1524-849bcc32963c-add_active_toggling_for_dataset.py @@ -0,0 +1,47 @@ +"""add_active_toggling_for_dataset + +Revision ID: 849bcc32963c +Revises: f233a368f1c4 +Create Date: 2023-05-19 15:24:01.728156+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '849bcc32963c' +down_revision = 'f233a368f1c4' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add an is_active column to the dataset table + # that defaults to true + op.add_column( + 'dataset', + sa.Column( + 'active', + sa.Boolean, + nullable=False, + server_default=sa.true(), + ), + ) + + # set any of the datasets using `facebook-nllb-200-3.3B` to inactive, + # but make sure we're not deactivating `sentencesplit_facebook-nllb-200-3.3B` + op.execute( + """ + UPDATE dataset + SET active = false + WHERE name LIKE '%facebook-nllb-200-3.3B%' + AND name NOT LIKE '%sentencesplit_facebook-nllb-200-3.3B%' + """ + ) + + +def downgrade() -> None: + # remove the active column from the dataset table + op.drop_column('dataset', 'active') + diff --git a/backend/alembic/versions/2023-05-22-0235-90905dca267b-separate_chinese_hans_hant.py b/backend/alembic/versions/2023-05-22-0235-90905dca267b-separate_chinese_hans_hant.py new file mode 100644 index 0000000..bcb08e4 --- /dev/null +++ b/backend/alembic/versions/2023-05-22-0235-90905dca267b-separate_chinese_hans_hant.py @@ -0,0 +1,131 @@ +"""separate_chinese_hans_hant + +Revision ID: 90905dca267b +Revises: 849bcc32963c +Create Date: 2023-05-22 02:35:39.625536+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '90905dca267b' +down_revision = '849bcc32963c' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # set the current "Chinese" language to "Chinese (Traditional)" in the language_code table + # since it uses the Hant character code + op.execute( + """ + UPDATE language_code + SET name = 'Chinese (Traditional)' + WHERE name = 'Chinese' + """ + ) + + # add a new language to the language_code table for "Chinese (Simplified)" + op.execute( + """ + INSERT INTO language_code (code, name, character_code, direction) + VALUES ('zho', 'Chinese (Simplified)', 'Hans', 'ltr') + """ + ) + + # update the dataset table to use the new language_code ids + op.execute( + """ + UPDATE dataset + SET language_id = ( + SELECT id + FROM language_code + WHERE name = 'Chinese (Traditional)' + ) + WHERE name like '%zho_Hant%' + """ + ) + + op.execute( + """ + UPDATE dataset + SET language_id = ( + SELECT id + FROM language_code + WHERE name = 'Chinese (Simplified)' + ) + WHERE name like '%zho_Hans%' + """ + ) + + # update the task table to use the new language_code ids + # Hant tasks will already be using the correct language_code id + # so we only need to update the Hans tasks. + # use the dataset's language_id since it will be the correct one + op.execute( + """ + UPDATE task + SET language_id = dataset.language_id + FROM dataset + WHERE task.dataset_id = dataset.id + AND dataset.active = TRUE + AND dataset.name LIKE '%zho_Hans%'; + """ + ) + +def downgrade() -> None: + # set the current "Chinese (Traditional)" language to "Chinese" in the language_code table + op.execute( + """ + UPDATE language_code + SET name = 'Chinese' + WHERE name = 'Chinese (Traditional)' + """ + ) + + # update the dataset table to use the new language_code ids + op.execute( + """ + UPDATE dataset + SET language_id = ( + SELECT id + FROM language_code + WHERE name = 'Chinese' + ) + WHERE name like '%zho_Hant%' + """ + ) + + op.execute( + """ + UPDATE dataset + SET language_id = ( + SELECT id + FROM language_code + WHERE name = 'Chinese' + ) + WHERE name like '%zho_Hans%' + """ + ) + + # update the task table to just use the Chinese language_code id + op.execute( + """ + UPDATE task + SET language_id = dataset.language_id + FROM dataset + WHERE task.dataset_id = dataset.id + AND dataset.active = TRUE + AND dataset.name LIKE '%zho_Hans%' + """ + ) + + # delete the "Chinese (Traditional)" language from the language_code table + op.execute( + """ + DELETE FROM language_code + WHERE name = 'Chinese (Simplified)' + """ + ) diff --git a/backend/alembic/versions/2023-05-25-0353-7e4985d7daa7-deactivate_arb_latn_dataset.py b/backend/alembic/versions/2023-05-25-0353-7e4985d7daa7-deactivate_arb_latn_dataset.py new file mode 100644 index 0000000..6be8744 --- /dev/null +++ b/backend/alembic/versions/2023-05-25-0353-7e4985d7daa7-deactivate_arb_latn_dataset.py @@ -0,0 +1,56 @@ +"""deactivate_arb_Latn_dataset + +Revision ID: 7e4985d7daa7 +Revises: 90905dca267b +Create Date: 2023-05-25 03:53:16.227154+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7e4985d7daa7' +down_revision = '90905dca267b' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Deactivate arb_Latn dataset in the dataset table + op.execute( + """ + UPDATE dataset + SET active = false + WHERE name LIKE '%to_arb_Latn%'; + """ + ) + + # Deactivate min_Arab dataset in the dataset table + op.execute( + """ + UPDATE dataset + SET active = false + WHERE name LIKE '%to_min_Arab%'; + """ + ) + + +def downgrade() -> None: + # Activate arb_Latn dataset in the dataset table + op.execute( + """ + UPDATE dataset + SET active = true + WHERE name LIKE '%to_arb_Latn%'; + """ + ) + + # Activate min_Arab dataset in the dataset table + op.execute( + """ + UPDATE dataset + SET active = true + WHERE name LIKE '%to_min_Arab%'; + """ + ) diff --git a/backend/alembic/versions/2023-05-26-0756-08d92ecbd824-mark_more_rtl_languages.py b/backend/alembic/versions/2023-05-26-0756-08d92ecbd824-mark_more_rtl_languages.py new file mode 100644 index 0000000..71f8048 --- /dev/null +++ b/backend/alembic/versions/2023-05-26-0756-08d92ecbd824-mark_more_rtl_languages.py @@ -0,0 +1,41 @@ +"""mark_more_rtl_languages + +Revision ID: 08d92ecbd824 +Revises: 7e4985d7daa7 +Create Date: 2023-05-26 07:56:16.568111+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '08d92ecbd824' +down_revision = '7e4985d7daa7' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # mark the following languages as RTL: + # Pashto, Panjabi, Iranian Persian + op.execute( + """ + UPDATE language_code + SET direction = 'rtl' + WHERE code IN ('pes', 'pan', 'pbt'); + """ + ) + + + +def downgrade() -> None: + # mark the following languages as LTR: + # Pashto, Panjabi, Iranian Persian + op.execute( + """ + UPDATE language_code + SET direction = 'ltr' + WHERE code IN ('pes', 'pan', 'pbt'); + """ + ) diff --git a/backend/alembic/versions/2023-05-30-0340-785f6b48ad7a-add_more_user_survey_questions.py b/backend/alembic/versions/2023-05-30-0340-785f6b48ad7a-add_more_user_survey_questions.py new file mode 100644 index 0000000..9cc6e2d --- /dev/null +++ b/backend/alembic/versions/2023-05-30-0340-785f6b48ad7a-add_more_user_survey_questions.py @@ -0,0 +1,54 @@ +"""add_more_user_survey_questions + +Revision ID: 785f6b48ad7a +Revises: 08d92ecbd824 +Create Date: 2023-05-30 03:40:39.025935+00:00 + +""" +from alembic import op +import sqlalchemy as sa + +import sqlalchemy.dialects.postgresql as sa_psql + + +# revision identifiers, used by Alembic. +revision = '785f6b48ad7a' +down_revision = '08d92ecbd824' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add age_range column to user table + op.add_column( + 'user', + sa.Column( + 'age_range', + sa_psql.INT4RANGE, + nullable=True, + ), + ) + + # add the gender column to user table + op.add_column( + 'user', + sa.Column( + 'gender', + sa_psql.VARCHAR(64), + nullable=True, + ), + ) + + +def downgrade() -> None: + # drop the age_range column from user table + op.drop_column( + 'user', + 'age_range', + ) + + # drop the gender column from user table + op.drop_column( + 'user', + 'gender', + ) \ No newline at end of file diff --git a/backend/alembic/versions/2023-06-01-0405-cf118e4d101b-make_hausa_ltr.py b/backend/alembic/versions/2023-06-01-0405-cf118e4d101b-make_hausa_ltr.py new file mode 100644 index 0000000..2369a25 --- /dev/null +++ b/backend/alembic/versions/2023-06-01-0405-cf118e4d101b-make_hausa_ltr.py @@ -0,0 +1,26 @@ +"""make_hausa_ltr + +Revision ID: cf118e4d101b +Revises: 785f6b48ad7a +Create Date: 2023-06-01 04:05:49.718957+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'cf118e4d101b' +down_revision = '785f6b48ad7a' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # make Hausa (hau) ltr direction in the language_code table + op.execute("UPDATE language_code SET direction = 'ltr' WHERE code = 'hau'") + + +def downgrade() -> None: + # make Hausa (hau) rtl direction in the language_code table + op.execute("UPDATE language_code SET direction = 'rtl' WHERE code = 'hau'") diff --git a/backend/alembic/versions/2023-06-02-0401-38bb36535ede-add_user_languages_to_leaderboards.py b/backend/alembic/versions/2023-06-02-0401-38bb36535ede-add_user_languages_to_leaderboards.py new file mode 100644 index 0000000..42c0225 --- /dev/null +++ b/backend/alembic/versions/2023-06-02-0401-38bb36535ede-add_user_languages_to_leaderboards.py @@ -0,0 +1,57 @@ +"""add_user_languages_to_leaderboards + +Revision ID: 38bb36535ede +Revises: cf118e4d101b +Create Date: 2023-06-02 04:01:21.383819+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '38bb36535ede' +down_revision = 'cf118e4d101b' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add a `languages` column to the leaderboard_daily table + # repeat for weekly and overall. it should be an array of text + op.add_column( + "leaderboard_daily", + sa.Column( + "languages", + sa.ARRAY(sa.Text), + # NOTE: this is nullable temporarily while we don't + # have to backfill leaderboard data yet + nullable=True, + ), + ) + + op.add_column( + "leaderboard_weekly", + sa.Column( + "languages", + sa.ARRAY(sa.Text), + nullable=True, + ), + ) + + op.add_column( + "leaderboard_overall", + sa.Column( + "languages", + sa.ARRAY(sa.Text), + nullable=True, + ), + ) + + +def downgrade() -> None: + # remove the `languages` column from the leaderboard_daily table + # repeat for weekly and overall. + op.drop_column("leaderboard_daily", "languages") + op.drop_column("leaderboard_weekly", "languages") + op.drop_column("leaderboard_overall", "languages") diff --git a/backend/alembic/versions/2023-06-16-1358-68daab0b8ccb-store_discord_email_and_id.py b/backend/alembic/versions/2023-06-16-1358-68daab0b8ccb-store_discord_email_and_id.py new file mode 100644 index 0000000..ba378b3 --- /dev/null +++ b/backend/alembic/versions/2023-06-16-1358-68daab0b8ccb-store_discord_email_and_id.py @@ -0,0 +1,38 @@ +"""store_discord_email_and_id + +Revision ID: 68daab0b8ccb +Revises: 38bb36535ede +Create Date: 2023-06-16 13:58:18.633420+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '68daab0b8ccb' +down_revision = '38bb36535ede' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column('user', sa.Column('email', sa.Text(), nullable=True)) + op.add_column('user', sa.Column('discord_id', sa.String(), nullable=True)) + + # drop the unique constraint on the username column + op.drop_constraint('user_username_key', 'user') + + # ensure that the discord_id column is unique + op.create_unique_constraint('uq_user_discord_id', 'user', ['discord_id']) + + +def downgrade() -> None: + op.drop_column('user', 'email') + op.drop_column('user', 'discord_id') + + # drop the unique constraint + op.drop_constraint('uq_user_discord_id', 'user') + + # ensure that the username column is unique + op.create_unique_constraint('user_username_key', 'user', ['username']) diff --git a/backend/alembic/versions/2023-06-19-1154-53f48b99dd8c-deactivate_gem_wiki_cat_sum_datasets.py b/backend/alembic/versions/2023-06-19-1154-53f48b99dd8c-deactivate_gem_wiki_cat_sum_datasets.py new file mode 100644 index 0000000..c30f781 --- /dev/null +++ b/backend/alembic/versions/2023-06-19-1154-53f48b99dd8c-deactivate_gem_wiki_cat_sum_datasets.py @@ -0,0 +1,38 @@ +"""deactivate_gem_wiki_sum_datasets + +Revision ID: 53f48b99dd8c +Revises: 68daab0b8ccb +Create Date: 2023-06-19 11:54:11.693539+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '53f48b99dd8c' +down_revision = '68daab0b8ccb' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # update the datasets table to make the gem_wiki_cat_sum datasets inactive + op.execute( + """ + UPDATE dataset + SET active = false + WHERE name LIKE '%GEM_wiki_cat_sum%'; + """ + ) + + +def downgrade() -> None: + # update the datasets table to make the gem_wiki_cat_sum datasets active + op.execute( + """ + UPDATE dataset + SET active = true + WHERE name LIKE '%GEM_wiki_cat_sum%'; + """ + ) diff --git a/backend/alembic/versions/2023-06-21-2151-b9d22315640b-add_edit_distance_to_task_audit_table.py b/backend/alembic/versions/2023-06-21-2151-b9d22315640b-add_edit_distance_to_task_audit_table.py new file mode 100644 index 0000000..c6d1a68 --- /dev/null +++ b/backend/alembic/versions/2023-06-21-2151-b9d22315640b-add_edit_distance_to_task_audit_table.py @@ -0,0 +1,29 @@ +"""add_edit_distance_to_task_audit_table + +Revision ID: b9d22315640b +Revises: 53f48b99dd8c +Create Date: 2023-06-21 21:51:51.195493+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b9d22315640b' +down_revision = '53f48b99dd8c' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add edit_distance as a decimal column to the task_audit table + op.add_column( + 'task_audit', + sa.Column('edit_distance', sa.DECIMAL, nullable=True) + ) + + +def downgrade() -> None: + # drop edit_distance column from the task_audit table + op.drop_column('task_audit', 'edit_distance') diff --git a/backend/alembic/versions/2023-06-24-2202-0bf3df1a1e43-update_leaderboard_table_uq_constraints.py b/backend/alembic/versions/2023-06-24-2202-0bf3df1a1e43-update_leaderboard_table_uq_constraints.py new file mode 100644 index 0000000..93897a0 --- /dev/null +++ b/backend/alembic/versions/2023-06-24-2202-0bf3df1a1e43-update_leaderboard_table_uq_constraints.py @@ -0,0 +1,141 @@ +"""update_leaderboard_table_uq_constraints + +Revision ID: 0bf3df1a1e43 +Revises: b9d22315640b +Create Date: 2023-06-24 22:02:07.372951+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0bf3df1a1e43' +down_revision = 'b9d22315640b' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # the leaderboard tables should have a unique constraint on the user_id column + # rather than the username column, since users can change their username + # and we don't want to have to manually fix data in the leaderboard table every time + # a user changes their username + + # drop the current constraints on username and day for leaderboard_daily + op.drop_constraint( + 'uq_leaderboard_daily_username_day', + 'leaderboard_daily', + type_='unique', + ) + + # drop the unique constraint on username and week for leaderboard_weekly + op.drop_constraint( + 'uq_leaderboard_weekly_username_week', + 'leaderboard_weekly', + type_='unique', + ) + + # drop the unique constraint on username and language for leaderboard_by_language + op.drop_constraint( + 'uq_leaderboard_by_language_username_language', + 'leaderboard_by_language', + type_='unique', + ) + + # drop the unique constraint on username for leaderboard_overall + op.drop_constraint( + 'uq_leaderboard_overall_username', + 'leaderboard_overall', + type_='unique', + ) + + # create a unique constraint on user_id and day for leaderboard_daily + op.create_unique_constraint( + 'uq_leaderboard_daily_user_id_day', + 'leaderboard_daily', + ['user_id', 'day'], + ) + + # create a unique constraint on user_id and week for leaderboard_weekly + op.create_unique_constraint( + 'uq_leaderboard_weekly_user_id_week', + 'leaderboard_weekly', + ['user_id', 'week_of'], + ) + + # create a unique constraint on user_id and language for leaderboard_by_language + op.create_unique_constraint( + 'uq_leaderboard_by_language_user_id_language', + 'leaderboard_by_language', + ['user_id', 'language'], + ) + + # create a unique constraint on user_id for leaderboard_overall + op.create_unique_constraint( + 'uq_leaderboard_overall_user_id', + 'leaderboard_overall', + ['user_id'], + ) + + + +def downgrade() -> None: + # do the inverse of the above operations + + # drop the current constraints on user_id and day for leaderboard_daily + op.drop_constraint( + 'uq_leaderboard_daily_user_id_day', + 'leaderboard_daily', + type_='unique', + ) + + # drop the unique constraint on user_id and week for leaderboard_weekly + op.drop_constraint( + 'uq_leaderboard_weekly_user_id_week', + 'leaderboard_weekly', + type_='unique', + ) + + # drop the unique constraint on user_id and language for leaderboard_by_language + op.drop_constraint( + 'uq_leaderboard_by_language_user_id_language', + 'leaderboard_by_language', + type_='unique', + ) + + # drop the unique constraint on user_id for leaderboard_overall + op.drop_constraint( + 'uq_leaderboard_overall_user_id', + 'leaderboard_overall', + type_='unique', + ) + + + # create a unique constraint on username and day for leaderboard_daily + op.create_unique_constraint( + 'uq_leaderboard_daily_username_day', + 'leaderboard_daily', + ['username', 'day'], + ) + + # create a unique constraint on username and week for leaderboard_weekly + op.create_unique_constraint( + 'uq_leaderboard_weekly_username_week', + 'leaderboard_weekly', + ['username', 'week_of'], + ) + + # create a unique constraint on username and language for leaderboard_by_language + op.create_unique_constraint( + 'uq_leaderboard_by_language_username_language', + 'leaderboard_by_language', + ['username', 'language'], + ) + + # create a unique constraint on username for leaderboard_overall + op.create_unique_constraint( + 'uq_leaderboard_overall_username', + 'leaderboard_overall', + ['username'], + ) diff --git a/backend/alembic/versions/2023-06-26-1423-3380a3512991-add_task_contribution_audit_table.py b/backend/alembic/versions/2023-06-26-1423-3380a3512991-add_task_contribution_audit_table.py new file mode 100644 index 0000000..0749d80 --- /dev/null +++ b/backend/alembic/versions/2023-06-26-1423-3380a3512991-add_task_contribution_audit_table.py @@ -0,0 +1,39 @@ +"""Add task_contribution_audit table + +Revision ID: 3380a3512991 +Revises: 0bf3df1a1e43 +Create Date: 2023-06-26 14:23:27.107054+00:00 + +""" +from alembic import op +import sqlalchemy as sa + +from sqlalchemy.dialects.postgresql import UUID + + +# revision identifiers, used by Alembic. +revision = '3380a3512991' +down_revision = '0bf3df1a1e43' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + 'task_contribution_audit', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('task_contribution_id', UUID(as_uuid=True), sa.ForeignKey('task_contribution.id'), nullable=False), + sa.Column('submitted_by', UUID(as_uuid=True), sa.ForeignKey('user.id'), nullable=False), + sa.Column('submitted_prompt', sa.Text, nullable=False), + sa.Column('submitted_completion', sa.Text, nullable=False), + sa.Column('prompt_edited', sa.Boolean, nullable=False), + sa.Column('completion_edited', sa.Boolean, nullable=False), + sa.Column('prompt_rating', sa.Integer, nullable=False), + sa.Column('completion_rating', sa.Integer, nullable=False), + sa.Column('edit_distance', sa.DECIMAL, nullable=True), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + +def downgrade() -> None: + op.drop_table('task_contribution_audit') diff --git a/backend/alembic/versions/2023-06-29-1305-233490849236-task_audit_review.py b/backend/alembic/versions/2023-06-29-1305-233490849236-task_audit_review.py new file mode 100644 index 0000000..d88d13c --- /dev/null +++ b/backend/alembic/versions/2023-06-29-1305-233490849236-task_audit_review.py @@ -0,0 +1,51 @@ +"""task_audit_review + +Revision ID: 233490849236 +Revises: 0bf3df1a1e43 +Create Date: 2023-06-29 13:05:36.361283+00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID + +# revision identifiers, used by Alembic. +revision = '233490849236' +down_revision = '3380a3512991' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + 'task_audit_review', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('task_audit_id', UUID(as_uuid=True), sa.ForeignKey('task_audit.id'), nullable=False), + sa.Column('edited_prompt_rating', sa.Integer, nullable=False), + sa.Column('edited_completion_rating', sa.Integer, nullable=False), + # these should only end up with null values if the ratings are < 5 + sa.Column('improved_prompt', sa.Text, nullable=True), + sa.Column('improved_completion', sa.Text, nullable=True), + sa.Column('improvement_feedback', sa.Text, nullable=False), + sa.Column('submitted_by', UUID(as_uuid=True), nullable=False), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + op.create_table( + 'task_contribution_audit_review', + sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=(sa.text("uuid_generate_v4()"))), + sa.Column('task_contribution_audit_id', UUID(as_uuid=True), sa.ForeignKey('task_contribution_audit.id'), nullable=False), + sa.Column('edited_prompt_rating', sa.Integer, nullable=False), + sa.Column('edited_completion_rating', sa.Integer, nullable=False), + # these should only end up with null values if the ratings are < 5 + sa.Column('improved_prompt', sa.Text, nullable=True), + sa.Column('improved_completion', sa.Text, nullable=True), + sa.Column('improvement_feedback', sa.Text, nullable=False), + sa.Column('submitted_by', UUID(as_uuid=True), nullable=False), + sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()), + ) + + +def downgrade() -> None: + op.drop_table('task_audit_review') + op.drop_table('task_contribution_audit_review') \ No newline at end of file diff --git a/backend/alembic/versions/2023-07-01-1250-35875912fb74-rename_pedi_to_northern_sotho.py b/backend/alembic/versions/2023-07-01-1250-35875912fb74-rename_pedi_to_northern_sotho.py new file mode 100644 index 0000000..22d5526 --- /dev/null +++ b/backend/alembic/versions/2023-07-01-1250-35875912fb74-rename_pedi_to_northern_sotho.py @@ -0,0 +1,25 @@ + +"""rename_pedi_to_northern_sotho + +Revision ID: 35875912fb74 +Revises: 233490849236 +Create Date: 2023-07-01 12:50:02.278126+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '35875912fb74' +down_revision = '233490849236' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute("UPDATE language_code SET name = 'Northern Sotho' WHERE code = 'nso'") + + +def downgrade() -> None: + op.execute("UPDATE language_code SET name = 'Pedi' WHERE code = 'nso'") diff --git a/backend/alembic/versions/2023-07-05-1855-aec96b6f1dd6-add_latin_support.py b/backend/alembic/versions/2023-07-05-1855-aec96b6f1dd6-add_latin_support.py new file mode 100644 index 0000000..c64d238 --- /dev/null +++ b/backend/alembic/versions/2023-07-05-1855-aec96b6f1dd6-add_latin_support.py @@ -0,0 +1,36 @@ +"""add_latin_support + +Revision ID: aec96b6f1dd6 +Revises: 35875912fb74 +Create Date: 2023-07-05 18:55:35.637514+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'aec96b6f1dd6' +down_revision = '35875912fb74' +branch_labels = None +depends_on = None + +# Table "public.language_code" +# Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +# ----------------+-----------------------------+-----------+----------+--------------------+----------+--------------+------------- +# id | uuid | | not null | uuid_generate_v4() | plain | | +# code | character varying(3) | | not null | | extended | | +# name | text | | not null | | extended | | +# created_at | timestamp without time zone | | not null | now() | plain | | +# character_code | character varying(5) | | not null | | extended | | +# direction | direction | | | | plain | | +# Indexes: + +def upgrade() -> None: + # add Latin to the language_code table + op.execute("INSERT INTO language_code (code, name, character_code, direction) VALUES ('lat', 'Latin', 'Latn', 'ltr');") + + +def downgrade() -> None: + # remove Latin from the language_code table + op.execute("DELETE FROM language_code WHERE code = 'lat';") diff --git a/backend/alembic/versions/2023-07-05-1857-9fdf109afc92-fix_panjabi_direction.py b/backend/alembic/versions/2023-07-05-1857-9fdf109afc92-fix_panjabi_direction.py new file mode 100644 index 0000000..38c3d88 --- /dev/null +++ b/backend/alembic/versions/2023-07-05-1857-9fdf109afc92-fix_panjabi_direction.py @@ -0,0 +1,26 @@ +"""fix_panjabi_direction + +Revision ID: 9fdf109afc92 +Revises: aec96b6f1dd6 +Create Date: 2023-07-05 18:57:30.311661+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9fdf109afc92' +down_revision = 'aec96b6f1dd6' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # make sure Panjabi is ltr and not rtl + op.execute("UPDATE language_code SET direction = 'ltr' WHERE code = 'pan';") + + +def downgrade() -> None: + # make sure Panjabi is rtl and not ltr + op.execute("UPDATE language_code SET direction = 'rtl' WHERE code = 'pan';") diff --git a/backend/alembic/versions/2023-07-06-0355-1dd7526c13d5-deactivate_xp3_flores_data.py b/backend/alembic/versions/2023-07-06-0355-1dd7526c13d5-deactivate_xp3_flores_data.py new file mode 100644 index 0000000..a2116e4 --- /dev/null +++ b/backend/alembic/versions/2023-07-06-0355-1dd7526c13d5-deactivate_xp3_flores_data.py @@ -0,0 +1,25 @@ +"""deactivate_xp3_flores_data + +Revision ID: 1dd7526c13d5 +Revises: 9fdf109afc92 +Create Date: 2023-07-06 03:55:24.257547+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1dd7526c13d5' +down_revision = '9fdf109afc92' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # deactivate datasets that are from 'dataset/sample_100/xp3_facebook_flores' + op.execute("UPDATE dataset SET active = false WHERE name LIKE '%%dataset/sample_100/xp3_facebook_flores%';") + +def downgrade() -> None: + # activate datasets that are from 'dataset/sample_100/xp3_facebook_flores' + op.execute("UPDATE dataset SET active = true WHERE name LIKE '%%dataset/sample_100/xp3_facebook_flores%';") diff --git a/backend/alembic/versions/2023-07-06-0418-97c68b55f6b7-add_audit_review_limit_table.py b/backend/alembic/versions/2023-07-06-0418-97c68b55f6b7-add_audit_review_limit_table.py new file mode 100644 index 0000000..b92e143 --- /dev/null +++ b/backend/alembic/versions/2023-07-06-0418-97c68b55f6b7-add_audit_review_limit_table.py @@ -0,0 +1,82 @@ +"""add_audit_review_limit_table + +Revision ID: 97c68b55f6b7 +Revises: 1dd7526c13d5 +Create Date: 2023-07-06 04:18:01.361815+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '97c68b55f6b7' +down_revision = '1dd7526c13d5' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add a table called `audit_review_limit` with the following columns: + # - id: uuid + # - user_id: uuid (fk to public.user) + # - remaining_annotation_validations: integer + # - last_updated_at: timestamp without time zone + # + # This will allows us to keep track of how many audit reviews a user has left to receive, + # summed up across all languages (which they contributed to). + # We'll use this to see how many more ratings we need to get a sense of their quality as an auditor. + op.execute(""" + CREATE TABLE audit_review_limit ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id uuid REFERENCES "user" (id), + remaining_audit_reviews integer NOT NULL, + last_updated_at timestamp without time zone NOT NULL DEFAULT now() + ); + """) + + # load the table with an initial value for each user + # that is the number of audit reviews they have left to receive + # for the generated or contributed tasks they audited + op.execute(""" + INSERT INTO audit_review_limit (user_id, remaining_audit_reviews) + SELECT + user_id, + count(*) AS remaining_audit_reviews + FROM ( + SELECT + u.id as user_id, + ROW_NUMBER() OVER ( + PARTITION BY ta.submitted_by + ORDER BY ta.created_at DESC + ) AS row_num, + COUNT(*) OVER (PARTITION BY ta.submitted_by) AS audit_count + FROM task_audit ta + JOIN task t ON ta.task_id = t.id + JOIN public.user u ON ta.submitted_by = u.id + LEFT JOIN task_audit_review tar ON ta.id = tar.task_audit_id + WHERE tar.task_audit_id IS NULL + + UNION + + SELECT + u.id as user_id, + ROW_NUMBER() OVER ( + PARTITION BY tac.submitted_by + ORDER BY tac.created_at DESC + ) AS row_num, + COUNT(*) OVER (PARTITION BY tac.submitted_by) AS audit_count + FROM task_contribution_audit tac + JOIN task_contribution tc ON tac.task_contribution_id = tc.id + JOIN public.user u ON tac.submitted_by = u.id + LEFT JOIN task_contribution_audit_review tcar ON tac.id = tcar.task_contribution_audit_id + WHERE tcar.task_contribution_audit_id IS NULL + ) AS subquery + WHERE row_num <= FLOOR(10 * LOG(10, audit_count) + 1) + GROUP BY user_id + ORDER BY remaining_audit_reviews DESC; + """) + +def downgrade() -> None: + # remove the `audit_review_limit` table + op.execute("DROP TABLE audit_review_limit;") diff --git a/backend/alembic/versions/2023-07-06-1248-c2583aaea36f-remove_translation_xp3_tasks.py b/backend/alembic/versions/2023-07-06-1248-c2583aaea36f-remove_translation_xp3_tasks.py new file mode 100644 index 0000000..2f93a7b --- /dev/null +++ b/backend/alembic/versions/2023-07-06-1248-c2583aaea36f-remove_translation_xp3_tasks.py @@ -0,0 +1,39 @@ +"""remove_translation_xp3_tasks + +Revision ID: c2583aaea36f +Revises: 97c68b55f6b7 +Create Date: 2023-07-06 12:48:37.075026+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c2583aaea36f' +down_revision = '97c68b55f6b7' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # deactivate datasets that are from: + # 'dataset/sample_100/xp3_GEM_wiki_lingua', 'dataset/sample_100/xp3_Helsinki-NLP_tatoeba', + # and 'dataset/sample_100/xp3_allenai_wmt22_african' + op.execute(""" + UPDATE dataset + SET active = false + WHERE name LIKE '%%dataset/sample_100/xp3_GEM_wiki_lingua%' + OR name LIKE '%%dataset/sample_100/xp3_Helsinki-NLP_tatoeba%' + OR name LIKE '%%dataset/sample_100/xp3_allenai_wmt22_african%' + """) + + +def downgrade() -> None: + op.execute(""" + UPDATE dataset + SET active = true + WHERE name LIKE '%%dataset/sample_100/xp3_GEM_wiki_lingua%' + OR name LIKE '%%dataset/sample_100/xp3_Helsinki-NLP_tatoeba%' + OR name LIKE '%%dataset/sample_100/xp3_allenai_wmt22_african%' + """) diff --git a/backend/alembic/versions/2023-07-06-1327-5c6ab0fe5920-add_created_at_for_audit_review_limit.py b/backend/alembic/versions/2023-07-06-1327-5c6ab0fe5920-add_created_at_for_audit_review_limit.py new file mode 100644 index 0000000..8e4a3f2 --- /dev/null +++ b/backend/alembic/versions/2023-07-06-1327-5c6ab0fe5920-add_created_at_for_audit_review_limit.py @@ -0,0 +1,41 @@ +"""add created_at for audit_review_limit + +Revision ID: 5c6ab0fe5920 +Revises: c2583aaea36f +Create Date: 2023-07-06 13:27:56.193812+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5c6ab0fe5920' +down_revision = 'c2583aaea36f' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add created_at for audit_review_limit + op.execute(""" + ALTER TABLE audit_review_limit + ADD COLUMN created_at TIMESTAMP WITHOUT TIME ZONE; + """) + + # assign a default value to backfill the created_at column + op.execute(""" + UPDATE audit_review_limit + SET created_at = '2023-07-06 06:24:27.895848' + """) + + # add a constraint to the created_at column + op.execute(""" + ALTER TABLE audit_review_limit + ALTER COLUMN created_at SET NOT NULL; + """) + + +def downgrade() -> None: + # remove the created_at column + op.drop_column('audit_review_limit', 'created_at') diff --git a/backend/alembic/versions/2023-07-30-2300-036364ef81bd-add_blended_points_overall_leaderboard.py b/backend/alembic/versions/2023-07-30-2300-036364ef81bd-add_blended_points_overall_leaderboard.py new file mode 100644 index 0000000..58dab48 --- /dev/null +++ b/backend/alembic/versions/2023-07-30-2300-036364ef81bd-add_blended_points_overall_leaderboard.py @@ -0,0 +1,28 @@ +"""add_blended_points_overall_leaderboard + +Revision ID: 036364ef81bd +Revises: 5c6ab0fe5920 +Create Date: 2023-07-30 23:00:50.251212+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '036364ef81bd' +down_revision = '5c6ab0fe5920' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add blended rank and blended points to the overall leaderboard table + # as integers + op.add_column('leaderboard_overall', sa.Column('blended_rank', sa.Integer(), nullable=True)) + op.add_column('leaderboard_overall', sa.Column('blended_points', sa.Integer(), nullable=True)) + +def downgrade() -> None: + # remove blended rank and blended points from the overall leaderboard table + op.drop_column('leaderboard_overall', 'blended_rank') + op.drop_column('leaderboard_overall', 'blended_points') diff --git a/backend/alembic/versions/2023-08-07-1810-8f4f6a9c6a31-add_quality_score_overall_leaderboard.py b/backend/alembic/versions/2023-08-07-1810-8f4f6a9c6a31-add_quality_score_overall_leaderboard.py new file mode 100644 index 0000000..4984076 --- /dev/null +++ b/backend/alembic/versions/2023-08-07-1810-8f4f6a9c6a31-add_quality_score_overall_leaderboard.py @@ -0,0 +1,36 @@ +"""add_quality_score_overall_leaderboard + +Revision ID: 8f4f6a9c6a31 +Revises: 036364ef81bd +Create Date: 2023-08-07 18:10:02.646046+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8f4f6a9c6a31' +down_revision = '036364ef81bd' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add quality_score column to overall leaderboard table + op.add_column( + "leaderboard_overall", + sa.Column( + "quality_score", + sa.DECIMAL, + nullable=True, + ), + ) + + +def downgrade() -> None: + # remove quality_score column from overall leaderboard table + op.drop_column( + "leaderboard_overall", + "quality_score", + ) diff --git a/backend/alembic/versions/2023-08-08-0547-4c8caa38e453-add_google_idp.py b/backend/alembic/versions/2023-08-08-0547-4c8caa38e453-add_google_idp.py new file mode 100644 index 0000000..65361ad --- /dev/null +++ b/backend/alembic/versions/2023-08-08-0547-4c8caa38e453-add_google_idp.py @@ -0,0 +1,36 @@ +"""add_google_idp + +Revision ID: 4c8caa38e453 +Revises: 8f4f6a9c6a31 +Create Date: 2023-08-08 05:47:20.374334+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4c8caa38e453' +down_revision = '8f4f6a9c6a31' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column('user', sa.Column('google_id', sa.String(), nullable=True)) + + # ensure that the email column is unique + op.create_unique_constraint('uq_user_email', 'user', ['email']) + + # ensure that the google_id column is unique + op.create_unique_constraint('uq_user_google_id', 'user', ['google_id']) + + +def downgrade() -> None: + op.drop_column('user', 'google_id') + + # Drop the unique constraint on email column + op.drop_constraint('uq_user_email', 'user') + + # Drop the unique constraint on google_id column + op.drop_constraint('uq_user_google_id', 'user') diff --git a/backend/alembic/versions/2023-08-17-0259-b9fb1a5e91f4-add_more_dataset_table_enums.py b/backend/alembic/versions/2023-08-17-0259-b9fb1a5e91f4-add_more_dataset_table_enums.py new file mode 100644 index 0000000..65f03ab --- /dev/null +++ b/backend/alembic/versions/2023-08-17-0259-b9fb1a5e91f4-add_more_dataset_table_enums.py @@ -0,0 +1,28 @@ +"""add_more_dataset_table_enums + +Revision ID: b9fb1a5e91f4 +Revises: 4c8caa38e453 +Create Date: 2023-08-17 02:59:20.054811+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b9fb1a5e91f4' +down_revision = '4c8caa38e453' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add an additional enum value to the task_type enum + # in the task table + op.execute("ALTER TYPE task_type ADD VALUE 'audit_crowdsourced_data';") + + +def downgrade() -> None: + # remove the additional enum value from the task_type enum + # in the task table + op.execute("ALTER TYPE task_type DROP VALUE 'audit_crowdsourced_data';") diff --git a/backend/alembic/versions/2023-08-17-0558-8aff6eda53a6-add_task_views.py b/backend/alembic/versions/2023-08-17-0558-8aff6eda53a6-add_task_views.py new file mode 100644 index 0000000..ad9e1aa --- /dev/null +++ b/backend/alembic/versions/2023-08-17-0558-8aff6eda53a6-add_task_views.py @@ -0,0 +1,94 @@ +"""add_task_views + +Revision ID: 8aff6eda53a6 +Revises: b9fb1a5e91f4 +Create Date: 2023-08-17 05:58:36.643735+00:00 + +""" +from alembic import op + +# revision identifiers, used by Alembic. +revision = '8aff6eda53a6' +down_revision = 'b9fb1a5e91f4' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + connection = op.get_bind() + connection.execute( + """ + CREATE OR REPLACE VIEW public.task_1_submission AS + SELECT + id, + submitted_by, + submitted_prompt, + submitted_completion, + prompt_edited, + completion_edited, + prompt_rating, + completion_rating, + created_at, + edit_distance + FROM + public.task_audit + UNION ALL + SELECT + id, + submitted_by, + submitted_prompt, + submitted_completion, + prompt_edited, + completion_edited, + prompt_rating, + completion_rating, + created_at, + edit_distance + FROM + public.task_contribution_audit; + """ + ) + + connection.execute( + """ + CREATE OR REPLACE VIEW public.task_3_submission AS + SELECT + id, + edited_prompt_rating, + edited_completion_rating, + improved_prompt, + improved_completion, + improvement_feedback, + submitted_by, + created_at + FROM + public.task_audit_review + UNION ALL + SELECT + id, + edited_prompt_rating, + edited_completion_rating, + improved_prompt, + improved_completion, + improvement_feedback, + submitted_by, + created_at + FROM + public.task_contribution_audit_review; + """ + ) + + +def downgrade() -> None: + connection = op.get_bind() + connection.execute( + """ + DROP VIEW public.task_1_submission; + """ + ) + + connection.execute( + """ + DROP VIEW public.task_3_submission; + """ + ) diff --git a/backend/alembic/versions/2023-08-23-0958-b5cbc7a4fb61-add_contrib_details_to_task_3.py b/backend/alembic/versions/2023-08-23-0958-b5cbc7a4fb61-add_contrib_details_to_task_3.py new file mode 100644 index 0000000..a37c347 --- /dev/null +++ b/backend/alembic/versions/2023-08-23-0958-b5cbc7a4fb61-add_contrib_details_to_task_3.py @@ -0,0 +1,99 @@ +"""add_contrib_details_to_task_3 + +Revision ID: b5cbc7a4fb61 +Revises: 8aff6eda53a6 +Create Date: 2023-08-23 09:58:06.051864+00:00 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'b5cbc7a4fb61' +down_revision = '8aff6eda53a6' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Drop the existing view + op.execute("DROP VIEW IF EXISTS public.task_3_submission;") + + # Create the updated view + op.execute(""" + CREATE OR REPLACE VIEW public.task_3_submission AS + SELECT + tar.id, + tar.edited_prompt_rating, + tar.edited_completion_rating, + tar.improved_prompt, + tar.improved_completion, + tar.improvement_feedback, + tar.submitted_by, + tar.created_at, + ta.submitted_prompt AS edited_prompt, + ta.submitted_completion AS edited_completion, + t.prompt AS original_prompt, + t.completion AS original_completion + FROM + public.task_audit_review tar + INNER JOIN + public.task_audit ta ON tar.task_audit_id = ta.id + INNER JOIN + public.task t ON ta.task_id = t.id + + UNION ALL + + SELECT + tcar.id, + tcar.edited_prompt_rating, + tcar.edited_completion_rating, + tcar.improved_prompt, + tcar.improved_completion, + tcar.improvement_feedback, + tcar.submitted_by, + tcar.created_at, + tca.submitted_prompt AS edited_prompt, + tca.submitted_completion AS edited_completion, + tc.submitted_prompt AS original_prompt, + tc.submitted_completion AS original_completion + FROM + public.task_contribution_audit_review tcar + INNER JOIN + public.task_contribution_audit tca ON tcar.task_contribution_audit_id = tca.id + INNER JOIN + public.task_contribution tc ON tca.task_contribution_id = tc.id; + """) + + +def downgrade() -> None: + # Drop the updated view + op.execute("DROP VIEW public.task_3_submission;") + + # Create the original view + op.execute(""" + CREATE OR REPLACE VIEW public.task_3_submission AS + SELECT + id, + edited_prompt_rating, + edited_completion_rating, + improved_prompt, + improved_completion, + improvement_feedback, + submitted_by, + created_at + FROM + public.task_audit_review + UNION ALL + SELECT + id, + edited_prompt_rating, + edited_completion_rating, + improved_prompt, + improved_completion, + improvement_feedback, + submitted_by, + created_at + FROM + public.task_contribution_audit_review; + """) diff --git a/backend/alembic/versions/2023-08-25-2238-e7409f573d47-add_dialect_column_to_users_table.py b/backend/alembic/versions/2023-08-25-2238-e7409f573d47-add_dialect_column_to_users_table.py new file mode 100644 index 0000000..e377ae0 --- /dev/null +++ b/backend/alembic/versions/2023-08-25-2238-e7409f573d47-add_dialect_column_to_users_table.py @@ -0,0 +1,29 @@ +"""Add dialect column to users table + +Revision ID: e7409f573d47 +Revises: b5cbc7a4fb61 +Create Date: 2023-08-25 22:38:57.293462+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e7409f573d47' +down_revision = 'b5cbc7a4fb61' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Add the dialect column to the users table, limited to 256 characters + op.add_column( + 'user', + sa.Column('dialect', sa.String(length=256)) + ) + + +def downgrade() -> None: + # Drop the dialect column from the users table + op.drop_column('user', 'dialect') diff --git a/backend/alembic/versions/2023-08-27-2240-ba32cbdb7ef4-drop_audit_review_limit.py b/backend/alembic/versions/2023-08-27-2240-ba32cbdb7ef4-drop_audit_review_limit.py new file mode 100644 index 0000000..38d64f3 --- /dev/null +++ b/backend/alembic/versions/2023-08-27-2240-ba32cbdb7ef4-drop_audit_review_limit.py @@ -0,0 +1,26 @@ +"""drop_audit_review_limit + +Revision ID: ba32cbdb7ef4 +Revises: e7409f573d47 +Create Date: 2023-08-27 22:40:11.836705+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ba32cbdb7ef4' +down_revision = 'e7409f573d47' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # drop the audit_review_limit table + op.drop_table('audit_review_limit') + + +def downgrade() -> None: + # no need to downgrade. this is a one-way migration. + pass diff --git a/backend/alembic/versions/2023-08-29-2317-476d8e005a09-change_dialect_column_to_list_of_strings.py b/backend/alembic/versions/2023-08-29-2317-476d8e005a09-change_dialect_column_to_list_of_strings.py new file mode 100644 index 0000000..c637616 --- /dev/null +++ b/backend/alembic/versions/2023-08-29-2317-476d8e005a09-change_dialect_column_to_list_of_strings.py @@ -0,0 +1,39 @@ +"""Change dialect column to list of strings + +Revision ID: 476d8e005a09 +Revises: e7409f573d47 +Create Date: 2023-08-29 23:17:33.706542+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '476d8e005a09' +down_revision = 'ba32cbdb7ef4' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Drop the old dialect column from the users table + op.drop_column('user', 'dialect') + + # Add the new dialects array column to the users table + op.add_column( + 'user', + sa.Column('dialects', sa.ARRAY(sa.String(50))) + ) + + +def downgrade() -> None: + # Drop the dialects array column from the users table + op.drop_column('user', 'dialects') + + # Add back the old dialect column to the users table + op.add_column( + 'user', + sa.Column('dialect', sa.String(length=256)) + ) + diff --git a/backend/alembic/versions/2023-08-30-2310-2ba53ea38f6c-add_iban_language.py b/backend/alembic/versions/2023-08-30-2310-2ba53ea38f6c-add_iban_language.py new file mode 100644 index 0000000..00dffec --- /dev/null +++ b/backend/alembic/versions/2023-08-30-2310-2ba53ea38f6c-add_iban_language.py @@ -0,0 +1,36 @@ +"""add_iban_language + +Revision ID: 2ba53ea38f6c +Revises: 476d8e005a09 +Create Date: 2023-08-30 23:10:59.505238+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2ba53ea38f6c' +down_revision = '476d8e005a09' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add Iban as a language, for task 2 support + # https://iso639-3.sil.org/code/iba + op.execute( + """ + INSERT INTO language_code (code, name, character_code, direction) + VALUES ('iba', 'Iban', 'Latn', 'ltr'); + """ + ) + + +def downgrade() -> None: + op.execute( + """ + DELETE FROM language_code + WHERE code = 'iba'; + """ + ) diff --git a/backend/alembic/versions/2023-09-13-1527-60808d189e69-update_sundanese_char_code.py b/backend/alembic/versions/2023-09-13-1527-60808d189e69-update_sundanese_char_code.py new file mode 100644 index 0000000..181e43d --- /dev/null +++ b/backend/alembic/versions/2023-09-13-1527-60808d189e69-update_sundanese_char_code.py @@ -0,0 +1,26 @@ +"""update_sundanese_char_code + +Revision ID: 60808d189e69 +Revises: 2ba53ea38f6c +Create Date: 2023-09-13 15:27:36.704062+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '60808d189e69' +down_revision = '2ba53ea38f6c' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # set the character_code for sundanese from Arab to Latn + op.execute("UPDATE language_code SET character_code = 'Latn' WHERE name = 'Sundanese'") + + +def downgrade() -> None: + # set the character_code for sundanese from Latn to Arab + op.execute("UPDATE language_code SET character_code = 'Arab' WHERE name = 'Sundanese'") diff --git a/backend/alembic/versions/2023-09-17-0351-a7ea807723e4-add_sarawak_malay.py b/backend/alembic/versions/2023-09-17-0351-a7ea807723e4-add_sarawak_malay.py new file mode 100644 index 0000000..9f0c7d7 --- /dev/null +++ b/backend/alembic/versions/2023-09-17-0351-a7ea807723e4-add_sarawak_malay.py @@ -0,0 +1,35 @@ +"""add_sarawak_malay + +Revision ID: a7ea807723e4 +Revises: 60808d189e69 +Create Date: 2023-09-17 03:51:36.719887+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a7ea807723e4' +down_revision = '60808d189e69' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add Sarawak Malay as a language, for task 2 support + op.execute( + """ + INSERT INTO language_code (code, name, character_code, direction) + VALUES ('poz', 'Sarawak Malay', 'Latn', 'ltr'); + """ + ) + + +def downgrade() -> None: + op.execute( + """ + DELETE FROM language_code + WHERE code = 'poz'; + """ + ) diff --git a/backend/alembic/versions/2023-09-20-0316-b389845cf201-add_quality_and_aya_score_per_language.py b/backend/alembic/versions/2023-09-20-0316-b389845cf201-add_quality_and_aya_score_per_language.py new file mode 100644 index 0000000..fc292e1 --- /dev/null +++ b/backend/alembic/versions/2023-09-20-0316-b389845cf201-add_quality_and_aya_score_per_language.py @@ -0,0 +1,57 @@ +"""add_quality_and_aya_score_per_language + +Revision ID: b389845cf201 +Revises: a7ea807723e4 +Create Date: 2023-09-20 03:16:16.357096+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b389845cf201' +down_revision = 'a7ea807723e4' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # add quality_score column to language leaderboard table + op.add_column( + "leaderboard_by_language", + sa.Column( + "quality_score", + sa.DECIMAL, + nullable=True, + ), + ) + + # add blended rank and blended points to the language leaderboard table + # as integers + op.add_column( + 'leaderboard_by_language', + sa.Column('blended_rank', + sa.Integer(), + nullable=True, + ), + ) + op.add_column( + 'leaderboard_by_language', + sa.Column( + 'blended_points', + sa.Integer(), + nullable=True, + ), + ) + + +def downgrade() -> None: + # remove quality_score column from language leaderboard table + op.drop_column( + "leaderboard_by_language", + "quality_score", + ) + + op.drop_column('leaderboard_by_language', 'blended_rank') + op.drop_column('leaderboard_by_language', 'blended_points') diff --git a/backend/alembic/versions/2023-11-18-0420-672360a33e72-identify_primary_101_langs.py b/backend/alembic/versions/2023-11-18-0420-672360a33e72-identify_primary_101_langs.py new file mode 100644 index 0000000..e3af165 --- /dev/null +++ b/backend/alembic/versions/2023-11-18-0420-672360a33e72-identify_primary_101_langs.py @@ -0,0 +1,150 @@ +"""identify_primary_101_langs + +Revision ID: 672360a33e72 +Revises: b389845cf201 +Create Date: 2023-11-18 04:20:21.574869+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '672360a33e72' +down_revision = 'b389845cf201' +branch_labels = None +depends_on = None + + +primary_langs = [ + 'Afrikaans', + 'Albanian', + 'Amharic', + 'Standard Arabic', + 'Mesopotamian Arabic', + 'Moroccan Arabic', + 'Najdi Arabic', + 'North Levantine Arabic', + 'South Levantine Arabic', + "Ta''izzi Arabic", + 'Tunisian Arabic', + 'Armenian', + 'North Azerbaijani', + 'South Azerbaijani', + 'Basque', + 'Belarusian', + 'Bengali', + 'Bulgarian', + 'Burmese', + 'Catalan', + 'Cebuano', + 'Nyanja', + 'Simplified Chinese', + 'Traditional Chinese', + 'Corsican', + 'Czech', + 'Danish', + 'Dutch', + 'English', + 'Esperanto', + 'Estonian', + 'Filipino', + 'Finnish', + 'French', + 'Galician', + 'Georgian', + 'German', + 'Modern Greek (1453-)', + 'Gujarati', + 'Haitian Creole', + 'Hausa', + 'Hawaiian', + 'Hebrew', + 'Hindi', + 'Hmong', + 'Hungarian', + 'Icelandic', + 'Igbo', + 'Indonesian', + 'Irish', + 'Italian', + 'Japanese', + 'Javanese', + 'Kannada', + 'Kazakh', + 'Central Khmer', + 'Korean', + 'Kurdish', + 'Kyrgyz', + 'Lao', + 'Latin', + 'Standard Latvian', + 'Lithuanian', + 'Luxembourgish', + 'Macedonian', + 'Plateau Malagasy', + 'Malay', + 'Malayalam', + 'Maltese', + 'Maori', + 'Marathi', + 'Mongolian', + 'Nepali', + 'Norwegian', + 'Pashto', + 'Iranian Persian', + 'Polish', + 'Portuguese', + 'Panjabi', + 'Romanian', + 'Russian', + 'Samoan', + 'Scottish Gaelic', + 'Serbian', + 'Shona', + 'Sindhi', + 'Sinhala', + 'Slovak', + 'Slovenian', + 'Somali', + 'Sotho', + 'Spanish', + 'Sundanese', + 'Swahili', + 'Swedish', + 'Tajik', + 'Tamil', + 'Telugu', + 'Thai', + 'Turkish', + 'Ukrainian', + 'Urdu', + 'Northern Uzbek', + 'Vietnamese', + 'Welsh', + 'Western Frisian', + 'Xhosa', + 'Eastern Yiddish', + 'Yoruba', + 'Zulu', +] + +def upgrade() -> None: + # add a boolean column to the language_code table to identify + # which languages are part of our original 101 languages + op.add_column('language_code', sa.Column('is_primary_101', sa.Boolean(), nullable=True)) + + # set the boolean column to true for all primary languages + op.execute( + "UPDATE language_code SET is_primary_101 = TRUE WHERE name IN ('{}')".format("', '".join(primary_langs)) + ) + + # set the boolean column to false for all non-primary languages + op.execute( + "UPDATE language_code SET is_primary_101 = FALSE WHERE is_primary_101 IS NULL" + ) + + + +def downgrade() -> None: + op.drop_column('language_code', 'is_primary_101') diff --git a/backend/alembic/versions/2023-12-24-0429-5b80958da127-fix_primary_101_langs.py b/backend/alembic/versions/2023-12-24-0429-5b80958da127-fix_primary_101_langs.py new file mode 100644 index 0000000..118dcd4 --- /dev/null +++ b/backend/alembic/versions/2023-12-24-0429-5b80958da127-fix_primary_101_langs.py @@ -0,0 +1,30 @@ +"""fix_primary_101_langs + +Revision ID: 5b80958da127 +Revises: 672360a33e72 +Create Date: 2023-12-24 04:29:59.634296+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5b80958da127' +down_revision = '672360a33e72' +branch_labels = None +depends_on = None + +def upgrade() -> None: + # update the list of languages in the primary 101 that we missed due to + # a bug in the identify_primary_101_langs migration. the naming of the languages + # was not accurate so we missed some languages. + op.execute( + "UPDATE language_code SET is_primary_101 = TRUE WHERE name IN ('Haitian', 'Southern Sotho', 'Norwegian Bokmål', 'Nepali (individual language)', 'Swahili (individual language)', 'Tagalog', 'Northern Sotho', 'Egyptian Arabic', 'Standard Malay', 'Central Kurdish', 'Southern Pashto', 'Tosk Albanian', 'Ta''izzi-Adeni Arabic')" + ) + + +def downgrade() -> None: + op.execute( + "UPDATE language_code SET is_primary_101 = FALSE WHERE name IN ('Haitian', 'Southern Sotho', 'Norwegian Bokmål', 'Nepali (individual language)', 'Swahili (individual language)', 'Tagalog', 'Northern Sotho', 'Egyptian Arabic', 'Standard Malay', 'Central Kurdish', 'Southern Pashto', 'Tosk Albanian', 'Ta''izzi-Adeni Arabic')" + ) diff --git a/backend/instruct_multilingual/__init__.py b/backend/instruct_multilingual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/instruct_multilingual/api/__init__.py b/backend/instruct_multilingual/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/instruct_multilingual/api/v1/__init__.py b/backend/instruct_multilingual/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/instruct_multilingual/api/v1/api.py b/backend/instruct_multilingual/api/v1/api.py new file mode 100644 index 0000000..541cd46 --- /dev/null +++ b/backend/instruct_multilingual/api/v1/api.py @@ -0,0 +1,20 @@ +# https://fastapi.tiangolo.com/tutorial/bigger-applications/ +from fastapi import APIRouter + +from instruct_multilingual.api.v1 import ( + leaderboards, + tasks, + users, + auth, + auth_discord, + auth_google, +) + +api_router = APIRouter() + +api_router.include_router(leaderboards.router, prefix="/leaderboards", tags=["leaderboards"]) +api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"]) +api_router.include_router(users.router, prefix="/users", tags=["users"]) +api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) +api_router.include_router(auth_discord.router, prefix="/auth/discord", tags=["auth"]) +api_router.include_router(auth_google.router, prefix="/auth/google", tags=["auth"]) diff --git a/backend/instruct_multilingual/api/v1/auth.py b/backend/instruct_multilingual/api/v1/auth.py new file mode 100644 index 0000000..328a9a5 --- /dev/null +++ b/backend/instruct_multilingual/api/v1/auth.py @@ -0,0 +1,74 @@ +import logging + +import jwt +from fastapi import APIRouter +from fastapi import HTTPException, Header, Query +from fastapi import Response + +from instruct_multilingual.api.v1.auth_discord import get_discord_user_info +from instruct_multilingual.api.v1.auth_google import get_google_user_info +from instruct_multilingual.config import get_settings + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +router = APIRouter() +settings = get_settings() + + +@router.get("/authenticated") +async def authenticated(authorization: str = Header(None), auth_provider: str = Query(None)): + """ + Check if a user is authenticated. + """ + if authorization is None: + logger.info('user is not authenticated') + return {"is_authenticated": False} + + jwt_access_token = authorization[len("Bearer "):] + + logger.info('checking if user is already authenticated...') + logger.info(f'headers: {authorization}') + logger.info(f'jwt_access_token: {jwt_access_token}') + + try: + payload = jwt.decode(jwt=jwt_access_token, key=settings.jwt_secret, algorithms=[settings.jwt_algorithm]) + access_token = payload['sub'] + except jwt.ExpiredSignatureError: + raise HTTPException(status_code=401, detail="Token has expired") + except jwt.InvalidTokenError: + raise HTTPException(status_code=401, detail="Invalid token") + + if auth_provider == 'google': + print("auth_provider is google") + try: + await get_google_user_info(access_token) + authenticated = True + print("authenticated is true") + except HTTPException: + authenticated = False + print("authenticated is false") + elif auth_provider == 'discord': + try: + await get_discord_user_info(access_token) + authenticated = True + except HTTPException: + authenticated = False + else: + authenticated = False + raise HTTPException(status_code=401, detail="Invalid auth provider") + + logger.info(f'user is authenticated? {authenticated}') + return {"is_authenticated": authenticated} + + +@router.get("/logout") +async def logout(response: Response): + """ + Logout the user. + """ + # Redirect to frontend + return {"redirect_url": f'{settings.for_ai_url}'} diff --git a/backend/instruct_multilingual/api/v1/auth_discord.py b/backend/instruct_multilingual/api/v1/auth_discord.py new file mode 100644 index 0000000..8ec84df --- /dev/null +++ b/backend/instruct_multilingual/api/v1/auth_discord.py @@ -0,0 +1,285 @@ +import datetime +import logging +from typing import Optional + +import jwt +from aiohttp import ClientSession +from fastapi import HTTPException, APIRouter +from fastapi import Response +from fastapi import status +from sqlalchemy.dialects.postgresql import insert +from sqlmodel import Session +from sqlmodel import select + +from instruct_multilingual import db +from instruct_multilingual.config import get_settings +from instruct_multilingual.exceptions import InstructMultilingualAPIError +from instruct_multilingual.models.user import User +from instruct_multilingual.schemas.user import UserResponseSchema + +DISCORD_SCOPES = 'identify guilds email' + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +router = APIRouter() +settings = get_settings() + + +async def get_discord_access_token(code: str) -> dict: + """ + Get the access token using the authorization code. + """ + # Prepare data for the POST request + data = { + "client_id": settings.discord_client_id, + "client_secret": settings.discord_client_secret, + "grant_type": "authorization_code", + "code": code, + "redirect_uri": settings.discord_redirect_uri, + "scope": DISCORD_SCOPES, + } + + logger.info('retrieving discord access token...') + # Send a POST request to get the access token + async with ClientSession() as session: + async with session.post(f"{settings.discord_api_base_url}/oauth2/token", data=data) as response: + if response.status != 200: + text = await response.text() + logger.error(f"error while retrieving access token from discord api: {text}") + raise HTTPException(status_code=response.status, detail="Invalid code") + + logger.info('access token retrieved!') + return await response.json() + + +async def get_discord_user_info(access_token: str) -> dict: + """ + Make an API request to Discord using the access token. + """ + # Prepare the headers for the GET request + headers = {"Authorization": f"Bearer {access_token}"} + url = f"{settings.discord_api_base_url}/users/@me" + + logger.info(f'making API request to discord url {url} with access token {access_token}...') + + # Send a GET request to the specified path + async with ClientSession() as session: + async with session.get(url, headers=headers) as response: + if response.status != 200: + text = await response.text() + logger.error(f"error while retrieving user info from discord api: {text}") + raise HTTPException(status_code=response.status, detail="API error") + return await response.json() + + +@router.get("/login") +async def login(): + """ + Redirect to Discord OAuth URL. + """ + oauth_url = ( + f"https://discord.com/oauth2/authorize?client_id={settings.discord_client_id}&redirect_uri={settings.discord_redirect_uri}" + f"&response_type=code&scope={DISCORD_SCOPES.replace(' ', '%20')}" + ) + + return {"url": oauth_url} + + +@router.get("/callback") +async def callback(response: Response, code: Optional[str] = None, error: Optional[str] = None, + error_description: Optional[str] = None): + """ + Callback endpoint for Discord OAuth. Register the user if they don't exist. + """ + if error: + # Redirect to frontend in case of error + response.status_code = 302 + response.headers["Location"] = f'{settings.for_ai_url}?message={error_description}' + return response + + if code: + try: + tokens = await get_discord_access_token(code) + except Exception as e: + # Redirect to frontend in case of another type of error + response.status_code = 302 + response.headers["Location"] = f'{settings.for_ai_url}?message={str(e)}' + return response + + access_token, refresh_token = tokens["access_token"], tokens["refresh_token"] + + logger.info(f'access_token: {access_token}') + logger.info('making API request for user data...') + user_data = await get_discord_user_info(access_token) + logger.info(f'user data retrieved: {user_data}') + + discord_user_id = user_data['id'] + discord_username = user_data['username'] + discord_avatar_id = user_data['avatar'] + discord_email = user_data['email'] + + if discord_avatar_id is None: + # Use the default clyde image if the user has no avatar + image_url = "https://cdn.discordapp.com/embed/avatars/0.png" + else: + image_url = f"https://cdn.discordapp.com/avatars/{discord_user_id}/{discord_avatar_id}.png" + + logger.info('attempting to register user...') + with Session(db.engine) as session: + # NOTE: this is not very pretty because we weren't storing + # discord ids or emails before the username change rollout, so we have + # to check if the user exists by username or by discord id to account + # for existing users who may have changed their username even after + # we've stored their discord id. + by_email_statement = select(User).where( + User.email == discord_email, + ) + by_discord_id_statement = select(User).where( + User.discord_id == discord_user_id, + ) + by_username_statement = select(User).where( + User.username == discord_username, + ) + user_via_email = session.execute(by_email_statement).first() + user_via_discord_id = session.execute(by_discord_id_statement).first() + user_via_username = session.execute(by_username_statement).first() + + if user_via_email is None and user_via_discord_id is None and user_via_username is None: + logger.info(f'user {discord_username} does not exist. registering...') + try: + statement = ( + insert(User) + .values( + discord_id=discord_user_id, + username=discord_username, + email=discord_email, + image_url=image_url, + ) + .on_conflict_do_update( + index_elements=["discord_id"], + set_=dict( + email=discord_email, + username=discord_username, + image_url=image_url, + ), + ) + .returning(User) + ) + + user = session.execute(statement).one() + session.commit() + except Exception as e: + logger.exception('ran into an issue with registering a user') + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error creating user", + ) + + logger.info(f'registered user {user} successfully!') + + is_registered = False + elif user_via_username is not None: + # User is already registered via Discord, and we have their username, but we don't + # have their email or Discord ID. + # NOTE: due to the discord rollout, and the fact we weren't storing + # emails / discord ids in the past, we need to update the email and + # discord id if it's not there for existing users + user = user_via_username[0] + logger.info(f'user {user} is already registered via username') + try: + logger.info(f'updating email and discord id for user {user}') + user.discord_id = discord_user_id + user.email = discord_email + session.commit() + session.refresh(user) + except Exception as e: + logger.exception( + 'ran into an issue with updating email and ' + 'discord id of an existing user' + ) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error updating user email", + ) + else: + user_schema = UserResponseSchema(**user.__dict__) + + is_registered = True + elif user_via_discord_id is not None: + # User is already registered via Discord, and we have their Discord ID, but we don't + # have their email or username. + user = user_via_discord_id[0] + logger.info(f'user {user} is already registered and has a discord id') + + try: + logger.info(f'updating username for user {user}') + user.username = discord_username + user.email = discord_email + session.commit() + session.refresh(user) + except Exception as e: + logger.exception( + 'ran into an issue with updating username of an existing user' + ) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error updating user username", + ) + else: + user_schema = UserResponseSchema(**user.__dict__) + + is_registered = True + else: + # User is already registered via Google, and we have their email, but we don't + # have their discord id or username. + user = user_via_email[0] + logger.info(f'user {user} is already registered and has an email') + + try: + logger.info(f'updating username for user {user}') + user.username = discord_username + user.discord_id = discord_user_id + session.commit() + session.refresh(user) + except Exception as e: + logger.exception( + 'ran into an issue with updating username of an existing user' + ) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error updating user username", + ) + else: + user_schema = UserResponseSchema(**user.__dict__) + + is_registered = True + + exp_time = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta( + seconds=settings.jwt_expiration_time) + + jwt_access_token = jwt.encode(payload={"sub": access_token, "exp": exp_time}, key=settings.jwt_secret, + algorithm=settings.jwt_algorithm) + jwt_refresh_token = jwt.encode(payload={"sub": refresh_token, "exp": exp_time}, key=settings.jwt_secret, + algorithm=settings.jwt_algorithm) + + is_complete_profile = False + if is_registered: + if user.language_codes is not None and user.language_codes != []: + is_complete_profile = True + + response.headers["Location"] = ( + f'{settings.for_ai_url}/callback?' + f'auth_provider=discord&' + f'access_token={jwt_access_token}&' + f'refresh_token={jwt_refresh_token}&' + f'user_id={user.id}&' + f'is_complete_profile={is_complete_profile}' + ) + + response.status_code = 302 + + return response diff --git a/backend/instruct_multilingual/api/v1/auth_google.py b/backend/instruct_multilingual/api/v1/auth_google.py new file mode 100644 index 0000000..fc0e229 --- /dev/null +++ b/backend/instruct_multilingual/api/v1/auth_google.py @@ -0,0 +1,230 @@ +import datetime +import hashlib +import logging +import re +from typing import Optional + +import jwt +from aiohttp import ClientSession +from fastapi import HTTPException, APIRouter +from fastapi import Response +from fastapi import status +from sqlalchemy.dialects.postgresql import insert +from sqlmodel import Session +from sqlmodel import select + +from instruct_multilingual import db +from instruct_multilingual.config import get_settings +from instruct_multilingual.exceptions import InstructMultilingualAPIError +from instruct_multilingual.models.user import User +from instruct_multilingual.schemas.user import UserResponseSchema + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +router = APIRouter() +settings = get_settings() + +GOOGLE_SCOPES = 'openid email profile' + + +@router.get("/login") +async def login(): + """ + Redirect to Google OAuth URL. + """ + oauth_url = ( + f"https://accounts.google.com/o/oauth2/v2/auth?client_id={settings.google_client_id}&redirect_uri={settings.google_redirect_uri}" + f"&response_type=code&scope={GOOGLE_SCOPES.replace(' ', '%20')}&prompt=consent&access_type=offline" + ) + + return {"url": oauth_url} + + +async def get_google_access_token(code: str) -> dict: + """ + Get the access token using the authorization code. + """ + # Prepare data for the POST request + data = { + "client_id": settings.google_client_id, + "client_secret": settings.google_client_secret, + "grant_type": "authorization_code", + "code": code, + "redirect_uri": settings.google_redirect_uri, + } + + logger.info('retrieving google access token...') + # Send a POST request to get the access token + async with ClientSession() as session: + async with session.post("https://oauth2.googleapis.com/token", data=data) as response: + if response.status != 200: + text = await response.text() + logger.error(f"error while retrieving access token from google api: {text}") + raise HTTPException(status_code=response.status, detail="Invalid code") + + logger.info('access token retrieved!') + return await response.json() + + +async def get_google_user_info(access_token: str) -> dict: + """ + Get the user's information using the access token. + """ + # Prepare the headers for the GET request + headers = {"Authorization": f"Bearer {access_token}"} + + logger.info(f'making API request to google with access token {access_token}...') + + # Send a GET request to the specified path + async with ClientSession() as session: + async with session.get("https://www.googleapis.com/oauth2/v1/userinfo", headers=headers) as response: + if response.status != 200: + text = await response.text() + logger.error(f"error while retrieving user info from google api: {text}") + raise HTTPException(status_code=response.status, detail="API error") + + logger.info('user info retrieved!') + return await response.json() + + +@router.get("/callback") +async def callback(response: Response, code: Optional[str] = None, error: Optional[str] = None, + error_description: Optional[str] = None): + """ + Callback endpoint for Google OAuth. + """ + if error: + # Redirect to frontend in case of error + response.status_code = 302 + response.headers["Location"] = f'{settings.for_ai_url}?message={error_description}' + return response + + if code: + try: + tokens = await get_google_access_token(code) + except Exception as e: + # Redirect to frontend in case of another type of error + response.status_code = 302 + response.headers["Location"] = f'{settings.for_ai_url}?message={str(e)}' + return response + + access_token, refresh_token = tokens["access_token"], tokens["refresh_token"] + + logger.info(f'access_token: {access_token}') + logger.info('making API request for user data...') + user_data = await get_google_user_info(access_token) + logger.info(f'user data retrieved: {user_data}') + + google_user_id = user_data['id'] + google_picture = user_data['picture'] + google_email = user_data['email'] + + # Create a hash using SHA-256 and the google_user_id, then take the first 8 characters + hash_object = hashlib.sha256(google_user_id.encode()) + random_hash = hash_object.hexdigest()[:8] + + # Combine the name and random hash + cleaned_name = re.sub(r'\W+', '', user_data["name"]) + derived_username = f'{cleaned_name}_{random_hash}' + + if google_picture is None: + # Use the default image if the user has no avatar + image_url = "https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&f=y" + # if the google picture URL is too long then just use the default image as well + elif len(google_picture) > 1000: + image_url = "https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&f=y" + else: + image_url = google_picture + + logger.info('attempting to register user...') + with Session(db.engine) as session: + by_email_statement = select(User).where( + User.email == google_email, + ) + user_via_email = session.execute(by_email_statement).first() + + if user_via_email is None: + logger.info(f'user {derived_username} does not exist. registering...') + try: + statement = ( + insert(User) + .values( + google_id=google_user_id, + username=derived_username, + email=google_email, + image_url=image_url, + ) + .on_conflict_do_update( + index_elements=["google_id"], + set_=dict( + email=google_email, + username=derived_username, + image_url=image_url, + ), + ) + .returning(User) + ) + + user = session.execute(statement).one() + session.commit() + except Exception as e: + logger.exception('ran into an issue with registering a user') + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error creating user", + ) + + logger.info(f'registered user {user} successfully!') + + is_registered = False + else: + user = user_via_email[0] + logger.info(f'user {user} is already registered via email') + + try: + logger.info(f'updating google id for user {user}') + user.google_id = google_user_id + session.commit() + session.refresh(user) + except Exception as e: + logger.exception( + 'ran into an issue with updating google id of an existing user' + ) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error updating user email", + ) + else: + user_schema = UserResponseSchema(**user.__dict__) + + is_registered = True + + exp_time = (datetime.datetime.now(tz=datetime.timezone.utc) + + datetime.timedelta(seconds=settings.jwt_expiration_time)) + + jwt_access_token = jwt.encode(payload={"sub": access_token, "exp": exp_time}, key=settings.jwt_secret, + algorithm=settings.jwt_algorithm) + jwt_refresh_token = jwt.encode(payload={"sub": refresh_token, "exp": exp_time}, key=settings.jwt_secret, + algorithm=settings.jwt_algorithm) + + is_complete_profile = False + if is_registered: + if user.language_codes is not None and user.language_codes != []: + is_complete_profile = True + + response.headers["Location"] = ( + f'{settings.for_ai_url}/callback?' + f'auth_provider=google&' + f'access_token={jwt_access_token}&' + f'refresh_token={jwt_refresh_token}&' + f'user_id={user.id}&' + f'is_complete_profile={is_complete_profile}' + ) + + response.status_code = 302 + + return response diff --git a/backend/instruct_multilingual/api/v1/leaderboards.py b/backend/instruct_multilingual/api/v1/leaderboards.py new file mode 100644 index 0000000..4a417a7 --- /dev/null +++ b/backend/instruct_multilingual/api/v1/leaderboards.py @@ -0,0 +1,265 @@ +import logging +from typing import List, Optional +from uuid import UUID + +from pydantic import BaseModel + +from fastapi import APIRouter, HTTPException, status +from sqlmodel import Session, select + +from instruct_multilingual import db +from instruct_multilingual.models import ( + LeaderboardDaily, + LeaderboardWeekly, + LeaderboardByLanguage, + LeaderboardOverall, +) +from instruct_multilingual.schemas.leaderboard import ( + LeaderboardRecordList, + OverallLeaderboardRecordList, + LanguageLeaderboardRecordList, +) +from instruct_multilingual.exceptions import InstructMultilingualAPIError + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +router = APIRouter() + + +@router.get( + "/daily/{user_id}/{day}", + response_model=LeaderboardRecordList, + status_code=status.HTTP_200_OK, +) +def get_leaderboard_daily( + *, + user_id: UUID, + day: str, +): + """ + Returns the current daily leaderboard. + """ + logger.info(f"Retrieving daily leaderboard for {day}...") + + try: + with Session(db.engine) as session: + stmt = ( + select(LeaderboardDaily) + .where(LeaderboardDaily.day == day) + .order_by(LeaderboardDaily.rank) + ) + result = session.exec(stmt) + daily_leaderboard = result.fetchall() + except Exception as e: + logger.error(f"error retrieving daily leaderboard: {e}") + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="error retrieving daily leaderboard", + ) from e + + logger.debug(f"retrieved daily leaderboard: {daily_leaderboard}") + + if not daily_leaderboard: + return LeaderboardRecordList( + records=[], + current_user=None, + ) + + current_user = None + for record in daily_leaderboard: + if record.user_id == user_id: + current_user = record + break + + return LeaderboardRecordList( + records=daily_leaderboard, + current_user=current_user, + ) + + +@router.get( + "/weekly/{user_id}/{week_of}", + response_model=LeaderboardRecordList, + status_code=status.HTTP_200_OK, +) +def get_leaderboard_weekly( + *, + user_id: UUID, + week_of: str, +): + """ + Returns the current weekly leaderboard. + """ + try: + with Session(db.engine) as session: + stmt = ( + select(LeaderboardWeekly) + .where(LeaderboardWeekly.week_of == week_of) + .order_by(LeaderboardWeekly.rank) + ) + result = session.exec(stmt) + weekly_leaderboard = result.fetchall() + except Exception as e: + logger.error(f"error retrieving weekly leaderboard: {e}") + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="error retrieving weekly leaderboard", + ) from e + + logger.debug(f"retrieved weekly leaderboard: {weekly_leaderboard}") + + if not weekly_leaderboard: + return LeaderboardRecordList( + records=[], + current_user=None, + ) + + current_user = None + for record in weekly_leaderboard: + if record.user_id == user_id: + current_user = record + break + + return LeaderboardRecordList( + records=weekly_leaderboard, + current_user=current_user, + ) + + +@router.get( + "/language/{user_id}/{language}", + response_model=LanguageLeaderboardRecordList, + status_code=status.HTTP_200_OK, +) +def get_leaderboard_by_language( + *, + user_id: UUID, + language: str, + order_by: str = "blended_rank", +): + """ + Returns the current leaderboard based on the indicated language. + """ + if order_by not in ["rank", "blended_rank"]: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=( + f"invalid order_by value: {order_by}. " + "options are 'rank' or 'blended_rank'", + ), + ) + elif order_by == "rank": + order_by = LeaderboardByLanguage.rank + elif order_by == "blended_rank": + order_by = LeaderboardByLanguage.blended_rank + + try: + with Session(db.engine) as session: + stmt = ( + select(LeaderboardByLanguage) + .where(LeaderboardByLanguage.language_code == language) + .order_by(order_by) + ) + result = session.exec(stmt) + language_leaderboard = result.fetchall() + except Exception as e: + logger.error(f"error retrieving language leaderboard: {e}") + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="error retrieving language leaderboard", + ) from e + + logger.debug(f"retrieved language leaderboard: {language_leaderboard}") + + if not language_leaderboard: + return LanguageLeaderboardRecordList( + records=[], + current_user=None, + ) + + current_user = None + for record in language_leaderboard: + if record.user_id == user_id: + current_user = record + break + + return LanguageLeaderboardRecordList( + records=language_leaderboard, + current_user=current_user, + ) + + +@router.get( + "/{user_id}/overall", + response_model=OverallLeaderboardRecordList, + status_code=status.HTTP_200_OK, +) +def get_leaderboard_overall( + *, + user_id: UUID, + limit: int = 20, + offset: int = 0, + order_by: str = "blended_rank", +): + """ + Returns the current leaderboard overall (since the beginning of time). + """ + if order_by not in ["rank", "blended_rank"]: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=( + f"invalid order_by value: {order_by}. " + "options are 'rank' or 'blended_rank'", + ), + ) + elif order_by == "rank": + order_by = LeaderboardOverall.rank + elif order_by == "blended_rank": + order_by = LeaderboardOverall.blended_rank + + try: + with Session(db.engine) as session: + # get the top `limit` records, offset by `offset` + stmt = select( + LeaderboardOverall, + ).order_by( + order_by, + ).limit( + limit, + ).offset( + offset, + ) + result = session.exec(stmt) + overall_leaderboard = result.fetchall() + + total_count = session.query(LeaderboardOverall).count() + + # get the rank of the current user as well + stmt = select(LeaderboardOverall).where(LeaderboardOverall.user_id == user_id) + result = session.exec(stmt) + + current_user = result.first() + except Exception as e: + logger.error(f"error retrieving overall leaderboard: {e}") + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="error retrieving overall leaderboard", + ) from e + + logger.debug(f"retrieved overall leaderboard: {overall_leaderboard}") + + if not overall_leaderboard: + return OverallLeaderboardRecordList( + records=[], + current_user=None, + ) + + return OverallLeaderboardRecordList( + records=overall_leaderboard, + current_user=current_user, + total_count=total_count, + ) diff --git a/backend/instruct_multilingual/api/v1/tasks.py b/backend/instruct_multilingual/api/v1/tasks.py new file mode 100644 index 0000000..3e34eae --- /dev/null +++ b/backend/instruct_multilingual/api/v1/tasks.py @@ -0,0 +1,329 @@ +import logging +import random +from uuid import UUID + +from fastapi import APIRouter, Response, status + +from instruct_multilingual.exceptions import ( + InstructMultilingualAPIError, + IDNotFoundError, +) +from instruct_multilingual.services import ( + TaskService, + TaskAuditService, + TaskContributionService, + TaskAuditReviewService, + TaskContributionAuditReviewService, +) +from instruct_multilingual.schemas.task import ( + TaskSchema, + TaskListSchema, + TaskAuditRequestSchema, + TaskAuditResponseSchema, + TaskAuditGetResponseSchema, + TaskAuditGetResponseListSchema, + TaskContributionRequestSchema, + TaskContributionResponseSchema, + TaskContributionAuditRequestSchema, + TaskContributionAuditResponseSchema, + TaskAuditReviewRequestSchema, + TaskAuditReviewResponseSchema, + TaskContributionAuditReviewRequestSchema, + TaskContributionAuditReviewResponseSchema, +) + +MAX_TASKS = 20 + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +router = APIRouter() + +audit_service = TaskAuditService() +contribution_service = TaskContributionService() +task_service = TaskService() +task_audit_review_service = TaskAuditReviewService() +contribution_audit_review_service = TaskContributionAuditReviewService() + + +@router.get( + "/", + response_model=TaskListSchema, + status_code=status.HTTP_200_OK, +) +def get_tasks( + *, + user_id: UUID, + language_id: UUID, +): + """ + Returns a list of tasks for a given language and user. + + Tasks are first pulled from the `task_contribution` table. If there are no tasks, + then tasks are pulled from the `task` table. + + Tasks from the `task` table are tasks the Aya team have created through translation + or through existing xP3 data. + + Tasks from the `task_contribution` table are tasks + that have been contributed by users, and we want to prioritize these tasks over + tasks from the `task` table, to ensure that we are getting feedback on the + quality of user contributions. + """ + task_queue = [] + + try: + contributed_tasks = contribution_service.get_task_contributions_for_user_and_language_id( + user_id=user_id, + language_id=language_id, + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="an error occurred while retrieving tasks.", + ) + + # if there are task contributions, add them to the list + # of tasks we want to return + for task_contribution in contributed_tasks: + task = TaskSchema( + id=task_contribution.id, + prompt=task_contribution.submitted_prompt, + completion=task_contribution.submitted_completion, + is_contributed=True, + ) + task_queue.append(task) + + # if we haven't filled up our queue to the max, + # add generated tasks to fill it + num_to_fill = MAX_TASKS - len(task_queue) + + # otherwise, return `diff` tasks from the task table + try: + generated_tasks = task_service.get_active_tasks_for_user_and_language_id( + user_id=user_id, + language_id=language_id, + num_tasks=num_to_fill, + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="an error occurred while retrieving tasks.", + ) + + for generated_task in generated_tasks: + task = TaskSchema( + id=generated_task.id, + prompt=generated_task.prompt, + completion=generated_task.completion, + is_contributed=False, + ) + task_queue.append(task) + + # and if we have no tasks at all, 204 + if not task_queue: + logger.debug( + f"no tasks available for language id {language_id} and user {user_id}", + ) + # see: https://github.com/tiangolo/fastapi/issues/717 + return Response(status_code=status.HTTP_204_NO_CONTENT) + + task_list = TaskListSchema( + tasks=task_queue, + ) + return task_list + + +@router.get( + "/audits", + response_model=TaskAuditGetResponseListSchema, + status_code=status.HTTP_200_OK, +) +def get_task_audits( + *, + user_id: UUID, + language_id: UUID, +): + """ + Returns a list of task audits for a given language and user, + pulling from the `task_audit` and `task_contribution_audit` tables. + """ + try: + task_audits = audit_service.get_task_audits_for_user_and_language_id( + user_id=user_id, + language_id=language_id, + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="an error occurred while retrieving task audits.", + ) + + # if we have no task audits at all, 204 + if not task_audits: + logger.debug( + f"no task audits available for language id {language_id} and user {user_id}", + ) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + # map the task audit records to the response schema + task_audit_list = [ + TaskAuditGetResponseSchema( + id=task_audit.id, + contributed_by_id=task_audit.user_id, + contributed_by=task_audit.username, + contributed_by_image=task_audit.image_url, + original_prompt=task_audit.prompt, + original_completion=task_audit.completion, + edited_prompt=task_audit.submitted_prompt, + edited_completion=task_audit.submitted_completion, + is_contributed=task_audit.is_contributed, + ) + for task_audit in task_audits + ] + + return TaskAuditGetResponseListSchema( + task_audits=task_audit_list, + ) + + +@router.post( + "/submit-audit", + response_model=TaskAuditResponseSchema, + status_code=status.HTTP_201_CREATED, +) +def submit_task_audit(audit: TaskAuditRequestSchema): + """ + Creates a new task audit. + """ + try: + task_audit = audit_service.create_task_audit(**audit.dict()) + except IDNotFoundError as e: + raise InstructMultilingualAPIError( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e), + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"an error occurred while submitting task {audit.task_id}.", + ) + + return task_audit + + +@router.post( + "/submit-contribution", + response_model=TaskContributionResponseSchema, + status_code=status.HTTP_201_CREATED, +) +def submit_task_contribution(contribution: TaskContributionRequestSchema): + """ + Creates a new task contribution. + """ + try: + task_contribution = contribution_service.create_task_contribution( + **contribution.dict(), + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=( + "an error occurred while submitting task contribution from user " + f"{contribution.submitted_by}." + ), + ) + + return task_contribution + + +@router.post( + "/submit-contribution-audit", + response_model=TaskContributionAuditResponseSchema, + status_code=status.HTTP_201_CREATED, +) +def submit_task_contribution_audit( + contribution_audit: TaskContributionAuditRequestSchema, +): + """ + Creates a new task contribution. + """ + try: + task_contribution = contribution_service.create_task_contribution_audit( + **contribution_audit.dict(), + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=( + "an error occurred while submitting task contribution from user " + f"{contribution_audit.submitted_by}." + ), + ) + + return task_contribution + + +@router.post( + "/submit-audit-review", + response_model=TaskAuditReviewResponseSchema, + status_code=status.HTTP_201_CREATED, +) +def submit_task_audit_review( + audit_review: TaskAuditReviewRequestSchema, +): + """ + Creates a new task audit review. + """ + try: + task_audit_review = task_audit_review_service.create_task_audit_review( + **audit_review.dict(), + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=( + "an error occurred while submitting task audit review " + f"for audited task id {audit_review.task_audit_id}." + ), + ) + + return task_audit_review + + +@router.post( + "/submit-contribution-audit-review", + response_model=TaskContributionAuditReviewResponseSchema, + status_code=status.HTTP_201_CREATED, +) +def submit_task_contribution_audit_review( + contribution_audit_review: TaskContributionAuditReviewRequestSchema, +): + """ + Creates a new contribution audit review. + """ + try: + task_contribution_audit_review = contribution_audit_review_service.create_task_contribution_audit_review( + **contribution_audit_review.dict(), + ) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=( + "an error occurred while submitting task contribution audit review " + f"for audited task id {contribution_audit_review.task_contribution_audit_id}." + ), + ) + + return task_contribution_audit_review diff --git a/backend/instruct_multilingual/api/v1/users.py b/backend/instruct_multilingual/api/v1/users.py new file mode 100644 index 0000000..714ba42 --- /dev/null +++ b/backend/instruct_multilingual/api/v1/users.py @@ -0,0 +1,230 @@ +import logging +from uuid import UUID + +from fastapi import APIRouter, HTTPException, status +from psycopg2.extras import NumericRange +from sqlalchemy.dialects.postgresql import insert +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.exceptions import InstructMultilingualAPIError +from instruct_multilingual.models.country_code import CountryCode +from instruct_multilingual.models.language_code import LanguageCode +from instruct_multilingual.models.user import User +from instruct_multilingual.schemas.user import ( + UserCountryOptionsResponseSchema, + UserLanguageOptionsResponseSchema, + UserRequestSchema, + UserResponseSchema, + UserTaskContributionPaginationResponseSchema, +) +from instruct_multilingual.services.user_contribution_service import ( + UserContributionService, +) + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +router = APIRouter() + + +@router.post( + "/", + response_model=UserResponseSchema, + status_code=status.HTTP_201_CREATED, + name="create_user", +) +def create_user(user_request: UserRequestSchema): + """ + Creates a new user. + """ + try: + with Session(db.engine) as session: + statement = ( + insert(User) + .values( + username=user_request.username, + image_url=user_request.image_url, + ) + .on_conflict_do_update( + index_elements=["discord_id"], + set_=dict( + email=discord_email, + username=discord_username, + image_url=image_url, + ), + ) + .returning(User) + ) + + user = session.execute(statement).one() + session.commit() + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error creating user", + ) + + return user + + +@router.patch( + "/{user_id}", + response_model=UserResponseSchema, + status_code=status.HTTP_200_OK, +) +def update_user( + user_id: UUID, + user_request: UserRequestSchema, +): + """ + Updates a user. + """ + try: + with Session(db.engine) as session: + user = session.query(User).filter(User.id == user_id).first() + if not user: + raise InstructMultilingualAPIError( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"user not found", + ) + + user.image_url = user_request.image_url + user.country_code = user_request.country_code + user.language_codes = user_request.language_codes + + if user_request.age_range is not None: + min_age = user_request.age_range[0] + max_age = user_request.age_range[1] + user.age_range = NumericRange(min_age, max_age, bounds='[]') + else: + user.age_range = None + + user.gender = user_request.gender + user.dialects = user_request.dialects + + session.add(user) + session.commit() + session.refresh(user) + except InstructMultilingualAPIError as e: + raise e + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"error updating user", + ) + + return user + + +@router.get("/{user_id}", response_model=UserResponseSchema) +def get_user( + *, + user_id: UUID, +): + """ + Returns a user. + """ + try: + with Session(db.engine) as session: + user = session.get(User, user_id) + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"an error occurred while retrieving user {user_id}", + ) + + if user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"user {user_id} not found", + ) + + return user + + +@router.get("/options/languages", response_model=UserLanguageOptionsResponseSchema) +def get_language_options(): + """ + Returns a list of language options. + """ + try: + with Session(db.engine) as session: + # sort by name + languages = session.query(LanguageCode).order_by(LanguageCode.name).all() + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="an error occurred while retrieving language options", + ) + + if languages is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"no languages found", + ) + + return UserLanguageOptionsResponseSchema(options=languages) + + +@router.get("/options/countries", response_model=UserCountryOptionsResponseSchema) +def get_country_options(): + """ + Returns a list of country options. + """ + try: + with Session(db.engine) as session: + countries = session.query(CountryCode).all() + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="an error occurred while retrieving country options", + ) + + if countries is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"no countries found", + ) + + return UserCountryOptionsResponseSchema(options=countries) + + +@router.get( + "/contributions/{user_id}/{task_type}", + response_model=UserTaskContributionPaginationResponseSchema, + status_code=status.HTTP_200_OK +) +def get_contributions( + *, + user_id: UUID, + task_type: str, + page_number: int = 1, + page_size: int = 20 +): + """ + Returns the contributions of a user for a specific task. + """ + try: + if task_type == "task1" or task_type == "task2" or task_type == "task3": + return UserContributionService.fetch_task_contributions_by_user(task_type, user_id, page_number, page_size) + else: + raise InstructMultilingualAPIError( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid task type {task_type} provided.", + ) + + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"an error occurred while retrieving contributions for user {user_id}", + ) diff --git a/backend/instruct_multilingual/config.py b/backend/instruct_multilingual/config.py new file mode 100644 index 0000000..0f4235b --- /dev/null +++ b/backend/instruct_multilingual/config.py @@ -0,0 +1,57 @@ +import os +from typing import Optional + +from pydantic import BaseSettings + +class Settings(BaseSettings): + # must be set in .env.local, .env.staging, or .env.production + # in order for the backend to work + instruct_multilingual_app_db_uri: str + + jwt_secret: str + jwt_algorithm: str + jwt_expiration_time: int + + discord_api_base_url: str + discord_client_id: str + discord_client_secret: str + discord_redirect_uri: str + discord_webhook_url: Optional[str] + + google_client_id: str + google_client_secret: str + google_redirect_uri: str + + frontend_url: str + # for_ai_url is the URL of the frontend under the for.ai + # domain, vs. frontend_url which is the auto-generated + # gcp cloud run URL + for_ai_url: str + + app_name: str = "instruct-multilingual" + run_migrations_on_startup: bool = True + + class Config: + # https://fastapi.tiangolo.com/advanced/settings/ + # .env.local takes priority over .env.production + environment: str = os.environ.get("ENVIRONMENT") + + if environment == "local": + env_file = ".env.local" + elif environment == "staging": + env_file = ".env.staging" + elif environment == "production": + env_file = ".env.production" + elif environment == "test": + env_file = ".env.test" + elif not environment: + raise ValueError(f"ENVIRONMENT must be 'test', 'local', 'staging', or 'production', got empty string") + else: + raise ValueError(f"ENVIRONMENT must be 'test', 'local', 'staging', or 'production', got {environment}") + + config = Config() + + +def get_settings(): + settings = Settings() + return settings diff --git a/backend/instruct_multilingual/db.py b/backend/instruct_multilingual/db.py new file mode 100644 index 0000000..27fe19f --- /dev/null +++ b/backend/instruct_multilingual/db.py @@ -0,0 +1,13 @@ +import os + +from sqlmodel import create_engine +from instruct_multilingual.config import get_settings + +settings = get_settings() + +engine = create_engine( + settings.instruct_multilingual_app_db_uri, + client_encoding="utf8", + pool_pre_ping=True, + isolation_level="REPEATABLE READ", +) diff --git a/backend/instruct_multilingual/exceptions.py b/backend/instruct_multilingual/exceptions.py new file mode 100644 index 0000000..3ac2f23 --- /dev/null +++ b/backend/instruct_multilingual/exceptions.py @@ -0,0 +1,27 @@ +from fastapi import HTTPException + +class InstructMultilingualAPIError(HTTPException): + """ + Raised when an error occurs in the Instruct Multilingual API. + """ + def __init__(self, detail, status_code): + super().__init__(detail, status_code) + self.detail = detail + self.status_code = status_code + + +class DBIntegrityError(Exception): + """ + Raised when a database integrity error occurs. + """ + def __init__(self, message): + super().__init__(message) + + +class IDNotFoundError(DBIntegrityError): + """ + Raised when a specified ID is not found. + """ + def __init__(self, message): + super().__init__(message) + diff --git a/backend/instruct_multilingual/models/__init__.py b/backend/instruct_multilingual/models/__init__.py new file mode 100644 index 0000000..6cdf285 --- /dev/null +++ b/backend/instruct_multilingual/models/__init__.py @@ -0,0 +1,30 @@ +from instruct_multilingual.models.country_code import CountryCode +from instruct_multilingual.models.dataset import Dataset +from instruct_multilingual.models.language_code import LanguageCode +from instruct_multilingual.models.leaderboard import ( + LeaderboardDaily, + LeaderboardWeekly, + LeaderboardByLanguage, + LeaderboardOverall, +) +from instruct_multilingual.models.task import Task +from instruct_multilingual.models.task_audit import TaskAudit +from instruct_multilingual.models.task_contribution import TaskContribution +from instruct_multilingual.models.task_contribution_audit import TaskContributionAudit +from instruct_multilingual.models.user import User +from instruct_multilingual.models.task_audit_review import TaskAuditReview +from instruct_multilingual.models.task_contribution_audit_review import TaskContributionAuditReview + + +__all__ = [ + "CountryCode", + "Dataset", + "LanguageCode", + "Task", + "TaskAudit", + "TaskContribution", + "TaskContributionAudit", + "User", + "TaskAuditReview", + "TaskContributionAuditReview", +] diff --git a/backend/instruct_multilingual/models/country_code.py b/backend/instruct_multilingual/models/country_code.py new file mode 100644 index 0000000..a371dd1 --- /dev/null +++ b/backend/instruct_multilingual/models/country_code.py @@ -0,0 +1,44 @@ +from datetime import datetime +from typing import List, Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class CountryCode(SQLModel, table=True): + __tablename__ = "country_code" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + code: str = Field( + sa_column=sa.Column( + sa_psql.VARCHAR(2), + nullable=False, + ) + ) + name: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ) + ) + created_at: Optional[datetime] = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) + + __table_args__ = ( + sa.UniqueConstraint("code", name="country_code_code_key"), + sa.UniqueConstraint("name", name="country_code_name_key"), + ) diff --git a/backend/instruct_multilingual/models/dataset.py b/backend/instruct_multilingual/models/dataset.py new file mode 100644 index 0000000..9444c27 --- /dev/null +++ b/backend/instruct_multilingual/models/dataset.py @@ -0,0 +1,53 @@ +from datetime import datetime +from typing import List, Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, Relationship, SQLModel + + +class Dataset(SQLModel, table=True): + __tablename__ = "dataset" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + name: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ) + ) + language_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("language_code.id"), + nullable=False, + ), + ) + translated: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ), + ) + templated: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ), + ) + created_at: datetime = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) + tasks: List["Task"] = Relationship(back_populates="dataset") diff --git a/backend/instruct_multilingual/models/language_code.py b/backend/instruct_multilingual/models/language_code.py new file mode 100644 index 0000000..25ce536 --- /dev/null +++ b/backend/instruct_multilingual/models/language_code.py @@ -0,0 +1,51 @@ +from datetime import datetime +from typing import List, Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class LanguageCode(SQLModel, table=True): + __tablename__ = "language_code" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + code: str = Field( + sa_column=sa.Column( + sa_psql.VARCHAR(2), + nullable=False, + ) + ) + name: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ) + ) + character_code: str = Field( + sa_column=sa.Column( + sa_psql.VARCHAR(5), + nullable=False, + ) + ) + direction: Optional[str] = Field( + sa_column=sa.Column( + sa.Enum("ltr", "rtl", name="direction"), + nullable=True, + ) + ) + created_at: Optional[datetime] = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) diff --git a/backend/instruct_multilingual/models/leaderboard.py b/backend/instruct_multilingual/models/leaderboard.py new file mode 100644 index 0000000..0358676 --- /dev/null +++ b/backend/instruct_multilingual/models/leaderboard.py @@ -0,0 +1,260 @@ +from datetime import date, datetime +from typing import List, Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class LeaderboardDaily(SQLModel, table=True): + __tablename__ = "leaderboard_daily" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + user_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + username: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + languages: List[str] = Field( + sa_column=sa.Column( + sa.ARRAY(sa.Text), + ), + # NOTE: this is nullable temporarily while + # we don't have to backfill leaderboard data yet + nullable=True, + ) + image_url: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + day: date = Field( + sa_column=sa.Column( + sa.Date, + nullable=False, + ), + ) + rank: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + points: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + + +class LeaderboardWeekly(SQLModel, table=True): + __tablename__ = "leaderboard_weekly" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + user_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + username: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + languages: List[str] = Field( + sa_column=sa.Column( + sa.ARRAY(sa.Text), + ), + # NOTE: this is nullable temporarily while + # we don't have to backfill leaderboard data yet + nullable=True, + ) + image_url: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + week_of: date = Field( + sa_column=sa.Column( + sa.Date, + nullable=False, + ), + ) + rank: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + points: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + + +class LeaderboardByLanguage(SQLModel, table=True): + __tablename__ = "leaderboard_by_language" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + user_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + username: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + image_url: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + language: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + language_code: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + rank: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + points: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + blended_rank: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + blended_points: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + quality_score: Optional[float] = Field( + sa_column=sa.Column( + sa.DECIMAL, + nullable=True, + ), + ) + + +class LeaderboardOverall(SQLModel, table=True): + __tablename__ = "leaderboard_overall" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + user_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + username: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + languages: List[str] = Field( + sa_column=sa.Column( + sa.ARRAY(sa.Text), + ), + # NOTE: this is nullable temporarily while + # we don't have to backfill leaderboard data yet + nullable=True, + ) + image_url: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + rank: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + points: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + blended_rank: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + blended_points: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + quality_score: Optional[float] = Field( + sa_column=sa.Column( + sa.DECIMAL, + nullable=True, + ), + ) diff --git a/backend/instruct_multilingual/models/task.py b/backend/instruct_multilingual/models/task.py new file mode 100644 index 0000000..07c3a57 --- /dev/null +++ b/backend/instruct_multilingual/models/task.py @@ -0,0 +1,72 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, Relationship, SQLModel + + +class Task(SQLModel, table=True): + __tablename__ = "task" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + prompt: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ) + ) + completion: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + task_type: Optional[str] = Field( + sa_column=sa.Column( + sa.Enum("audit_translation", "audit_xp3", name="task_type"), + nullable=False, + ), + ) + dataset_id: Optional[UUID] = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("dataset.id"), + nullable=False, + ), + ) + language_id: Optional[UUID] = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("language_code.id"), + nullable=False, + ), + ) + created_at: Optional[datetime] = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) + + key_hash: Optional[str] = Field( + sa_column=sa.Column( + sa.String(length=64), + nullable=False, + ), + ) + + __table_args__ = ( + sa.UniqueConstraint('prompt', 'completion'), + ) + + dataset: Optional["Dataset"] = Relationship(back_populates="tasks") diff --git a/backend/instruct_multilingual/models/task_1_submission.py b/backend/instruct_multilingual/models/task_1_submission.py new file mode 100644 index 0000000..6784ef9 --- /dev/null +++ b/backend/instruct_multilingual/models/task_1_submission.py @@ -0,0 +1,71 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class Task1Submission(SQLModel, table=True): + __tablename__ = "task_1_submission" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + ), + ) + submitted_by: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + submitted_prompt: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ) + ) + submitted_completion: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + prompt_edited: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ) + ) + completion_edited: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ) + ) + prompt_rating: Optional[int] = Field( + sa_column=sa.Column( + sa.Integer, + ), + ) + completion_rating: Optional[int] = Field( + sa_column=sa.Column( + sa.Integer, + ), + ) + created_at: datetime = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) + edit_distance: Optional[float] = Field( + sa_column=sa.Column( + sa.Float, + ), + ) diff --git a/backend/instruct_multilingual/models/task_3_submission.py b/backend/instruct_multilingual/models/task_3_submission.py new file mode 100644 index 0000000..38191f4 --- /dev/null +++ b/backend/instruct_multilingual/models/task_3_submission.py @@ -0,0 +1,81 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql +from sqlmodel import Field, SQLModel + + +class Task3Submission(SQLModel, table=True): + __tablename__ = "task_3_submission" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + ), + ) + original_prompt: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + original_completion: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + edited_prompt: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + edited_completion: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + edited_prompt_rating: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ) + ) + edited_completion_rating: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ) + ) + improved_prompt: Optional[str] = Field( + sa_column=sa.Column( + sa.Text, + ) + ) + improved_completion: Optional[str] = Field( + sa_column=sa.Column( + sa.Text, + ) + ) + improvement_feedback: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ) + ) + submitted_by: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + created_at: datetime = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + ), + ) diff --git a/backend/instruct_multilingual/models/task_audit.py b/backend/instruct_multilingual/models/task_audit.py new file mode 100644 index 0000000..4bb5e5e --- /dev/null +++ b/backend/instruct_multilingual/models/task_audit.py @@ -0,0 +1,83 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class TaskAudit(SQLModel, table=True): + __tablename__ = "task_audit" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + task_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("task.id"), + nullable=False, + ), + ) + submitted_by: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("user.id"), + nullable=False, + ), + ) + submitted_prompt: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + submitted_completion: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + prompt_edited: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ), + ) + completion_edited: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ), + ) + prompt_rating: Optional[int] = Field( + sa_column=sa.Column( + sa.Integer, + nullable=True, + ), + ) + completion_rating: Optional[int] = Field( + sa_column=sa.Column( + sa.Integer, + nullable=True, + ), + ) + created_at: Optional[datetime] = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) + edit_distance: Optional[float] = Field( + sa_column=sa.Column( + sa.DECIMAL, + nullable=True, + ), + ) diff --git a/backend/instruct_multilingual/models/task_audit_review.py b/backend/instruct_multilingual/models/task_audit_review.py new file mode 100644 index 0000000..9d0ef7b --- /dev/null +++ b/backend/instruct_multilingual/models/task_audit_review.py @@ -0,0 +1,70 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class TaskAuditReview(SQLModel, table=True): + __tablename__ = "task_audit_review" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + task_audit_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("task_audit.id"), + nullable=False, + ), + ) + edited_prompt_rating: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + edited_completion_rating: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + improved_prompt: Optional[str] = Field( + sa_column=sa.Column( + sa.Text, + nullable=True, + ), + ) + improved_completion: Optional[str] = Field( + sa_column=sa.Column( + sa.Text, + nullable=True, + ), + ) + improvement_feedback: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + submitted_by: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + created_at: datetime = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) diff --git a/backend/instruct_multilingual/models/task_contribution.py b/backend/instruct_multilingual/models/task_contribution.py new file mode 100644 index 0000000..62a05da --- /dev/null +++ b/backend/instruct_multilingual/models/task_contribution.py @@ -0,0 +1,53 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class TaskContribution(SQLModel, table=True): + __tablename__ = "task_contribution" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + submitted_by: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("user.id"), + nullable=False, + ), + ) + submitted_prompt: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + submitted_completion: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + language_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("language_code.id"), + nullable=False, + ), + ) + created_at: Optional[datetime] = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) diff --git a/backend/instruct_multilingual/models/task_contribution_audit.py b/backend/instruct_multilingual/models/task_contribution_audit.py new file mode 100644 index 0000000..943a74c --- /dev/null +++ b/backend/instruct_multilingual/models/task_contribution_audit.py @@ -0,0 +1,83 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class TaskContributionAudit(SQLModel, table=True): + __tablename__ = "task_contribution_audit" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + task_contribution_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("task_contribution.id"), + nullable=False, + ), + ) + submitted_by: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("user.id"), + nullable=False, + ), + ) + submitted_prompt: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + submitted_completion: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + prompt_edited: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ), + ) + completion_edited: bool = Field( + sa_column=sa.Column( + sa.Boolean, + nullable=False, + ), + ) + prompt_rating: Optional[int] = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + completion_rating: Optional[int] = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + edit_distance: Optional[float] = Field( + sa_column=sa.Column( + sa.DECIMAL, + nullable=True, + ), + ) + created_at: Optional[datetime] = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) diff --git a/backend/instruct_multilingual/models/task_contribution_audit_review.py b/backend/instruct_multilingual/models/task_contribution_audit_review.py new file mode 100644 index 0000000..5b9d9d0 --- /dev/null +++ b/backend/instruct_multilingual/models/task_contribution_audit_review.py @@ -0,0 +1,70 @@ +from datetime import datetime +from typing import Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class TaskContributionAuditReview(SQLModel, table=True): + __tablename__ = "task_contribution_audit_review" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + task_contribution_audit_id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("task_contribution_audit.id"), + nullable=False, + ), + ) + edited_prompt_rating: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + edited_completion_rating: int = Field( + sa_column=sa.Column( + sa.Integer, + nullable=False, + ), + ) + improved_prompt: Optional[str] = Field( + sa_column=sa.Column( + sa.Text, + nullable=True, + ), + ) + improved_completion: Optional[str] = Field( + sa_column=sa.Column( + sa.Text, + nullable=True, + ), + ) + improvement_feedback: str = Field( + sa_column=sa.Column( + sa.Text, + nullable=False, + ), + ) + submitted_by: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + nullable=False, + ), + ) + created_at: datetime = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) diff --git a/backend/instruct_multilingual/models/user.py b/backend/instruct_multilingual/models/user.py new file mode 100644 index 0000000..f9a7c20 --- /dev/null +++ b/backend/instruct_multilingual/models/user.py @@ -0,0 +1,94 @@ +from datetime import datetime +from typing import List, Optional +from uuid import UUID + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as sa_psql + +from sqlmodel import Field, SQLModel + + +class User(SQLModel, table=True): + __tablename__ = "user" + + id: UUID = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + primary_key=True, + server_default=(sa.text("uuid_generate_v4()")), + ), + ) + # discord ids are "snowflake" ids + # https://discord.com/developers/docs/reference?ref=blog.netcord.in#snowflakes + # we'll store them as strings to avoid having to create custom types + discord_id: str = Field( + sa_column=sa.Column( + sa.String(), + nullable=True, + ) + ) + google_id: str = Field( + sa_column=sa.Column( + sa.String(), + nullable=True, + ) + ) + username: str = Field( + sa_column=sa.Column( + sa_psql.VARCHAR(64), + nullable=False, + ) + ) + email: Optional[str] = Field( + sa_column=sa.Column( + sa.Text, + nullable=True, + ) + ) + image_url: str = Field( + sa_column=sa.Column( + sa_psql.VARCHAR(256), + nullable=False, + ) + ) + country_code: Optional[UUID] = Field( + sa_column=sa.Column( + sa_psql.UUID(as_uuid=True), + sa.ForeignKey("country_code.id"), + nullable=True, + ), + ) + language_codes: Optional[List[UUID]] = Field( + sa_column=sa.Column( + sa.ARRAY(sa_psql.UUID(as_uuid=True)), + nullable=True, + ), + ) + # 0 to infinity (no upper bound) + # https://docs.sqlalchemy.org/en/14/core/custom_types.html#sqlalchemy.types.RangeType + # inclusive lower bound, inclusive upper bound + age_range: Optional[int] = Field( + sa_column=sa.Column( + sa_psql.INT4RANGE, + nullable=True, + ), + ) + gender: Optional[str] = Field( + sa_column=sa.Column( + sa_psql.VARCHAR(64), + nullable=True, + ), + ) + dialects: Optional[List[str]] = Field( + sa_column=sa.Column( + sa.ARRAY(sa.String(50)), + nullable=True + ) + ) + created_at: Optional[datetime] = Field( + sa_column=sa.Column( + sa.DateTime, + nullable=False, + server_default=sa.func.now(), + ), + ) diff --git a/backend/instruct_multilingual/schemas/leaderboard.py b/backend/instruct_multilingual/schemas/leaderboard.py new file mode 100644 index 0000000..0ef5946 --- /dev/null +++ b/backend/instruct_multilingual/schemas/leaderboard.py @@ -0,0 +1,66 @@ +from typing import List, Optional, Union + +from pydantic import BaseModel, validator + +class LeaderboardBaseRecord(BaseModel): + username: str + languages: Optional[List[str]] + image_url: str + rank: int + points: int + + +class LeaderboardDailyRecord(LeaderboardBaseRecord): + day: str + + +class LeaderboardWeeklyRecord(LeaderboardBaseRecord): + week_of: str + + +class LeaderboardByLanguageRecord(LeaderboardBaseRecord): + language: str + blended_rank: int + blended_points: int + quality_score: Optional[float] + + class Config: + exclude = {"languages"} + + @validator('quality_score') + def result_check(cls, v): + if v is not None: + return round(v, 4) + +class LeaderboardOverallRecord(LeaderboardBaseRecord): + blended_rank: int + blended_points: int + quality_score: Optional[float] + + @validator('quality_score') + def result_check(cls, v): + if v is not None: + return round(v, 4) + +class LeaderboardRecordList(BaseModel): + records: List[Union[ + LeaderboardOverallRecord, + LeaderboardDailyRecord, + LeaderboardWeeklyRecord, + LeaderboardByLanguageRecord, + LeaderboardBaseRecord, + ]] + + current_user: Optional[LeaderboardBaseRecord] + total_count: Optional[int] + + +class OverallLeaderboardRecordList(BaseModel): + records: List[LeaderboardOverallRecord] + + current_user: Optional[LeaderboardOverallRecord] + total_count: int + +class LanguageLeaderboardRecordList(BaseModel): + records: List[LeaderboardByLanguageRecord] + current_user: Optional[LeaderboardByLanguageRecord] \ No newline at end of file diff --git a/backend/instruct_multilingual/schemas/task.py b/backend/instruct_multilingual/schemas/task.py new file mode 100644 index 0000000..adff2e0 --- /dev/null +++ b/backend/instruct_multilingual/schemas/task.py @@ -0,0 +1,158 @@ +from datetime import datetime +from typing import List, Optional +from uuid import UUID + +from pydantic import BaseModel, validator + + +class TaskSchema(BaseModel): + id: UUID + prompt: str + completion: str + is_contributed: bool + + +class TaskListSchema(BaseModel): + tasks: List[TaskSchema] + + +class TaskAuditRequestSchema(BaseModel): + task_id: UUID + submitted_by: UUID + submitted_prompt: str + submitted_completion: str + prompt_edited: bool + completion_edited: bool + prompt_rating: Optional[int] = None + completion_rating: Optional[int] = None + + +class TaskAuditResponseSchema(TaskAuditRequestSchema): + id: UUID + created_at: datetime + + +class TaskContributionRequestSchema(BaseModel): + submitted_by: UUID + submitted_prompt: str + submitted_completion: str + language_id: UUID + + # if the submitted prompt and completion are empty, raise a validation error + @validator("submitted_prompt") + def prompt_must_be_nonempty(cls, v): + if not v: + raise ValueError("prompt must not be empty") + + return v + + @validator("submitted_completion") + def completion_must_be_nonempty(cls, v): + if not v: + raise ValueError("completion must not be empty") + + return v + + +class TaskContributionResponseSchema(TaskContributionRequestSchema): + id: UUID + created_at: datetime + + +class TaskContributionAuditRequestSchema(BaseModel): + task_contribution_id: UUID + submitted_by: UUID + submitted_prompt: str + submitted_completion: str + prompt_edited: bool + completion_edited: bool + prompt_rating: Optional[int] = None + completion_rating: Optional[int] = None + + +class TaskContributionAuditResponseSchema(TaskContributionAuditRequestSchema): + id: UUID + created_at: datetime + + +class TaskAuditReviewRequestSchema(BaseModel): + task_audit_id: UUID + submitted_by: UUID + edited_prompt_rating: int + edited_completion_rating: int + improved_edited_prompt: Optional[str] = None + improved_edited_completion: Optional[str] = None + feedback: Optional[str] = None + + +class TaskAuditReviewResponseSchema(TaskAuditReviewRequestSchema): + id: UUID + created_at: datetime + + +class TaskContributionAuditReviewRequestSchema(BaseModel): + task_contribution_audit_id: UUID + submitted_by: UUID + edited_prompt_rating: int + edited_completion_rating: int + improved_edited_prompt: Optional[str] = None + improved_edited_completion: Optional[str] = None + feedback: Optional[str] = None + + +class TaskContributionAuditReviewResponseSchema(TaskContributionAuditReviewRequestSchema): + id: UUID + created_at: datetime + + +class TaskAuditGetResponseSchema(BaseModel): + id: UUID + contributed_by_id: UUID + contributed_by: str + contributed_by_image: str + original_prompt: str + original_completion: str + edited_prompt: str + edited_completion: str + is_contributed: bool + + +class TaskAuditGetResponseListSchema(BaseModel): + task_audits: List[TaskAuditGetResponseSchema] + + +class Task1SubmissionResponseSchema(BaseModel): + id: UUID + submitted_by: UUID + submitted_prompt: str + submitted_completion: str + prompt_edited: bool + completion_edited: bool + prompt_rating: Optional[int] = None + completion_rating: Optional[int] = None + created_at: datetime + edit_distance: Optional[float] = None + + +class Task2SubmissionResponseSchema(BaseModel): + id: UUID + submitted_by: UUID + submitted_prompt: str + submitted_completion: str + language_id: UUID + created_at: datetime + + +class Task3SubmissionResponseSchema(BaseModel): + id: UUID + original_prompt: str + original_completion: str + edited_prompt: str + edited_completion: str + edited_prompt_rating: int + edited_completion_rating: int + improved_prompt: Optional[str] = None + improved_completion: Optional[str] = None + improvement_feedback: str + submitted_by: UUID + created_at: datetime diff --git a/backend/instruct_multilingual/schemas/user.py b/backend/instruct_multilingual/schemas/user.py new file mode 100644 index 0000000..cc436e7 --- /dev/null +++ b/backend/instruct_multilingual/schemas/user.py @@ -0,0 +1,130 @@ +from datetime import datetime +from enum import Enum +from typing import List, Optional, Union +from uuid import UUID + +from pydantic import BaseModel, validator + +from instruct_multilingual.schemas.task import Task3SubmissionResponseSchema, Task1SubmissionResponseSchema, \ + Task2SubmissionResponseSchema + + +class GenderOptions(str, Enum): + male = "male" + female = "female" + non_binary = "non-binary" + prefer_not_to_say = "prefer not to say" + other = "other" + + +# schemas for creating a user +class UserRequestSchema(BaseModel): + username: str + image_url: str + country_code: Optional[UUID] + language_codes: Optional[List[UUID]] + age_range: Optional[List[Optional[int]]] + gender: Optional[GenderOptions] + dialects: Optional[List[str]] + + @validator("age_range") + def age_range_must_be_valid(cls, v): + """ + Validates that the age range is valid and contains two values. + """ + if v is not None: + if len(v) < 2 or len(v) > 2: + raise ValueError( + "age range must contain two values, lower bound and upper bound. " + "If there is no upper bound, use null. " + "If there is no lower bound, use null." + ) + if v[0] is not None and v[0] < 0: + raise ValueError("age range lower bound must be greater than or equal to 0") + if v[1] is not None and v[1] < 0: + raise ValueError("age range upper bound must be greater than or equal to 0") + if v[0] is not None and v[1] is not None and v[0] > v[1]: + raise ValueError("age range lower bound must be less than or equal to upper bound") + + return v + + @validator("dialects", each_item=True) + def dialect_must_be_less_than_50_char(cls, v): + """ + Validates that the dialect is less than or equal to 50 characters. + Param each_item allows the validator to run on each item in the list. + """ + if v is not None and len(v) > 50: + raise ValueError("Dialect must be less than or equal to 50 characters.") + return v + + +class UserResponseSchema(BaseModel): + id: UUID + username: str + image_url: str + country_code: Optional[UUID] + language_codes: Optional[List[UUID]] + age_range: Optional[List[Optional[int]]] + gender: Optional[GenderOptions] + dialects: Optional[List[str]] + created_at: datetime + + @validator("age_range", pre=True) + def convert_age_range_to_list(cls, v): + """ + Converts the age range which is a postgres dialect NumericRange to a list. + + Runs before the standard validator. + """ + if v is None: + return v + + # by default, INT4RANGE uses a canonical form of '[)' + # https://www.postgresql.org/docs/current/rangetypes.html + # so let's work with that and subtract 1 from the upper bound + # if it is not null, instead of creating our own custom range type. + lower_bound = v.lower + upper_bound = v.upper + + if upper_bound is not None: + upper_bound = upper_bound - 1 + + return [lower_bound, upper_bound] + + +# schemas for getting language and country options +# that are available to a user +class UserLanguageOptionsSchema(BaseModel): + id: UUID + code: str + name: str + character_code: str + direction: str + + +class UserLanguageOptionsResponseSchema(BaseModel): + options: List[UserLanguageOptionsSchema] + + +class UserCountryOptionsSchema(BaseModel): + id: UUID + code: str + name: str + + +class UserCountryOptionsResponseSchema(BaseModel): + options: List[UserCountryOptionsSchema] + + +class UserTaskContributionPaginationResponseSchema(BaseModel): + total_count: int + page: int + page_size: int + total_pages: int + results: List[Union[ + Task1SubmissionResponseSchema, + Task2SubmissionResponseSchema, + Task3SubmissionResponseSchema + ] +] diff --git a/backend/instruct_multilingual/services/__init__.py b/backend/instruct_multilingual/services/__init__.py new file mode 100644 index 0000000..a8f7e86 --- /dev/null +++ b/backend/instruct_multilingual/services/__init__.py @@ -0,0 +1,12 @@ +from instruct_multilingual.services.task_service import TaskService +from instruct_multilingual.services.task_audit_service import TaskAuditService +from instruct_multilingual.services.task_contribution_service import TaskContributionService +from instruct_multilingual.services.task_review_service import TaskAuditReviewService, TaskContributionAuditReviewService + +__all__ = [ + "TaskService", + "TaskAuditService", + "TaskContributionService", + "TaskAuditReviewService", + "TaskContributionAuditReviewService", +] \ No newline at end of file diff --git a/backend/instruct_multilingual/services/task_audit_service.py b/backend/instruct_multilingual/services/task_audit_service.py new file mode 100644 index 0000000..fcc714d --- /dev/null +++ b/backend/instruct_multilingual/services/task_audit_service.py @@ -0,0 +1,149 @@ +import logging +import random +from typing import List, Union +from uuid import UUID + +from sqlalchemy import exc as sa_errors +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.exceptions import IDNotFoundError +from instruct_multilingual.models import TaskAudit, TaskContributionAudit + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +class TaskAuditService: + + def create_task_audit( + self, + *, + task_id: UUID, + submitted_prompt: str, + submitted_completion: str, + submitted_by: UUID, + prompt_edited: bool, + completion_edited: bool, + prompt_rating: int, + completion_rating: int, + ) -> TaskAudit: + """ + Create a task audit. + """ + try: + with Session(db.engine) as session: + task_audit = TaskAudit( + task_id=task_id, + submitted_prompt=submitted_prompt, + submitted_completion=submitted_completion, + submitted_by=submitted_by, + prompt_edited=prompt_edited, + completion_edited=completion_edited, + prompt_rating=prompt_rating, + completion_rating=completion_rating, + ) + session.add(task_audit) + session.commit() + session.refresh(task_audit) + except sa_errors.IntegrityError as e: + message = str(e).lower() + + if "key (task_id)" in message: + logger.debug(e) + raise IDNotFoundError( + message=f"invalid task id. task {task_audit.task_id} does not exist.", + ) + elif "key (submitted_by)" in message: + logger.debug(e) + raise IDNotFoundError( + message=f"invalid user id. user {task_audit.submitted_by} does not exist.", + ) + + return task_audit + + + def get_task_audits_for_user_and_language_id( + self, + *, + user_id: UUID, + language_id: UUID, + ) -> List[Union[TaskAudit, TaskContributionAudit]]: + """ + Returns a list of task audits, not created by the user, for a given language. + """ + query = f""" + WITH ranked_records AS ( + SELECT + ta.id, + u.id as user_id, + u.username, + u.image_url, + t.prompt, + t.completion, + ta.submitted_prompt, + ta.submitted_completion, + ta.prompt_edited as original_prompt_edited, + ta.completion_edited as original_completion_edited, + ta.created_at, + FALSE AS is_contributed, + COUNT(*) OVER (PARTITION BY ta.submitted_by) AS audit_count + FROM task_audit ta + JOIN task t ON ta.task_id = t.id + JOIN public.user u ON ta.submitted_by = u.id + LEFT JOIN task_audit_review tar ON ta.id = tar.task_audit_id + WHERE tar.task_audit_id IS NULL + AND ta.submitted_by != '{user_id}' + AND t.language_id = '{language_id}' + AND (ta.prompt_edited = TRUE OR ta.completion_edited = TRUE) + + UNION + + SELECT + tac.id, + u.id as user_id, + u.username, + u.image_url, + tc.submitted_prompt as prompt, + tc.submitted_completion as completion, + tac.submitted_prompt, + tac.submitted_completion, + tac.prompt_edited as original_prompt_edited, + tac.completion_edited as original_completion_edited, + tac.created_at, + TRUE AS is_contributed, + COUNT(*) OVER (PARTITION BY tac.submitted_by) AS audit_count + FROM task_contribution_audit tac + JOIN task_contribution tc ON tac.task_contribution_id = tc.id + JOIN public.user u ON tac.submitted_by = u.id + LEFT JOIN task_contribution_audit_review tcar ON tac.id = tcar.task_contribution_audit_id + WHERE tcar.task_contribution_audit_id IS NULL + AND tac.submitted_by != '{user_id}' + AND tc.language_id = '{language_id}' + AND (tac.prompt_edited = TRUE OR tac.completion_edited = TRUE) + ), + -- shuffle the records + shuffled_records AS ( + SELECT + *, + ROW_NUMBER() OVER ( + ORDER BY random() + ) AS shuffle_num + FROM ranked_records + ), + -- limit the number of records we return to 20 + limited_records AS ( + SELECT * + FROM shuffled_records + ORDER BY shuffle_num + LIMIT 20 + ) + SELECT * + FROM limited_records + """ + with Session(db.engine) as session: + results = session.exec(query).all() + + return results diff --git a/backend/instruct_multilingual/services/task_contribution_service.py b/backend/instruct_multilingual/services/task_contribution_service.py new file mode 100644 index 0000000..86f14ff --- /dev/null +++ b/backend/instruct_multilingual/services/task_contribution_service.py @@ -0,0 +1,147 @@ +from uuid import UUID +from typing import List, Optional + +from sqlalchemy import text +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.models import ( + TaskContribution, + TaskContributionAudit, +) + + +class TaskContributionService: + """ + Service for interacting with task contributions. + """ + def create_task_contribution( + self, + *, + submitted_prompt: str, + submitted_completion: str, + submitted_by: UUID, + language_id: UUID, + ): + """ + Creates a task contribution. + """ + with Session(db.engine) as session: + task_contribution = TaskContribution( + submitted_prompt=submitted_prompt, + submitted_completion=submitted_completion, + submitted_by=submitted_by, + language_id=language_id, + ) + session.add(task_contribution) + session.commit() + session.refresh(task_contribution) + + return task_contribution + + + def get_task_contributions_for_user_and_language_id( + self, + *, + user_id: UUID, + language_id: UUID, + ) -> List[TaskContribution]: + """ + Returns a list of task contributions for a given language, + that: + + - have not been audited by the user + - have not been audited 3 times already + - have not been contributed by the current user + """ + task_contributions_query = f""" + WITH language_tasks AS ( + SELECT + tc.id, + tc.submitted_prompt, + tc.submitted_completion, + tc.language_id + FROM task_contribution tc + -- by doing this left join, we ensure that we only include tasks that have + -- not been audited by the specific user. + LEFT JOIN task_contribution_audit tca + ON (tc.id = tca.task_contribution_id) + AND tca.submitted_by = '{user_id}' + JOIN language_code lc + ON (tc.language_id = lc.id) + WHERE + lc.id = '{language_id}' + AND tca.task_contribution_id IS NULL + AND tc.submitted_by != '{user_id}' + -- this is a quick hack to exclude poor contributions from a specific user + AND tc.submitted_by != '3189d48b-83b3-479e-9395-0130a97dc8c8' + ), + task_contribution_audit_counts AS ( + SELECT + task_contribution_id, + COUNT(*) AS audit_count + FROM task_contribution_audit + GROUP BY task_contribution_id + ) + SELECT + id, + submitted_prompt, + submitted_completion, + language_id + FROM ( + SELECT + lt.id, + lt.submitted_prompt, + lt.submitted_completion, + lt.language_id, + ROW_NUMBER() OVER (ORDER BY RANDOM()) AS rownum, + COALESCE(tcacs.audit_count, 0) + FROM language_tasks lt + LEFT JOIN task_contribution_audit_counts tcacs + ON lt.id = tcacs.task_contribution_id + WHERE COALESCE(tcacs.audit_count, 0) < 3 + OR tcacs.audit_count IS NULL + ) AS filtered_task_contributions + WHERE rownum <= 20; + """ + with Session(db.engine) as session: + task_contributions = ( + session.query(TaskContribution) + .from_statement(text(task_contributions_query)) + .all() + ) + + return task_contributions + + + def create_task_contribution_audit( + self, + *, + task_contribution_id: UUID, + submitted_by: UUID, + submitted_prompt: str, + submitted_completion: str, + prompt_edited: bool, + completion_edited: bool, + prompt_rating: Optional[int] = None, + completion_rating: Optional[int] = None, + ) -> TaskContributionAudit: + """ + Creates a task contribution audit. + """ + with Session(db.engine) as session: + task_contribution_audit = TaskContributionAudit( + task_contribution_id=task_contribution_id, + submitted_by=submitted_by, + submitted_prompt=submitted_prompt, + submitted_completion=submitted_completion, + prompt_edited=prompt_edited, + completion_edited=completion_edited, + prompt_rating=prompt_rating, + completion_rating=completion_rating, + ) + session.add(task_contribution_audit) + session.commit() + session.refresh(task_contribution_audit) + + return task_contribution_audit diff --git a/backend/instruct_multilingual/services/task_review_service.py b/backend/instruct_multilingual/services/task_review_service.py new file mode 100644 index 0000000..e61047c --- /dev/null +++ b/backend/instruct_multilingual/services/task_review_service.py @@ -0,0 +1,88 @@ +import logging + +from datetime import datetime +from uuid import UUID +from typing import Optional + +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.models import ( + TaskAudit, + TaskAuditReview, + TaskContributionAudit, + TaskContributionAuditReview, +) + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +class TaskAuditReviewService: + + def create_task_audit_review( + self, + *, + task_audit_id: UUID, + submitted_by: UUID, + edited_prompt_rating: int, + edited_completion_rating: int, + improved_edited_prompt: Optional[str] = None, + improved_edited_completion: Optional[str] = None, + feedback: str, + ) -> TaskAuditReview: + """ + Create a task audit review, and then decrement the number of reviews + that a particular user can receive (the author of the task audit) + """ + with Session(db.engine) as session: + review = TaskAuditReview( + task_audit_id=task_audit_id, + submitted_by=submitted_by, + edited_prompt_rating=edited_prompt_rating, + edited_completion_rating=edited_completion_rating, + improved_prompt=improved_edited_prompt, + improved_completion=improved_edited_completion, + improvement_feedback=feedback, + ) + session.add(review) + session.commit() + session.refresh(review) + + return review + + +class TaskContributionAuditReviewService: + + def create_task_contribution_audit_review( + self, + *, + task_contribution_audit_id: UUID, + submitted_by: UUID, + edited_prompt_rating: int, + edited_completion_rating: int, + improved_edited_prompt: Optional[str] = None, + improved_edited_completion: Optional[str] = None, + feedback: str, + ) -> TaskContributionAuditReview: + """ + Create a task contribution audit review, and then decrement the number of reviews + that a particular user can receive (the author of the task contribution audit) + """ + with Session(db.engine) as session: + review = TaskContributionAuditReview( + task_contribution_audit_id=task_contribution_audit_id, + submitted_by=submitted_by, + edited_prompt_rating=edited_prompt_rating, + edited_completion_rating=edited_completion_rating, + improved_prompt=improved_edited_prompt, + improved_completion=improved_edited_completion, + improvement_feedback=feedback, + ) + session.add(review) + session.commit() + session.refresh(review) + + return review \ No newline at end of file diff --git a/backend/instruct_multilingual/services/task_service.py b/backend/instruct_multilingual/services/task_service.py new file mode 100644 index 0000000..fe6c05a --- /dev/null +++ b/backend/instruct_multilingual/services/task_service.py @@ -0,0 +1,78 @@ +from uuid import UUID +from typing import List, Optional + +from sqlalchemy import text +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.models import Task + +class TaskService: + """ + Service for interacting with tasks. + """ + def get_active_tasks_for_user_and_language_id( + self, + *, + user_id: UUID, + language_id: UUID, + num_tasks: int, + ) -> List[Optional[Task]]: + """ + Returns a list of tasks for a given language, that have not been audited by the + user, and that have not been audited 3 times already. + """ + # don't query at all if we don't have to + if num_tasks == 0: + return [] + + tasks_query = f""" + WITH language_tasks AS ( + SELECT + t.id, + t.prompt, + t.completion, + t.language_id + FROM task t + LEFT JOIN task_audit ta + ON t.id = ta.task_id AND ta.submitted_by = '{user_id}' + JOIN language_code lc + ON t.language_id = lc.id + JOIN dataset ds + ON t.dataset_id = ds.id + WHERE ta.task_id IS NULL + AND lc.id = '{language_id}' + AND ds.active = TRUE + ), + task_audit_counts AS ( + SELECT + task_id, + COUNT(*) AS audit_count + FROM task_audit + GROUP BY task_id + ) + SELECT + id, + prompt, + completion, + language_id + FROM ( + SELECT + lt.id, + lt.prompt, + lt.completion, + lt.language_id, + ROW_NUMBER() OVER (ORDER BY RANDOM()) AS rownum, + COALESCE(tac.audit_count, 0) + FROM language_tasks lt + LEFT JOIN task_audit_counts tac + ON lt.id = tac.task_id + WHERE COALESCE(tac.audit_count, 0) < 3 + OR tac.audit_count IS NULL + ) AS numbered_tasks + WHERE rownum <= {num_tasks}; + """ + with Session(db.engine) as session: + tasks = session.query(Task).from_statement(text(tasks_query)).all() + + return tasks diff --git a/backend/instruct_multilingual/services/user_contribution_service.py b/backend/instruct_multilingual/services/user_contribution_service.py new file mode 100644 index 0000000..6fb0239 --- /dev/null +++ b/backend/instruct_multilingual/services/user_contribution_service.py @@ -0,0 +1,58 @@ +import logging +from uuid import UUID + +from fastapi import status +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.exceptions import InstructMultilingualAPIError +from instruct_multilingual.models.task_1_submission import Task1Submission +from instruct_multilingual.models.task_3_submission import Task3Submission +from instruct_multilingual.models.task_contribution import TaskContribution +from instruct_multilingual.schemas.user import UserTaskContributionPaginationResponseSchema + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + + +class UserContributionService: + + @staticmethod + def fetch_task_contributions_by_user( + task_type: str, + user_id: UUID, + page: int = 1, + page_size: int = 20 + ) -> UserTaskContributionPaginationResponseSchema: + """ Fetch contributions for a Task type by a specific user with pagination. """ + + if task_type == 'task1': + entity = Task1Submission + elif task_type == 'task2': + entity = TaskContribution + elif task_type == 'task3': + entity = Task3Submission + + try: + with Session(db.engine) as session: + total_count = session.query(entity).filter(entity.submitted_by == user_id).count() + task_submissions = (session.query(entity) + .filter(entity.submitted_by == user_id) + .order_by(entity.created_at.desc()) + .limit(page_size) + .offset((page - 1) * page_size) + .all()) + + total_pages = -(-total_count // page_size) + return UserTaskContributionPaginationResponseSchema(total_count=total_count, page=page, page_size=page_size, + total_pages=total_pages, results=task_submissions) + + except Exception as e: + logger.error(e) + raise InstructMultilingualAPIError( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error retrieving the contributions for {task_type}", + ) diff --git a/backend/instruct_multilingual/utils/__init__.py b/backend/instruct_multilingual/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/instruct_multilingual/utils/webhook_utils.py b/backend/instruct_multilingual/utils/webhook_utils.py new file mode 100644 index 0000000..fc4a5c1 --- /dev/null +++ b/backend/instruct_multilingual/utils/webhook_utils.py @@ -0,0 +1,59 @@ +import logging +import time + +from typing import List, Optional + +import requests + +from instruct_multilingual.config import get_settings + +settings = get_settings() + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +# normally, we'd want to do this async, but +# instead we'll use a background task since the +# API layer is not async +def send_discord_webhook_message(message: str, embeds: Optional[List[str]] = None): + """ + Sends a message to a discord webhook. + """ + # bail out if the webhook URL is not set + if not settings.discord_webhook_url or settings.discord_webhook_url is None: + logger.info( + f"discord webhook URL not set, skipping webhook payload delivery" + ) + return + + # send a message to a discord webhook + # and use the default username and avatar + data = {"content" : message} + + if embeds is not None: + data["embeds"] = embeds + + logger.info(f"sending discord webhook payload...") + + # time the request + start_time = time.time() + + response = requests.post( + settings.discord_webhook_url, + json=data, + ) + + elapsed_time = time.time() - start_time + + try: + response.raise_for_status() + except requests.exceptions.HTTPError as err: + logger.error(err) + else: + logger.info( + f"payload took {elapsed_time:.2f} seconds with status code {response.status_code}" + ) + \ No newline at end of file diff --git a/backend/jobs/__init__.py b/backend/jobs/__init__.py new file mode 100644 index 0000000..30178ae --- /dev/null +++ b/backend/jobs/__init__.py @@ -0,0 +1,4 @@ +from jobs.by_language.leaderboard_by_language_job import update_leaderboard_by_language +from jobs.daily.leaderboard_daily_job import update_leaderboard_daily +from jobs.overall.leaderboard_overall_job import update_leaderboard_overall +from jobs.weekly.leaderboard_weekly_job import update_leaderboard_weekly \ No newline at end of file diff --git a/backend/jobs/by_language/Dockerfile b/backend/jobs/by_language/Dockerfile new file mode 100644 index 0000000..9d205b1 --- /dev/null +++ b/backend/jobs/by_language/Dockerfile @@ -0,0 +1,15 @@ +# Start with the official Python 3.10 image +FROM python:3.10-slim-buster + +# Set the working directory to /job +WORKDIR /job + +# Copy the backend code into the container +COPY ./ /job/ + +# Install the dependencies using poetry +RUN pip install poetry +RUN poetry config virtualenvs.create false +RUN poetry install + +CMD ["python", "-m", "jobs.leaderboard_update_job", "--leaderboard", "by_language"] \ No newline at end of file diff --git a/backend/jobs/by_language/__init__.py b/backend/jobs/by_language/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/jobs/by_language/leaderboard_by_language_job.py b/backend/jobs/by_language/leaderboard_by_language_job.py new file mode 100644 index 0000000..aeec688 --- /dev/null +++ b/backend/jobs/by_language/leaderboard_by_language_job.py @@ -0,0 +1,561 @@ +import logging + +from sqlalchemy import text +from sqlalchemy.dialects.postgresql import insert + +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.models import ( + LeaderboardByLanguage, +) + +from jobs.common import ( + compute_aya_score, + even_out_ranks, + even_out_blended_ranks, + update_blended_ranks_within_language_groups, + update_ranks_within_language_groups, +) + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + + +def update_leaderboard_by_language(): + """ + Query the task_audit, language_code, task, and user table, and then update the `leaderboard_by_language` table + with the number of points each user has earned overall. + """ + logger.info('updating leaderboard by language...') + + query = """ + -- combined task audit and task contribution points using UNION ALL + SELECT + user_id, + username, + language, + language_code, + SUM(points) AS points, + image_url, + 0 AS rank + FROM ( + SELECT + user_id, + username, + language, + language_code, + points, + image_url + FROM ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + lc.code AS language_code, + COALESCE(SUM( + CASE + WHEN ta.prompt_rating IS NOT NULL AND ta.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_audit ta + INNER JOIN "user" u ON ta.submitted_by = u.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url, + lc.code + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + lc.code AS language_code, + COALESCE(SUM(2), 0) AS points, + u.image_url AS image_url + FROM + task_contribution tc + INNER JOIN "user" u ON tc.submitted_by = u.id + INNER JOIN language_code lc ON tc.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url, + lc.code + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + lc.code AS language_code, + COALESCE(SUM( + CASE + WHEN tca.prompt_rating IS NOT NULL AND tca.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_contribution_audit tca + INNER JOIN "user" u ON tca.submitted_by = u.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url, + lc.code + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + lc.code AS language_code, + COALESCE(SUM( + CASE + WHEN tar.edited_prompt_rating IS NOT NULL AND tar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_audit_review tar + INNER JOIN "user" u ON tar.submitted_by = u.id + INNER JOIN task_audit ta ON tar.task_audit_id = ta.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url, + lc.code + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + lc.code AS language_code, + COALESCE(SUM( + CASE + WHEN tcar.edited_prompt_rating IS NOT NULL AND tcar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_contribution_audit_review tcar + INNER JOIN "user" u ON tcar.submitted_by = u.id + INNER JOIN task_contribution_audit tca ON tcar.task_contribution_audit_id = tca.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url, + lc.code + + ) AS combined_results + ) AS final_results + GROUP BY + user_id, + username, + image_url, + language, + language_code + ORDER BY + points DESC; + """ + + try: + with Session(db.engine) as session: + # get the records by combining the user table and the task_audit table + by_language_records = session.execute(text(query)).all() + + if not by_language_records: + logger.info('no records found for leaderboard by language update') + return None + + # then re-rank the users based on the number of points they have earned + # overall + by_language_records.sort(key=lambda x: x.points, reverse=True) + + # create a list of LeaderboardByLanguage objects and then assign them a rank + # based on their position in the sorted list + by_language_leaderboard_records = [] + for i, record in enumerate(by_language_records): + lb_record = dict( + user_id=record.user_id, + username=record.username, + points=record.points, + language=record.language, + language_code=record.language_code, + image_url=record.image_url, + rank=i+1, + ) + by_language_leaderboard_records.append(lb_record) + + # users should be ranked _within_ each language, and not overall + logger.info('updating ranks within language groups...') + by_language_leaderboard_records = update_ranks_within_language_groups( + by_language_leaderboard_records, + ) + logger.info('ranks within language groups updated!') + + # then update the leaderboard_by_language table + insert_stmt = insert( + LeaderboardByLanguage + ).values( + by_language_leaderboard_records + ) + + statement = insert_stmt.on_conflict_do_update( + index_elements=['user_id', 'language'], + # update the points and rank columns + # if the user_id and language already exist in the table + set_={ + 'points': insert_stmt.excluded.points, + 'rank': insert_stmt.excluded.rank, + 'username': insert_stmt.excluded.username, + }, + ).returning( + LeaderboardByLanguage.id + ) + results = session.execute(statement).fetchall() + session.commit() + except Exception as e: + logger.error(f'error while updating leaderboard by language: {e}') + raise e + else: + logger.info(f'leaderboard by language updated with {len(results)} records!') + + # next we'll use the existing records in the leaderboard_by_language table to + # update the blended points and blended ranks + quality_score_query = """ + -- Calculate the average rating for each user and language they've contributed to based on the reviews they have received for task audits + WITH combined_task_audit_reviews AS ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language_name, + AVG((tar.edited_prompt_rating + tar.edited_completion_rating) / 2) AS avg_rating_for_task_audits + FROM task_audit_review tar + JOIN task_audit ta ON ta.id = tar.task_audit_id + JOIN "user" u ON u.id = ta.submitted_by + JOIN task t on ta.task_id = t.id + JOIN language_code lc ON lc.id = t.language_id + GROUP BY u.id, u.username, lc.id + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language_name, + AVG((tcar.edited_prompt_rating + tcar.edited_completion_rating) / 2) AS avg_rating_for_task_audits + FROM task_contribution_audit_review tcar + JOIN task_contribution_audit tca ON tca.id = tcar.task_contribution_audit_id + JOIN task_contribution tc on tca.task_contribution_id = tc.id + JOIN "user" u ON u.id = tca.submitted_by + JOIN language_code lc ON lc.id = tc.language_id + GROUP BY u.id, u.username, lc.id + ), + -- Calculate the number of audits each user and language they've contributed to has completed and also edited + task_audits_edited AS ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language_name, + SUM( + CASE + WHEN (ta.prompt_edited = true OR ta.completion_edited = true) THEN 1 + ELSE 0 + END + ) AS num_audits_edited + FROM task_audit ta + JOIN task t on ta.task_id = t.id + JOIN "user" u ON u.id = ta.submitted_by + JOIN language_code lc ON lc.id = t.language_id + GROUP BY u.id, u.username, lc.id + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language_name, + SUM( + CASE + WHEN (tca.prompt_edited = true OR tca.completion_edited = true) THEN 1 + ELSE 0 + END + ) AS num_audits_edited + FROM task_contribution_audit tca + JOIN task_contribution tc on tca.task_contribution_id = tc.id + JOIN "user" u ON u.id = tca.submitted_by + JOIN language_code lc ON lc.id = tc.language_id + GROUP BY u.id, u.username, lc.id + ), + -- This counts the number of audits each user and language they've contributed to has reviewed and further edited + task_audits_reviewed AS ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language_name, + COUNT(*) AS num_audits_reviewed_and_improved + FROM task_audit_review tar + JOIN task_audit ta ON ta.id = tar.task_audit_id + JOIN task t on ta.task_id = t.id + JOIN "user" u ON u.id = tar.submitted_by + JOIN language_code lc ON lc.id = t.language_id + WHERE (tar.improved_prompt != '' OR tar.improved_completion != '') + GROUP BY u.id, u.username, lc.id + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language_name, + COUNT(*) AS num_audits_reviewed_and_improved + FROM task_contribution_audit_review tcar + JOIN task_contribution_audit tca ON tca.id = tcar.task_contribution_audit_id + JOIN task_contribution tc on tca.task_contribution_id = tc.id + JOIN "user" u ON u.id = tcar.submitted_by + JOIN language_code lc ON lc.id = tc.language_id + WHERE (tcar.improved_prompt != '' OR tcar.improved_completion != '') + GROUP BY u.id, u.username, lc.id + ), + -- Count the number of tasks that have been contributed per user and language they've contributed to + tasks_contributed AS ( + SELECT + u.id as user_id, + u.username, + lc.name AS language_name, + COUNT(*) as num_tasks_contributed + FROM task_contribution tc + JOIN "user" u ON u.id = tc.submitted_by + JOIN language_code lc ON lc.id = tc.language_id + GROUP BY u.id, u.username, lc.id + ORDER BY u.username ASC + ), + -- Count the number of thumbs up out of total audits given per user and language they've contributed to + task_audits_for_contributions AS ( + SELECT + u.id AS user_id, + u.username, + lc.name AS language_name, + tc.id AS task_contribution_id, + tca.prompt_rating, + tca.completion_rating + FROM public."user" u + JOIN task_contribution tc ON u.id = tc.submitted_by + JOIN task_contribution_audit tca ON tc.id = tca.task_contribution_id + JOIN language_code lc ON lc.id = tc.language_id + ), + thumbs_up_per_contribution_audit AS ( + SELECT + user_id, + username, + language_name, + COUNT(DISTINCT task_contribution_id) AS total_contributions, + COUNT(*) AS total_audits, + SUM(CASE WHEN prompt_rating = 1 THEN 1 ELSE 0 END) AS positive_prompt_ratings, + SUM(CASE WHEN completion_rating = 1 THEN 1 ELSE 0 END) AS positive_completion_ratings + FROM task_audits_for_contributions + GROUP BY user_id, username, language_name + ), + thumbs_up_ratio_per_contribution_audit AS ( + SELECT + user_id, + username, + language_name, + CASE WHEN total_audits > 0 THEN (positive_prompt_ratings::decimal / total_audits) ELSE 0.0 END AS avg_prompt_thumbs_up_ratio, + CASE WHEN total_audits > 0 THEN (positive_completion_ratings::decimal / total_audits) ELSE 0.0 END AS avg_completion_thumbs_up_ratio, + CASE WHEN total_audits > 0 THEN ((positive_prompt_ratings::decimal / total_audits) + (positive_completion_ratings::decimal / total_audits)) / 2 ELSE 0.0 END AS avg_thumbs_up_ratio + FROM thumbs_up_per_contribution_audit + ), + aggregated_user_data AS ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language_name, + COALESCE(AVG(ctar.avg_rating_for_task_audits), 0) AS avg_quality_score, + COALESCE(SUM(e.num_audits_edited), 0) AS num_audits_edited, + COALESCE(SUM(r.num_audits_reviewed_and_improved), 0) AS num_audits_reviewed_and_improved, + COALESCE(t.num_tasks_contributed, 0) AS num_tasks_contributed + FROM + "user" u + JOIN language_code lc ON lc.id = ANY(u.language_codes) + LEFT JOIN combined_task_audit_reviews ctar ON u.id = ctar.user_id + AND lc.name = ctar.language_name + LEFT JOIN task_audits_edited e ON u.id = e.user_id + AND lc.name = e.language_name + LEFT JOIN task_audits_reviewed r ON u.id = r.user_id + AND lc.name = r.language_name + LEFT JOIN tasks_contributed t ON u.id = t.user_id + AND lc.name = t.language_name + GROUP BY + u.id, + u.username, + lc.name, + t.num_tasks_contributed + ) + -- Combine and join the aggregated user data with the other subqueries + SELECT + agg.user_id AS user_id, + agg.username AS username, + agg.language_name AS language_name, + COALESCE(agg.avg_quality_score, 0) AS avg_quality_score, + COALESCE(agg.num_audits_edited, 0) AS num_audits_edited, + COALESCE(agg.num_audits_reviewed_and_improved, 0) AS num_audits_reviewed_and_improved, + COALESCE(agg.num_tasks_contributed, 0) AS num_tasks_contributed, + COALESCE(tp.avg_thumbs_up_ratio, 0) AS avg_thumbs_up_ratio + FROM + aggregated_user_data agg + LEFT JOIN thumbs_up_ratio_per_contribution_audit tp ON agg.user_id = tp.user_id + AND agg.language_name = tp.language_name + ORDER BY + username ASC, + language_name ASC; + """ + try: + with Session(db.engine) as session: + # get the records by combining the user table and the task_audit table + aya_score_component_records = session.execute(text(quality_score_query)).all() + + if not aya_score_component_records: + raise Exception('no records found for quality score update') + + # create a list of user_ids for users we computed scores for + aya_score_user_ids = [ + record.user_id + for record in aya_score_component_records + ] + + # then get the records from the leaderboard_by_language table + leaderboard_records = session.query(LeaderboardByLanguage).all() + + # then assign blended points to each user based on the function: + # Aya Score = AYA Score = max(0, (Avg Quality - 3.0)) * + # (3.0 * (Number Prompts / Completions Audited and Edited + Number of Tasks Further Edited)) + + # ( 3.0 * (Number of Thumbs Up / Number of Thumbs) * Number of Task 2 Contributions ) + # where Quality is the average rating for the user. + # + # We are essentially extrapolating out the average score computed in task 3 to + # everything the user has audited and edited. Which makes sense because we are essentially + # saying we form a guess of quality based on a subset but reward the user for all edits + # they have done as a sign since we trust the sample edited is a good proxy for by_language quality. + # + # These aya scores below are specific to each language a user has contributed to. + for lb_record in leaderboard_records: + if lb_record.user_id in aya_score_user_ids: + for record in aya_score_component_records: + if record.user_id == lb_record.user_id and record.language_name == lb_record.language: + logger.debug( + 'computing aya score for user_id, username, and language: ' + f'{lb_record.user_id}, {lb_record.username}, {lb_record.language}' + ) + aya_score_for_language = compute_aya_score( + thumbs_up_received_ratio=record.avg_thumbs_up_ratio, + num_contributed_tasks=record.num_tasks_contributed, + avg_quality_score=record.avg_quality_score, + num_audits_edited=record.num_audits_edited, + num_audits_further_improved=record.num_audits_reviewed_and_improved, + ) + lb_record.blended_points = round(aya_score_for_language) + lb_record.quality_score = round(record.avg_quality_score, 2) + else: + # this can happen because a user may have contributed to a language + # but not have any audits or reviews + # and therefore not have a quality score or aya score + logger.warning(f'no aya score found for user_id: {lb_record.user_id} and username: {lb_record.username}') + + if lb_record.blended_points is None: + lb_record.blended_points = 0 + lb_record.quality_score = 0 + lb_record.blended_rank = 0 + + # then re-rank the users based on the number of blended points they have earned + # within each of there language groups + leaderboard_records = update_blended_ranks_within_language_groups( + leaderboard_records, + ) + + # then commit the changes to the database + session.commit() + except Exception as e: + logger.error(f'error while updating blended ranking and points for language leaderboard: {e}') + raise e + else: + logger.info('blended ranking and points updated for language leaderboard!') + +update_leaderboard_by_language() \ No newline at end of file diff --git a/backend/jobs/common.py b/backend/jobs/common.py new file mode 100644 index 0000000..c799ff0 --- /dev/null +++ b/backend/jobs/common.py @@ -0,0 +1,132 @@ +import logging + +from collections import defaultdict + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + + +def even_out_ranks(records): + """ + If any users have the same number of points, then they should have the same rank. + """ + for i, record in enumerate(records): + if i > 0: + if record['points'] == records[i-1]['points']: + record['rank'] = records[i-1]['rank'] + + return records + + +def even_out_blended_ranks(records): + """ + If any users have the same number of blended points, then they should + have the same blended rank. + """ + for i, record in enumerate(records): + if i > 0: + if record.blended_points == records[i-1].blended_points: + record.blended_rank = records[i-1].blended_rank + + return records + + +def update_ranks_within_language_groups(records): + """ + Group all the users together by the language (code) they have contributed to. + + Then, within each language group, re-rank the users based on the number of points + they have earned in that language. + """ + # group the records by language code + language_groups = defaultdict(list) + + for record in records: + language_code = record['language_code'] + + if language_code in language_groups: + language_groups[language_code].append(record) + else: + language_groups[language_code] = [record] + + # then re-rank the users within each language group + updated_records = [] + for language_code, user_records in language_groups.items(): + # sort the records, which will be a list of dicts, by the number of points + # each user has earned in that language (code) + user_records.sort(key=lambda x: x['points'], reverse=True) + + # and then assign them a rank based on their position in the sorted list + for i, record in enumerate(user_records): + record['rank'] = i+1 + + # if any users have the same number of points, then they should have + # the same rank + user_records = even_out_ranks(user_records) + + updated_records.extend(user_records) + + return updated_records + + +def update_blended_ranks_within_language_groups(records): + """ + Group all the users together by the language (code) they have contributed to. + + Then, within each language group, re-rank the users based on the number of points + they have earned in that language. + """ + # group the records by language code + language_groups = defaultdict(list) + + for record in records: + language_code = record.language_code + + if language_code in language_groups: + language_groups[language_code].append(record) + else: + language_groups[language_code] = [record] + + # then re-rank the users within each language group + updated_records = [] + for language_code, user_records in language_groups.items(): + # sort the records, which will be a list of dicts, by the number of points + # each user has earned in that language (code) + user_records.sort(key=lambda x: x.blended_points, reverse=True) + + # and then assign them a rank based on their position in the sorted list + for i, record in enumerate(user_records): + record.blended_rank = i+1 + + # if any users have the same number of points, then they should have + # the same rank + user_records = even_out_blended_ranks(user_records) + + updated_records.extend(user_records) + + return updated_records + + +def compute_aya_score( + avg_quality_score, + num_audits_edited, + num_audits_further_improved, + thumbs_up_received_ratio, + num_contributed_tasks, +): + logger.debug( + f'Aya score params: ' + f'{max(0, (avg_quality_score - 3))} *' + f'({num_audits_edited} + {num_audits_further_improved}) +' + f'({thumbs_up_received_ratio} * {num_contributed_tasks})' + ) + score = ( + max(0, (avg_quality_score - 3)) * + (num_audits_edited + num_audits_further_improved) + + (thumbs_up_received_ratio * num_contributed_tasks) + ) + logger.debug(f'results: {score}') + return score \ No newline at end of file diff --git a/backend/jobs/daily/Dockerfile b/backend/jobs/daily/Dockerfile new file mode 100644 index 0000000..4f63085 --- /dev/null +++ b/backend/jobs/daily/Dockerfile @@ -0,0 +1,15 @@ +# Start with the official Python 3.10 image +FROM python:3.10-slim-buster + +# Set the working directory to /job +WORKDIR /job + +# Copy the backend code into the container +COPY ./ /job/ + +# Install the dependencies using poetry +RUN pip install poetry +RUN poetry config virtualenvs.create false +RUN poetry install + +CMD ["python", "-m", "jobs.leaderboard_update_job", "--leaderboard", "daily"] \ No newline at end of file diff --git a/backend/jobs/daily/__init__.py b/backend/jobs/daily/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/jobs/daily/leaderboard_daily_job.py b/backend/jobs/daily/leaderboard_daily_job.py new file mode 100644 index 0000000..dda60b6 --- /dev/null +++ b/backend/jobs/daily/leaderboard_daily_job.py @@ -0,0 +1,306 @@ +import logging + +from datetime import datetime + +from sqlalchemy import text +from sqlalchemy.dialects.postgresql import insert + +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.models import ( + LeaderboardDaily, +) + +from jobs.common import even_out_ranks, update_ranks_within_language_groups + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +def update_leaderboard_daily(): + """ + Query the task_audit and user table, and then update the `leaderboard_daily` table + with the number of points each user has earned in the past day. + + Each task audit submitted is worth 2 points. Each user also gets a rank based + on the number of points they have earned. + + The daily leaderboard is updated every 24 hours at 00:30 UTC, or otherwise + determined by the Cloud Run job schedule. + """ + query = """ + SELECT + user_id, + username, + ARRAY_AGG(DISTINCT language) AS languages, + SUM(points) AS points, + lb_day, + image_url, + 0 AS rank + FROM ( + SELECT + user_id, + username, + language, + points, + lb_day, + image_url + FROM ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN ta.prompt_rating IS NOT NULL AND ta.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + CURRENT_DATE AS lb_day, + u.image_url AS image_url + FROM + task_audit ta + INNER JOIN "user" u ON ta.submitted_by = u.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + WHERE + -- current leaderboard represents the previous day + -- so we need to subtract 1 day from the current date + -- to account for all the tasks submitted. + DATE_TRUNC('day', ta.created_at) = CURRENT_DATE + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM(2), 0) AS points, + CURRENT_DATE AS lb_day, + u.image_url AS image_url + FROM + task_contribution tc + INNER JOIN "user" u ON tc.submitted_by = u.id + INNER JOIN language_code lc ON tc.language_id = lc.id + WHERE + -- current leaderboard represents the previous day + -- so we need to subtract 1 day from the current date + -- to account for all the tasks submitted. + DATE_TRUNC('day', tc.created_at) = CURRENT_DATE + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tca.prompt_rating IS NOT NULL AND tca.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + CURRENT_DATE AS lb_day, + u.image_url AS image_url + FROM + task_contribution_audit tca + INNER JOIN "user" u ON tca.submitted_by = u.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + WHERE + -- current leaderboard represents the previous day + -- so we need to subtract 1 day from the current date + -- to account for all the tasks submitted. + DATE_TRUNC('day', tca.created_at) = CURRENT_DATE + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tar.edited_prompt_rating IS NOT NULL AND tar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + CURRENT_DATE AS lb_day, + u.image_url AS image_url + FROM + task_audit_review tar + INNER JOIN "user" u ON tar.submitted_by = u.id + INNER JOIN task_audit ta ON tar.task_audit_id = ta.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + WHERE + DATE_TRUNC('day', tar.created_at) = CURRENT_DATE + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tcar.edited_prompt_rating IS NOT NULL AND tcar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + CURRENT_DATE AS lb_day, + u.image_url AS image_url + FROM + task_contribution_audit_review tcar + INNER JOIN "user" u ON tcar.submitted_by = u.id + INNER JOIN task_contribution_audit tca ON tcar.task_contribution_audit_id = tca.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + WHERE + DATE_TRUNC('day', tcar.created_at) = CURRENT_DATE + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + ) AS combined_results + ) AS final_results + GROUP BY + user_id, + username, + image_url, + lb_day + ORDER BY + points DESC; + """ + + logger.info(f'updating daily leaderboard for date {datetime.utcnow().date()}...') + try: + with Session(db.engine) as session: + # get the daily records by combining the user table and the task_audit table + daily_records = session.execute(text(query)).all() + + if not daily_records: + logger.info('no records found for daily leaderboard update') + return None + + # then re-rank the users based on the number of points they have earned + # in the past 24 hours + daily_records.sort(key=lambda x: x.points, reverse=True) + + # create a list of LeaderboardDaily objects and then assign them a rank + # based on their position in the sorted list + daily_leaderboard_records = [] + for i, record in enumerate(daily_records): + lb_record = dict( + user_id=record.user_id, + username=record.username, + languages=record.languages, + points=record.points, + day=record.lb_day, + image_url=record.image_url, + rank=i+1, + ) + daily_leaderboard_records.append(lb_record) + + daily_leaderboard_records = even_out_ranks(daily_leaderboard_records) + + # then update the daily leaderboard table + insert_stmt = insert( + LeaderboardDaily + ).values( + daily_leaderboard_records + ) + + statement = insert_stmt.on_conflict_do_update( + index_elements=['user_id', 'day'], + # update the points and rank columns + # if the user_id and day already exist in the table + set_={ + 'points': insert_stmt.excluded.points, + 'rank': insert_stmt.excluded.rank, + 'languages': insert_stmt.excluded.languages, + 'username': insert_stmt.excluded.username, + }, + ).returning( + LeaderboardDaily.id + ) + results = session.execute(statement).fetchall() + session.commit() + except Exception as e: + logger.error(f'error while updating daily leaderboard: {e}') + raise e + else: + logger.info(f'daily leaderboard updated for date {datetime.utcnow().date()} with {len(results)} records!') \ No newline at end of file diff --git a/backend/jobs/leaderboard_update_job.py b/backend/jobs/leaderboard_update_job.py new file mode 100644 index 0000000..81069e6 --- /dev/null +++ b/backend/jobs/leaderboard_update_job.py @@ -0,0 +1,56 @@ +import click +import logging + +from collections import defaultdict +from datetime import datetime, timedelta + +from sqlalchemy import text +from sqlalchemy.dialects.postgresql import insert + +from sqlmodel import Session + +from instruct_multilingual import db + +from jobs import ( + update_leaderboard_by_language, + update_leaderboard_daily, + update_leaderboard_overall, + update_leaderboard_weekly, +) + + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + + +@click.command() +@click.option( + '--leaderboard', + '-l', + type=click.Choice([ + 'daily', + 'weekly', + 'by_language', + 'overall', + ]), + required=True, +) +def update_leaderboard(leaderboard): + """ + Update the leaderboard specified by the argument. + """ + if leaderboard == 'daily': + update_leaderboard_daily() + elif leaderboard == 'weekly': + update_leaderboard_weekly() + elif leaderboard == 'by_language': + update_leaderboard_by_language() + elif leaderboard == 'overall': + update_leaderboard_overall() + + +if __name__ == '__main__': + update_leaderboard() diff --git a/backend/jobs/overall/Dockerfile b/backend/jobs/overall/Dockerfile new file mode 100644 index 0000000..bbf6ef5 --- /dev/null +++ b/backend/jobs/overall/Dockerfile @@ -0,0 +1,15 @@ +# Start with the official Python 3.10 image +FROM python:3.10-slim-buster + +# Set the working directory to /job +WORKDIR /job + +# Copy the backend code into the container +COPY ./ /job/ + +# Install the dependencies using poetry +RUN pip install poetry +RUN poetry config virtualenvs.create false +RUN poetry install + +CMD ["python", "-m", "jobs.leaderboard_update_job", "--leaderboard", "overall"] \ No newline at end of file diff --git a/backend/jobs/overall/__init__.py b/backend/jobs/overall/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/jobs/overall/leaderboard_overall_job.py b/backend/jobs/overall/leaderboard_overall_job.py new file mode 100644 index 0000000..6d3bfa3 --- /dev/null +++ b/backend/jobs/overall/leaderboard_overall_job.py @@ -0,0 +1,500 @@ +import logging + +from decimal import Decimal + +from sqlalchemy import text +from sqlalchemy.dialects.postgresql import insert + +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.models import ( + LeaderboardOverall, +) + +from jobs.common import ( + compute_aya_score, + even_out_ranks, + even_out_blended_ranks, + update_ranks_within_language_groups, +) + + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + + +def update_leaderboard_overall(): + """ + Query the task_audit and user table, and then update the `leaderboard_overall` table. + """ + logger.info('updating overall leaderboard...') + + query = """ + -- combined task audit and task contribution points using UNION ALL + SELECT + user_id, + username, + ARRAY_AGG(DISTINCT language) AS languages, + SUM(points) AS points, + image_url, + 0 AS rank, + 0 AS blended_rank + FROM ( + SELECT + user_id, + username, + language, + points, + image_url + FROM ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN ta.prompt_rating IS NOT NULL AND ta.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_audit ta + INNER JOIN "user" u ON ta.submitted_by = u.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM(2), 0) AS points, + u.image_url AS image_url + FROM + task_contribution tc + INNER JOIN "user" u ON tc.submitted_by = u.id + INNER JOIN language_code lc ON tc.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tca.prompt_rating IS NOT NULL AND tca.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_contribution_audit tca + INNER JOIN "user" u ON tca.submitted_by = u.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tar.edited_prompt_rating IS NOT NULL AND tar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_audit_review tar + INNER JOIN "user" u ON tar.submitted_by = u.id + INNER JOIN task_audit ta ON tar.task_audit_id = ta.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tcar.edited_prompt_rating IS NOT NULL AND tcar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + u.image_url AS image_url + FROM + task_contribution_audit_review tcar + INNER JOIN "user" u ON tcar.submitted_by = u.id + INNER JOIN task_contribution_audit tca ON tcar.task_contribution_audit_id = tca.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + ) AS combined_results + ) AS final_results + GROUP BY + user_id, + username, + image_url + ORDER BY + points DESC; + """ + + # first, we'll update the overall leaderboard table with points and ranks + try: + with Session(db.engine) as session: + # get the records by combining the user table and the task_audit table + overall_records = session.execute(text(query)).all() + + if not overall_records: + logger.info('no records found for overall leaderboard update') + return None + + # then re-rank the users based on the number of points they have earned + # overall + overall_records.sort(key=lambda x: x.points, reverse=True) + + # create a list of LeaderboardOverall objects and then assign them a rank + # based on their position in the sorted list + overall_leaderboard_records = [] + for i, record in enumerate(overall_records): + lb_record = dict( + user_id=record.user_id, + languages=record.languages, + username=record.username, + points=record.points, + image_url=record.image_url, + rank=i+1, + ) + overall_leaderboard_records.append(lb_record) + + overall_leaderboard_records = even_out_ranks(overall_leaderboard_records) + + # then update the leaderboard_overall table + insert_stmt = insert( + LeaderboardOverall + ).values( + overall_leaderboard_records + ) + + statement = insert_stmt.on_conflict_do_update( + index_elements=['user_id'], + # update the points and rank columns + # if the user_id already exists in the table + set_={ + 'points': insert_stmt.excluded.points, + 'rank': insert_stmt.excluded.rank, + 'languages': insert_stmt.excluded.languages, + 'username': insert_stmt.excluded.username, + }, + ).returning( + LeaderboardOverall.id + ) + results = session.execute(statement).fetchall() + session.commit() + except Exception as e: + logger.error(f'error while updating overall leaderboard: {e}') + raise e + else: + logger.info(f'overall leaderboard updated with {len(results)} records!') + + # next we'll use the existing records in the leaderboard_overall table to + # update the blended points and blended ranks + quality_score_query = """ + -- Calculate the average rating for each user based on the reviews they have received for task audits + WITH combined_task_audit_reviews AS ( + SELECT + u.id AS user_id, + u.username AS username, + AVG((tar.edited_prompt_rating + tar.edited_completion_rating) / 2) AS avg_rating_for_task_audits + FROM task_audit_review tar + JOIN task_audit ta ON ta.id = tar.task_audit_id + JOIN "user" u ON u.id = ta.submitted_by + GROUP BY u.id, u.username + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + AVG((tcar.edited_prompt_rating + tcar.edited_completion_rating) / 2) AS avg_rating_for_task_audits + FROM task_contribution_audit_review tcar + JOIN task_contribution_audit tca ON tca.id = tcar.task_contribution_audit_id + JOIN "user" u ON u.id = tca.submitted_by + GROUP BY u.id, u.username + ), + -- Calculate the number of audits each user has completed and also edited + task_audits_edited AS ( + SELECT + u.id AS user_id, + u.username AS username, + SUM( + CASE + WHEN (ta.prompt_edited = true OR ta.completion_edited = true) THEN 1 + ELSE 0 + END + ) AS num_audits_edited + FROM task_audit ta + JOIN "user" u ON u.id = ta.submitted_by + GROUP BY u.id, u.username + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + SUM( + CASE + WHEN (tca.prompt_edited = true OR tca.completion_edited = true) THEN 1 + ELSE 0 + END + ) AS num_audits_edited + FROM task_contribution_audit tca + JOIN "user" u ON u.id = tca.submitted_by + GROUP BY u.id, u.username + ), + -- this counts the number of audits each user has reviewed in task 3 + -- and further edited + task_audits_reviewed AS ( + SELECT + u.id AS user_id, + u.username AS username, + COUNT(*) AS num_audits_reviewed_and_improved + FROM task_audit_review tar + JOIN "user" u ON u.id = tar.submitted_by + WHERE (tar.improved_prompt != '' OR tar.improved_completion != '') + GROUP BY u.id, u.username + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + COUNT(*) AS num_audits_reviewed_and_improved + FROM task_contribution_audit_review tcar + JOIN "user" u ON u.id = tcar.submitted_by + WHERE (tcar.improved_prompt != '' OR tcar.improved_completion != '') + GROUP BY u.id, u.username + ), + -- count the number of tasks that have been contributed per user + tasks_contributed AS ( + SELECT + u.id as user_id, + u.username, + COUNT(*) as num_tasks_contributed + FROM task_contribution tc + JOIN "user" u ON u.id = tc.submitted_by + GROUP BY u.id, u.username + ORDER BY u.username ASC + ), + -- count the number of thumbs up out of total audits given per user + task_audits_for_contributions AS ( + SELECT + u.id AS user_id, + u.username, + tc.id AS task_contribution_id, + tca.prompt_rating, + tca.completion_rating + FROM public."user" u + JOIN task_contribution tc ON u.id = tc.submitted_by + JOIN task_contribution_audit tca ON tc.id = tca.task_contribution_id + ), + thumbs_up_per_contribution_audit AS ( + SELECT + user_id, + username, + COUNT(DISTINCT task_contribution_id) AS total_contributions, + COUNT(*) AS total_audits, + SUM(CASE WHEN prompt_rating = 1 THEN 1 ELSE 0 END) AS positive_prompt_ratings, + SUM(CASE WHEN completion_rating = 1 THEN 1 ELSE 0 END) AS positive_completion_ratings + FROM task_audits_for_contributions + GROUP BY user_id, username + ), + thumbs_up_ratio_per_contribution_audit AS ( + SELECT + user_id, + username, + CASE WHEN total_audits > 0 THEN (positive_prompt_ratings::decimal / total_audits) ELSE 0.0 END AS avg_prompt_thumbs_up_ratio, + CASE WHEN total_audits > 0 THEN (positive_completion_ratings::decimal / total_audits) ELSE 0.0 END AS avg_completion_thumbs_up_ratio, + CASE WHEN total_audits > 0 THEN ((positive_prompt_ratings::decimal / total_audits) + (positive_completion_ratings::decimal / total_audits)) / 2 ELSE 0.0 END AS avg_thumbs_up_ratio + FROM thumbs_up_per_contribution_audit + ), + aggregated_user_data AS ( + SELECT + u.id AS user_id, + u.username AS username, + COALESCE(AVG(ctar.avg_rating_for_task_audits), 0) AS avg_quality_score, + COALESCE(SUM(e.num_audits_edited), 0) AS num_audits_edited, + COALESCE(SUM(r.num_audits_reviewed_and_improved), 0) AS num_audits_reviewed_and_improved, + t.num_tasks_contributed AS num_tasks_contributed + FROM "user" u + LEFT JOIN task_audits_edited e ON u.id = e.user_id + LEFT JOIN task_audits_reviewed r ON u.id = r.user_id + LEFT JOIN tasks_contributed t ON u.id = t.user_id + LEFT JOIN combined_task_audit_reviews ctar ON u.id = ctar.user_id + GROUP BY u.id, u.username, t.num_tasks_contributed + ) + -- Combine and join the aggregated user data with the other subqueries + SELECT + u.id AS user_id, + u.username AS username, + COALESCE(a.avg_quality_score, 0) AS avg_quality_score, + COALESCE(a.num_audits_edited, 0) AS num_audits_edited, + COALESCE(a.num_audits_reviewed_and_improved, 0) AS num_audits_reviewed_and_improved, + COALESCE(a.num_tasks_contributed, 0) AS num_tasks_contributed, + COALESCE(tp.avg_thumbs_up_ratio, 0) AS avg_thumbs_up_ratio + FROM "user" u + LEFT JOIN aggregated_user_data a ON u.id = a.user_id + LEFT JOIN thumbs_up_ratio_per_contribution_audit tp ON u.id = tp.user_id + ORDER BY username ASC; + """ + try: + with Session(db.engine) as session: + # get the records by combining the user table and the task_audit table + aya_score_component_records = session.execute(text(quality_score_query)).all() + + if not aya_score_component_records: + raise Exception('no records found for quality score update') + + # create a list of user_ids for users we computed scores for + aya_score_user_ids = [ + record.user_id + for record in aya_score_component_records + ] + + # then get the records from the leaderboard_overall table + leaderboard_records = session.query(LeaderboardOverall).all() + + # then assign blended points to each user based on the function: + # Aya Score = AYA Score = max(0, (Avg Quality - 3.0)) * + # (3.0 * (Number Prompts / Completions Audited and Edited + Number of Tasks Further Edited)) + + # ( 3.0 * (Number of Thumbs Up / Number of Thumbs) * Number of Task 2 Contributions ) + # where Quality is the average rating for the user. + # + # We are essentially extrapolating out the average score computed in task 3 to + # everything the user has audited and edited. Which makes sense because we are essentially + # saying we form a guess of quality based on a subset but reward the user for all edits + # they have done as a sign since we trust the sample edited is a good proxy for overall quality. + for lb_record in leaderboard_records: + if lb_record.user_id in aya_score_user_ids: + for record in aya_score_component_records: + if record.user_id == lb_record.user_id: + logger.info( + 'computing aya score for user_id and username: ' + f'{lb_record.user_id}, {lb_record.username}' + ) + aya_score = compute_aya_score( + thumbs_up_received_ratio=record.avg_thumbs_up_ratio, + num_contributed_tasks=record.num_tasks_contributed, + avg_quality_score=record.avg_quality_score, + num_audits_edited=record.num_audits_edited, + num_audits_further_improved=record.num_audits_reviewed_and_improved, + ) + lb_record.blended_points = round(aya_score) + lb_record.quality_score = round(record.avg_quality_score, 2) + else: + # this should never happen + logger.error('no aya score found for user_id: {lb_record.user_id} and username: {lb_record.username}') + raise Exception('no aya score found for user_id: {lb_record.user_id} and username: {lb_record.username}') + + # then re-rank the users based on the number of blended points they have earned + leaderboard_records.sort(key=lambda x: x.blended_points, reverse=True) + + # assign a blended rank based on their position in the sorted list + for i, record in enumerate(leaderboard_records): + record.blended_rank = i+1 + + # if any users have the same number of blended points, then they should + # have the same blended rank + leaderboard_records = even_out_blended_ranks(leaderboard_records) + + # then commit the changes to the database + session.commit() + except Exception as e: + logger.error(f'error while updating blended ranking and points for overall leaderboard: {e}') + raise e + else: + logger.info('blended ranking and points updated for overall leaderboard!') diff --git a/backend/jobs/weekly/Dockerfile b/backend/jobs/weekly/Dockerfile new file mode 100644 index 0000000..b2405d8 --- /dev/null +++ b/backend/jobs/weekly/Dockerfile @@ -0,0 +1,15 @@ +# Start with the official Python 3.10 image +FROM python:3.10-slim-buster + +# Set the working directory to /job +WORKDIR /job + +# Copy the backend code into the container +COPY ./ /job/ + +# Install the dependencies using poetry +RUN pip install poetry +RUN poetry config virtualenvs.create false +RUN poetry install + +CMD ["python", "-m", "jobs.leaderboard_update_job", "--leaderboard", "weekly"] \ No newline at end of file diff --git a/backend/jobs/weekly/__init__.py b/backend/jobs/weekly/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/jobs/weekly/leaderboard_weekly_job.py b/backend/jobs/weekly/leaderboard_weekly_job.py new file mode 100644 index 0000000..3f6a3d0 --- /dev/null +++ b/backend/jobs/weekly/leaderboard_weekly_job.py @@ -0,0 +1,328 @@ +import logging + +from datetime import datetime, timedelta + +from sqlalchemy import text +from sqlalchemy.dialects.postgresql import insert + +from sqlmodel import Session + +from instruct_multilingual import db +from instruct_multilingual.models import ( + LeaderboardWeekly, +) + +from jobs.common import even_out_ranks, update_ranks_within_language_groups + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + + +def update_leaderboard_weekly(): + """ + Query the task_audit and user table, and then update the `leaderboard_daily` table + with the number of points each user has earned in the past week. + """ + query = """ + -- combined task audit and task contribution points using UNION ALL + SELECT + user_id, + username, + ARRAY_AGG(DISTINCT language) AS languages, + SUM(points) AS points, + lb_week, + image_url, + 0 AS rank + FROM ( + SELECT + user_id, + username, + language, + points, + lb_week, + image_url + FROM ( + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN ta.prompt_rating IS NOT NULL AND ta.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN ta.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + DATE_TRUNC( + 'week', + CURRENT_DATE + ) AS lb_week, + u.image_url AS image_url + FROM + task_audit ta + INNER JOIN "user" u ON ta.submitted_by = u.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + WHERE + -- Find records that begin on Monday and end on Sunday at 23:59 UTC + ta.created_at >= DATE_TRUNC('week', CURRENT_DATE) + AND + ta.created_at < DATE_TRUNC('week', CURRENT_DATE) + INTERVAL '1 week' + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM(2), 0) AS points, + DATE_TRUNC( + 'week', + CURRENT_DATE + ) AS lb_week, + u.image_url AS image_url + FROM + task_contribution tc + INNER JOIN "user" u ON tc.submitted_by = u.id + INNER JOIN language_code lc ON tc.language_id = lc.id + WHERE + -- Find records that begin on Monday and end on Sunday at 23:59 UTC + tc.created_at >= DATE_TRUNC('week', CURRENT_DATE) + AND + tc.created_at < DATE_TRUNC('week', CURRENT_DATE) + INTERVAL '1 week' + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tca.prompt_rating IS NOT NULL AND tca.completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.prompt_edited THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tca.completion_edited THEN 1 + ELSE 0 + END + ), 0) AS points, + DATE_TRUNC( + 'week', + CURRENT_DATE + ) AS lb_week, + u.image_url AS image_url + FROM + task_contribution_audit tca + INNER JOIN "user" u ON tca.submitted_by = u.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + WHERE + -- Find records that begin on Monday and end on Sunday at 23:59 UTC + tc.created_at >= DATE_TRUNC('week', CURRENT_DATE) + AND + tc.created_at < DATE_TRUNC('week', CURRENT_DATE) + INTERVAL '1 week' + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tar.edited_prompt_rating IS NOT NULL AND tar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + DATE_TRUNC( + 'week', + CURRENT_DATE + ) AS lb_week, + u.image_url AS image_url + FROM + task_audit_review tar + INNER JOIN "user" u ON tar.submitted_by = u.id + INNER JOIN task_audit ta ON tar.task_audit_id = ta.id + INNER JOIN task t ON ta.task_id = t.id + INNER JOIN language_code lc ON t.language_id = lc.id + WHERE + -- Find records that begin on Monday and end on Sunday at 23:59 UTC + tar.created_at >= DATE_TRUNC('week', CURRENT_DATE) + AND + tar.created_at < DATE_TRUNC('week', CURRENT_DATE) + INTERVAL '1 week' + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + UNION ALL + + SELECT + u.id AS user_id, + u.username AS username, + lc.name AS language, + COALESCE(SUM( + CASE + WHEN tcar.edited_prompt_rating IS NOT NULL AND tcar.edited_completion_rating IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_prompt IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) + + COALESCE(SUM( + CASE + WHEN tcar.improved_completion IS NOT NULL THEN 1 + ELSE 0 + END + ), 0) AS points, + DATE_TRUNC( + 'week', + CURRENT_DATE + ) AS lb_week, + u.image_url AS image_url + FROM + task_contribution_audit_review tcar + INNER JOIN "user" u ON tcar.submitted_by = u.id + INNER JOIN task_contribution_audit tca ON tcar.task_contribution_audit_id = tca.id + INNER JOIN task_contribution tc ON tca.task_contribution_id = tc.id + INNER JOIN language_code lc ON tc.language_id = lc.id + WHERE + -- Find records that begin on Monday and end on Sunday at 23:59 UTC + tcar.created_at >= DATE_TRUNC('week', CURRENT_DATE) + AND + tcar.created_at < DATE_TRUNC('week', CURRENT_DATE) + INTERVAL '1 week' + GROUP BY + u.id, + lc.name, + u.username, + u.image_url + + ) AS combined_results + ) AS final_results + GROUP BY + user_id, + username, + image_url, + lb_week + ORDER BY + points DESC; + """ + # Get the current date + today = datetime.utcnow().date() + + # Calculate the start of the week (Monday) + start_of_week = today - timedelta(days=today.weekday()) + + logger.info(f'updating weekly leaderboard for week of {start_of_week}...') + try: + with Session(db.engine) as session: + # get the weekly records by combining the user table and the task_audit table + weekly_records = session.execute(text(query)).all() + + if not weekly_records: + logger.info('no records found for weekly leaderboard update') + return None + + # then re-rank the users based on the number of points they have earned + # in the past 7 days + weekly_records.sort(key=lambda x: x.points, reverse=True) + + # create a list of LeaderboardWeekly objects and then assign them a rank + # based on their position in the sorted list + weekly_leaderboard_records = [] + for i, record in enumerate(weekly_records): + lb_record = dict( + user_id=record.user_id, + username=record.username, + languages=record.languages, + points=record.points, + week_of=record.lb_week, + image_url=record.image_url, + rank=i+1, + ) + weekly_leaderboard_records.append(lb_record) + + weekly_leaderboard_records = even_out_ranks(weekly_leaderboard_records) + + # then update the weekly leaderboard table + insert_stmt = insert( + LeaderboardWeekly + ).values( + weekly_leaderboard_records + ) + + statement = insert_stmt.on_conflict_do_update( + index_elements=['user_id', 'week_of'], + # update the points and rank columns + # if the user_id and week already exist in the table + set_={ + 'points': insert_stmt.excluded.points, + 'username': insert_stmt.excluded.username, + 'rank': insert_stmt.excluded.rank, + 'languages': insert_stmt.excluded.languages, + }, + ).returning( + LeaderboardWeekly.id + ) + results = session.execute(statement).fetchall() + session.commit() + except Exception as e: + logger.error(f'error while updating weekly leaderboard: {e}') + raise e + else: + logger.info(f'weekly leaderboard updated for week of {start_of_week} with {len(results)} records!') \ No newline at end of file diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..b68fd87 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,95 @@ +import logging +import os +import sys + +from pathlib import Path + +from alembic.config import Config as AlembicConfig +from alembic.command import upgrade as alembic_upgrade + +from fastapi import FastAPI, Request, status, HTTPException +from fastapi.exceptions import RequestValidationError +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from instruct_multilingual.api.v1.api import api_router +from instruct_multilingual.config import get_settings + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[ + logging.StreamHandler(sys.stdout), + ], +) + +logger = logging.getLogger(__name__) +settings = get_settings() + +app = FastAPI(title=settings.app_name) + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + settings.frontend_url, + settings.for_ai_url, + "http://localhost", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"] +) + +# https://fastapi.tiangolo.com/advanced/events/ +if settings.run_migrations_on_startup: + @app.on_event("startup") + def run_alembic_migrations(): + alembic_ini_path = ( + Path(__file__).parent / "alembic.ini" + ).as_posix() + + alembic_cfg = AlembicConfig( + str(alembic_ini_path), + attributes={'configure_logger': False}, + ) + + logger.info("attempting to run alembic migrations on startup...") + try: + alembic_upgrade(alembic_cfg, "head") + except Exception: + logger.exception("alembic migrations failed on startup due to exception") + else: + logger.info("successfully upgraded alembic!") + + +app.include_router(api_router, prefix="/api/v1") + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler( + request: Request, + exc: RequestValidationError, +): + logger.error(f"Request validation error: {exc.errors()}") + return JSONResponse( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + content={"detail": exc.errors()}, + ) + + +@app.exception_handler(HTTPException) +async def internal_server_error_handler(request: Request, exc: HTTPException): + if exc.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR: + logger.error(f"Internal server error: {exc.detail}") + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content={"detail": "Internal server error"}, + ) + return JSONResponse( + status_code=exc.status_code, + content={"detail": exc.detail}, + ) + + +@app.get("/") +async def root(): + return {"message": "Hello World"} diff --git a/backend/poetry.lock b/backend/poetry.lock new file mode 100644 index 0000000..bb2da2e --- /dev/null +++ b/backend/poetry.lock @@ -0,0 +1,3492 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "aiocache" +version = "0.11.1" +description = "multi backend asyncio cache" +optional = false +python-versions = "*" +files = [ + {file = "aiocache-0.11.1-py2.py3-none-any.whl", hash = "sha256:e55c7caaa5753794fd301c3a2e592737fa1d036db9f8d04ae154facdfb48a157"}, + {file = "aiocache-0.11.1.tar.gz", hash = "sha256:f2ebe0b05cec45782e7b5ea0bb74640f157dd4bb1028b4565364dda9fe33be7f"}, +] + +[package.extras] +dev = ["asynctest (>=0.11.0)", "black", "codecov", "coverage", "flake8", "ipdb", "marshmallow", "pystache", "pytest", "pytest-asyncio", "pytest-mock", "sphinx", "sphinx-autobuild", "sphinx-rtd-theme"] +memcached = ["aiomcache (>=0.5.2)"] +msgpack = ["msgpack (>=0.5.5)"] +redis = ["aioredis (>=0.3.3)", "aioredis (>=1.0.0)"] + +[[package]] +name = "aiohttp" +version = "3.8.3" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"}, + {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"}, + {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"}, + {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"}, + {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"}, + {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"}, + {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"}, + {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"}, + {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"}, + {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"}, + {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, + {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "alembic" +version = "1.11.3" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "alembic-1.11.3-py3-none-any.whl", hash = "sha256:d6c96c2482740592777c400550a523bc7a9aada4e210cae2e733354ddae6f6f8"}, + {file = "alembic-1.11.3.tar.gz", hash = "sha256:3db4ce81a9072e1b5aa44c2d202add24553182672a12daf21608d6f62a8f9cf9"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["python-dateutil"] + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + +[[package]] +name = "arrow" +version = "1.2.3" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, + {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" + +[[package]] +name = "asttokens" +version = "2.2.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, + {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + +[[package]] +name = "async-lru" +version = "2.0.4" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, + {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "babel" +version = "2.12.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bleach" +version = "6.0.0" +description = "An easy safelist-based HTML-sanitizing tool." +optional = false +python-versions = ">=3.7" +files = [ + {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, + {file = "bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414"}, +] + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.2)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.1.4" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.6" +files = [ + {file = "comm-0.1.4-py3-none-any.whl", hash = "sha256:6d52794cba11b36ed9860999cd10fd02d6b2eac177068fdd585e1e2f8a96e67a"}, + {file = "comm-0.1.4.tar.gz", hash = "sha256:354e40a59c9dd6db50c5cc6b4acc887d82e9603787f83b68c01a80a923984d15"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +lint = ["black (>=22.6.0)", "mdformat (>0.7)", "mdformat-gfm (>=0.3.5)", "ruff (>=0.0.156)"] +test = ["pytest"] +typing = ["mypy (>=0.990)"] + +[[package]] +name = "contourpy" +version = "1.1.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc"}, + {file = "contourpy-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, + {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, + {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, + {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, + {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, + {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, + {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, + {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, + {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, + {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, + {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, + {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, + {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, + {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, + {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f"}, + {file = "contourpy-1.1.0.tar.gz", hash = "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21"}, +] + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "wurlitzer"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] + +[[package]] +name = "debugpy" +version = "1.6.7.post1" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "debugpy-1.6.7.post1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:903bd61d5eb433b6c25b48eae5e23821d4c1a19e25c9610205f5aeaccae64e32"}, + {file = "debugpy-1.6.7.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d16882030860081e7dd5aa619f30dec3c2f9a421e69861125f83cc372c94e57d"}, + {file = "debugpy-1.6.7.post1-cp310-cp310-win32.whl", hash = "sha256:eea8d8cfb9965ac41b99a61f8e755a8f50e9a20330938ad8271530210f54e09c"}, + {file = "debugpy-1.6.7.post1-cp310-cp310-win_amd64.whl", hash = "sha256:85969d864c45f70c3996067cfa76a319bae749b04171f2cdeceebe4add316155"}, + {file = "debugpy-1.6.7.post1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:890f7ab9a683886a0f185786ffbda3b46495c4b929dab083b8c79d6825832a52"}, + {file = "debugpy-1.6.7.post1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4ac7a4dba28801d184b7fc0e024da2635ca87d8b0a825c6087bb5168e3c0d28"}, + {file = "debugpy-1.6.7.post1-cp37-cp37m-win32.whl", hash = "sha256:3370ef1b9951d15799ef7af41f8174194f3482ee689988379763ef61a5456426"}, + {file = "debugpy-1.6.7.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:65b28435a17cba4c09e739621173ff90c515f7b9e8ea469b92e3c28ef8e5cdfb"}, + {file = "debugpy-1.6.7.post1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:92b6dae8bfbd497c90596bbb69089acf7954164aea3228a99d7e43e5267f5b36"}, + {file = "debugpy-1.6.7.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72f5d2ecead8125cf669e62784ef1e6300f4067b0f14d9f95ee00ae06fc7c4f7"}, + {file = "debugpy-1.6.7.post1-cp38-cp38-win32.whl", hash = "sha256:f0851403030f3975d6e2eaa4abf73232ab90b98f041e3c09ba33be2beda43fcf"}, + {file = "debugpy-1.6.7.post1-cp38-cp38-win_amd64.whl", hash = "sha256:3de5d0f97c425dc49bce4293df6a04494309eedadd2b52c22e58d95107e178d9"}, + {file = "debugpy-1.6.7.post1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:38651c3639a4e8bbf0ca7e52d799f6abd07d622a193c406be375da4d510d968d"}, + {file = "debugpy-1.6.7.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:038c51268367c9c935905a90b1c2d2dbfe304037c27ba9d19fe7409f8cdc710c"}, + {file = "debugpy-1.6.7.post1-cp39-cp39-win32.whl", hash = "sha256:4b9eba71c290852f959d2cf8a03af28afd3ca639ad374d393d53d367f7f685b2"}, + {file = "debugpy-1.6.7.post1-cp39-cp39-win_amd64.whl", hash = "sha256:973a97ed3b434eab0f792719a484566c35328196540676685c975651266fccf9"}, + {file = "debugpy-1.6.7.post1-py2.py3-none-any.whl", hash = "sha256:1093a5c541af079c13ac8c70ab8b24d1d35c8cacb676306cf11e57f699c02926"}, + {file = "debugpy-1.6.7.post1.zip", hash = "sha256:fe87ec0182ef624855d05e6ed7e0b7cb1359d2ffa2a925f8ec2d22e98b75d0ca"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + +[[package]] +name = "fancycompleter" +version = "0.9.1" +description = "colorful TAB completion for Python prompt" +optional = false +python-versions = "*" +files = [ + {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, + {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, +] + +[package.dependencies] +pyreadline = {version = "*", markers = "platform_system == \"Windows\""} +pyrepl = ">=0.8.2" + +[[package]] +name = "fastapi" +version = "0.85.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.85.0-py3-none-any.whl", hash = "sha256:1803d962f169dc9f8dde54a64b22eb16f6d81573f54401971f90f0a67234a8b4"}, + {file = "fastapi-0.85.0.tar.gz", hash = "sha256:bb219cfafd0d2ccf8f32310c9a257a06b0210bd8e2a03706a6f5a9f9f1416878"}, +] + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.20.4" + +[package.extras] +all = ["email-validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"] +dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.7.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.971)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-orjson (==3.6.2)", "types-ujson (==5.4.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] + +[[package]] +name = "fastapi-discord" +version = "0.2.4" +description = "Discord OAuth FastAPI extension for APIs" +optional = false +python-versions = ">=3.5" +files = [ + {file = "fastapi_discord-0.2.4-py3-none-any.whl", hash = "sha256:75855d9839cc903b4eaf5de42ac8b0e29f8339ed1b617b34cc137082ce76a7ed"}, +] + +[package.dependencies] +aiocache = "0.11.1" +aiohttp = "3.8.3" +fastapi = "0.85.0" + +[[package]] +name = "fastapi-sqlalchemy" +version = "0.2.1" +description = "Adds simple SQLAlchemy support to FastAPI" +optional = false +python-versions = ">=3.7" +files = [ + {file = "FastAPI-SQLAlchemy-0.2.1.tar.gz", hash = "sha256:7a9d44e46cbc73c3f5ee8c444f7e0bcd3d01370a878740abd4cd4d2e900ce9af"}, + {file = "FastAPI_SQLAlchemy-0.2.1-py3-none-any.whl", hash = "sha256:d3bfc6d9388a73a2c3726bc6bd7764cd82debfa71c16e3991c544b9701f48d96"}, +] + +[package.dependencies] +SQLAlchemy = ">=1.2" +starlette = ">=0.12.9" + +[[package]] +name = "fastjsonschema" +version = "2.18.0" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.18.0-py3-none-any.whl", hash = "sha256:128039912a11a807068a7c87d0da36660afbfd7202780db26c4aa7153cfdc799"}, + {file = "fastjsonschema-2.18.0.tar.gz", hash = "sha256:e820349dd16f806e4bd1467a138dced9def4bc7d6213a34295272a6cac95b5bd"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "fonttools" +version = "4.42.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed1a13a27f59d1fc1920394a7f596792e9d546c9ca5a044419dca70c37815d7c"}, + {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b1ce7a45978b821a06d375b83763b27a3a5e8a2e4570b3065abad240a18760"}, + {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f720fa82a11c0f9042376fd509b5ed88dab7e3cd602eee63a1af08883b37342b"}, + {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db55cbaea02a20b49fefbd8e9d62bd481aaabe1f2301dabc575acc6b358874fa"}, + {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a35981d90feebeaef05e46e33e6b9e5b5e618504672ca9cd0ff96b171e4bfff"}, + {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68a02bbe020dc22ee0540e040117535f06df9358106d3775e8817d826047f3fd"}, + {file = "fonttools-4.42.1-cp310-cp310-win32.whl", hash = "sha256:12a7c247d1b946829bfa2f331107a629ea77dc5391dfd34fdcd78efa61f354ca"}, + {file = "fonttools-4.42.1-cp310-cp310-win_amd64.whl", hash = "sha256:a398bdadb055f8de69f62b0fc70625f7cbdab436bbb31eef5816e28cab083ee8"}, + {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:689508b918332fb40ce117131633647731d098b1b10d092234aa959b4251add5"}, + {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e36344e48af3e3bde867a1ca54f97c308735dd8697005c2d24a86054a114a71"}, + {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7db825c8adee96fac0692e6e1ecd858cae9affb3b4812cdb9d934a898b29e"}, + {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113337c2d29665839b7d90b39f99b3cac731f72a0eda9306165a305c7c31d341"}, + {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37983b6bdab42c501202500a2be3a572f50d4efe3237e0686ee9d5f794d76b35"}, + {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6ed2662a3d9c832afa36405f8748c250be94ae5dfc5283d668308391f2102861"}, + {file = "fonttools-4.42.1-cp311-cp311-win32.whl", hash = "sha256:179737095eb98332a2744e8f12037b2977f22948cf23ff96656928923ddf560a"}, + {file = "fonttools-4.42.1-cp311-cp311-win_amd64.whl", hash = "sha256:f2b82f46917d8722e6b5eafeefb4fb585d23babd15d8246c664cd88a5bddd19c"}, + {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:62f481ac772fd68901573956231aea3e4b1ad87b9b1089a61613a91e2b50bb9b"}, + {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2f806990160d1ce42d287aa419df3ffc42dfefe60d473695fb048355fe0c6a0"}, + {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db372213d39fa33af667c2aa586a0c1235e88e9c850f5dd5c8e1f17515861868"}, + {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d18fc642fd0ac29236ff88ecfccff229ec0386090a839dd3f1162e9a7944a40"}, + {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8708b98c278012ad267ee8a7433baeb809948855e81922878118464b274c909d"}, + {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c95b0724a6deea2c8c5d3222191783ced0a2f09bd6d33f93e563f6f1a4b3b3a4"}, + {file = "fonttools-4.42.1-cp38-cp38-win32.whl", hash = "sha256:4aa79366e442dbca6e2c8595645a3a605d9eeabdb7a094d745ed6106816bef5d"}, + {file = "fonttools-4.42.1-cp38-cp38-win_amd64.whl", hash = "sha256:acb47f6f8680de24c1ab65ebde39dd035768e2a9b571a07c7b8da95f6c8815fd"}, + {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb289b7a815638a7613d46bcf324c9106804725b2bb8ad913c12b6958ffc4ec"}, + {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:53eb5091ddc8b1199330bb7b4a8a2e7995ad5d43376cadce84523d8223ef3136"}, + {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46a0ec8adbc6ff13494eb0c9c2e643b6f009ce7320cf640de106fb614e4d4360"}, + {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cc7d685b8eeca7ae69dc6416833fbfea61660684b7089bca666067cb2937dcf"}, + {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:be24fcb80493b2c94eae21df70017351851652a37de514de553435b256b2f249"}, + {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:515607ec756d7865f23070682622c49d922901943697871fc292277cf1e71967"}, + {file = "fonttools-4.42.1-cp39-cp39-win32.whl", hash = "sha256:0eb79a2da5eb6457a6f8ab904838454accc7d4cccdaff1fd2bd3a0679ea33d64"}, + {file = "fonttools-4.42.1-cp39-cp39-win_amd64.whl", hash = "sha256:7286aed4ea271df9eab8d7a9b29e507094b51397812f7ce051ecd77915a6e26b"}, + {file = "fonttools-4.42.1-py3-none-any.whl", hash = "sha256:9398f244e28e0596e2ee6024f808b06060109e33ed38dcc9bded452fd9bbb853"}, + {file = "fonttools-4.42.1.tar.gz", hash = "sha256:c391cd5af88aacaf41dd7cfb96eeedfad297b5899a39e12f4c2c3706d0a3329d"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "fqdn" +version = "1.5.1" +description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +optional = false +python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +files = [ + {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, + {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, +] + +[[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "0.17.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "httpx" +version = "0.24.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, + {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, +] + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.18.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipykernel" +version = "6.25.1" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.25.1-py3-none-any.whl", hash = "sha256:c8a2430b357073b37c76c21c52184db42f6b4b0e438e1eb7df3c4440d120497c"}, + {file = "ipykernel-6.25.1.tar.gz", hash = "sha256:050391364c0977e768e354bdb60cbbfbee7cbb943b1af1618382021136ffd42f"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=20" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.14.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"}, + {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +optional = false +python-versions = "*" +files = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] + +[[package]] +name = "ipywidgets" +version = "8.1.0" +description = "Jupyter interactive widgets" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipywidgets-8.1.0-py3-none-any.whl", hash = "sha256:6c8396cc7b8c95dfb4e9ab0054f48c002f045e7e5d7ae523f559d64e525a98ab"}, + {file = "ipywidgets-8.1.0.tar.gz", hash = "sha256:ce97dd90525b3066fd00094690964e7eac14cf9b7745d35565b5eeac20cce687"}, +] + +[package.dependencies] +comm = ">=0.1.3" +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0.7,<3.1.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0.7,<4.1.0" + +[package.extras] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + +[[package]] +name = "isoduration" +version = "20.11.0" +description = "Operations with ISO 8601 durations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, + {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, +] + +[package.dependencies] +arrow = ">=0.15.0" + +[[package]] +name = "jedi" +version = "0.19.0" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"}, + {file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "json5" +version = "0.9.14" +description = "A Python implementation of the JSON5 data format." +optional = false +python-versions = "*" +files = [ + {file = "json5-0.9.14-py2.py3-none-any.whl", hash = "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f"}, + {file = "json5-0.9.14.tar.gz", hash = "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"}, +] + +[package.extras] +dev = ["hypothesis"] + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, +] + +[[package]] +name = "jsonschema" +version = "4.19.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.19.0-py3-none-any.whl", hash = "sha256:043dc26a3845ff09d20e4420d6012a9c91c9aa8999fa184e7efcfeccb41e32cb"}, + {file = "jsonschema-4.19.0.tar.gz", hash = "sha256:6e1e7569ac13be8139b2dd2c21a55d350066ee3f80df06c608b398cdc6f30e8f"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +fqdn = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +idna = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +isoduration = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format-nongpl\""} +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} +rpds-py = ">=0.7.1" +uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +webcolors = {version = ">=1.11", optional = true, markers = "extra == \"format-nongpl\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.7.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, + {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, +] + +[package.dependencies] +referencing = ">=0.28.0" + +[[package]] +name = "jupyter" +version = "1.0.0" +description = "Jupyter metapackage. Install all the Jupyter components in one go." +optional = false +python-versions = "*" +files = [ + {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, + {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, + {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, +] + +[package.dependencies] +ipykernel = "*" +ipywidgets = "*" +jupyter-console = "*" +nbconvert = "*" +notebook = "*" +qtconsole = "*" + +[[package]] +name = "jupyter-client" +version = "8.3.0" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.3.0-py3-none-any.whl", hash = "sha256:7441af0c0672edc5d28035e92ba5e32fadcfa8a4e608a434c228836a89df6158"}, + {file = "jupyter_client-8.3.0.tar.gz", hash = "sha256:3af69921fe99617be1670399a0b857ad67275eefcfa291e2c81a160b7b650f5f"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +description = "Jupyter terminal console" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, + {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, +] + +[package.dependencies] +ipykernel = ">=6.14" +ipython = "*" +jupyter-client = ">=7.0.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +prompt-toolkit = ">=3.0.30" +pygments = "*" +pyzmq = ">=17" +traitlets = ">=5.4" + +[package.extras] +test = ["flaky", "pexpect", "pytest"] + +[[package]] +name = "jupyter-core" +version = "5.3.1" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.3.1-py3-none-any.whl", hash = "sha256:ae9036db959a71ec1cac33081eeb040a79e681f08ab68b0883e9a676c7a90dce"}, + {file = "jupyter_core-5.3.1.tar.gz", hash = "sha256:5ba5c7938a7f97a6b0481463f7ff0dbac7c15ba48cf46fa4035ca6e838aa1aba"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-events" +version = "0.7.0" +description = "Jupyter Event System library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_events-0.7.0-py3-none-any.whl", hash = "sha256:4753da434c13a37c3f3c89b500afa0c0a6241633441421f6adafe2fb2e2b924e"}, + {file = "jupyter_events-0.7.0.tar.gz", hash = "sha256:7be27f54b8388c03eefea123a4f79247c5b9381c49fb1cd48615ee191eb12615"}, +] + +[package.dependencies] +jsonschema = {version = ">=4.18.0", extras = ["format-nongpl"]} +python-json-logger = ">=2.0.4" +pyyaml = ">=5.3" +referencing = "*" +rfc3339-validator = "*" +rfc3986-validator = ">=0.1.1" +traitlets = ">=5.3" + +[package.extras] +cli = ["click", "rich"] +docs = ["jupyterlite-sphinx", "myst-parser", "pydata-sphinx-theme", "sphinxcontrib-spelling"] +test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "pytest-console-scripts", "rich"] + +[[package]] +name = "jupyter-lsp" +version = "2.2.0" +description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter-lsp-2.2.0.tar.gz", hash = "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1"}, + {file = "jupyter_lsp-2.2.0-py3-none-any.whl", hash = "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f"}, +] + +[package.dependencies] +jupyter-server = ">=1.1.2" + +[[package]] +name = "jupyter-server" +version = "2.7.2" +description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server-2.7.2-py3-none-any.whl", hash = "sha256:98a375347b580e837e7016007c24680a4261ed8ad7cd35196ac087d229f48e5a"}, + {file = "jupyter_server-2.7.2.tar.gz", hash = "sha256:d64fb4e593907290e5df916e3c9399c15ab2cd7bdb71cbcd1d36452dbfb30523"}, +] + +[package.dependencies] +anyio = ">=3.1.0" +argon2-cffi = "*" +jinja2 = "*" +jupyter-client = ">=7.4.4" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-events = ">=0.6.0" +jupyter-server-terminals = "*" +nbconvert = ">=6.4.4" +nbformat = ">=5.3.0" +overrides = "*" +packaging = "*" +prometheus-client = "*" +pywinpty = {version = "*", markers = "os_name == \"nt\""} +pyzmq = ">=24" +send2trash = ">=1.8.2" +terminado = ">=0.8.3" +tornado = ">=6.2.0" +traitlets = ">=5.6.0" +websocket-client = "*" + +[package.extras] +docs = ["ipykernel", "jinja2", "jupyter-client", "jupyter-server", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] +test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.4)", "pytest-timeout", "requests"] + +[[package]] +name = "jupyter-server-terminals" +version = "0.4.4" +description = "A Jupyter Server Extension Providing Terminals." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server_terminals-0.4.4-py3-none-any.whl", hash = "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36"}, + {file = "jupyter_server_terminals-0.4.4.tar.gz", hash = "sha256:57ab779797c25a7ba68e97bcfb5d7740f2b5e8a83b5e8102b10438041a7eac5d"}, +] + +[package.dependencies] +pywinpty = {version = ">=2.0.3", markers = "os_name == \"nt\""} +terminado = ">=0.8.3" + +[package.extras] +docs = ["jinja2", "jupyter-server", "mistune (<3.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] +test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] + +[[package]] +name = "jupyterlab" +version = "4.0.5" +description = "JupyterLab computational environment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab-4.0.5-py3-none-any.whl", hash = "sha256:13b3a326e7b95d72746fe20dbe80ee1e71165d6905e01ceaf1320eb809cb1b47"}, + {file = "jupyterlab-4.0.5.tar.gz", hash = "sha256:de49deb75f9b9aec478ed04754cbefe9c5d22fd796a5783cdc65e212983d3611"}, +] + +[package.dependencies] +async-lru = ">=1.0.0" +ipykernel = "*" +jinja2 = ">=3.0.3" +jupyter-core = "*" +jupyter-lsp = ">=2.0.0" +jupyter-server = ">=2.4.0,<3" +jupyterlab-server = ">=2.19.0,<3" +notebook-shim = ">=0.2" +packaging = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} +tornado = ">=6.2.0" +traitlets = "*" + +[package.extras] +dev = ["black[jupyter] (==23.3.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.271)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] +test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.2.2" +description = "Pygments theme using JupyterLab CSS variables" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, + {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, +] + +[[package]] +name = "jupyterlab-server" +version = "2.24.0" +description = "A set of server components for JupyterLab and JupyterLab like applications." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_server-2.24.0-py3-none-any.whl", hash = "sha256:5f077e142bb8dc9b843d960f940c513581bceca3793a0d80f9c67d9522c4e876"}, + {file = "jupyterlab_server-2.24.0.tar.gz", hash = "sha256:4e6f99e0a5579bbbc32e449c4dbb039561d4f1a7827d5733273ed56738f21f07"}, +] + +[package.dependencies] +babel = ">=2.10" +jinja2 = ">=3.0.3" +json5 = ">=0.9.0" +jsonschema = ">=4.17.3" +jupyter-server = ">=1.21,<3" +packaging = ">=21.3" +requests = ">=2.28" + +[package.extras] +docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] +openapi = ["openapi-core (>=0.16.1,<0.17.0)", "ruamel-yaml"] +test = ["hatch", "ipykernel", "jupyterlab-server[openapi]", "openapi-spec-validator (>=0.5.1,<0.7.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.8" +description = "Jupyter interactive widgets for JupyterLab" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_widgets-3.0.8-py3-none-any.whl", hash = "sha256:4715912d6ceab839c9db35953c764b3214ebbc9161c809f6e0510168845dfdf5"}, + {file = "jupyterlab_widgets-3.0.8.tar.gz", hash = "sha256:d428ab97b8d87cc7c54cbf37644d6e0f0e662f23876e05fa460a73ec3257252a"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] + +[[package]] +name = "mako" +version = "1.2.4" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, + {file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "matplotlib" +version = "3.7.2" +description = "Python plotting package" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de"}, + {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d"}, + {file = "matplotlib-3.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1"}, + {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603"}, + {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201"}, + {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8"}, + {file = "matplotlib-3.7.2-cp310-cp310-win32.whl", hash = "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a"}, + {file = "matplotlib-3.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1"}, + {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544"}, + {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e"}, + {file = "matplotlib-3.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64"}, + {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24"}, + {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d"}, + {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4"}, + {file = "matplotlib-3.7.2-cp311-cp311-win32.whl", hash = "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc"}, + {file = "matplotlib-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e"}, + {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7"}, + {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391"}, + {file = "matplotlib-3.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca"}, + {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9"}, + {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273"}, + {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117"}, + {file = "matplotlib-3.7.2-cp38-cp38-win32.whl", hash = "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13"}, + {file = "matplotlib-3.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676"}, + {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608"}, + {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256"}, + {file = "matplotlib-3.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f"}, + {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b"}, + {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e"}, + {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f"}, + {file = "matplotlib-3.7.2-cp39-cp39-win32.whl", hash = "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20"}, + {file = "matplotlib-3.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e"}, + {file = "matplotlib-3.7.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0"}, + {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b"}, + {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c"}, + {file = "matplotlib-3.7.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2"}, + {file = "matplotlib-3.7.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd"}, + {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07"}, + {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d"}, + {file = "matplotlib-3.7.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11"}, + {file = "matplotlib-3.7.2.tar.gz", hash = "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.0.1" +numpy = ">=1.20" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1,<3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mistune" +version = "3.0.1" +description = "A sane and fast Markdown parser with useful plugins and renderers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mistune-3.0.1-py3-none-any.whl", hash = "sha256:b9b3e438efbb57c62b5beb5e134dab664800bdf1284a7ee09e8b12b13eb1aac6"}, + {file = "mistune-3.0.1.tar.gz", hash = "sha256:e912116c13aa0944f9dc530db38eb88f6a77087ab128f49f84a48f4c05ea163c"}, +] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "nbclient" +version = "0.8.0" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "nbclient-0.8.0-py3-none-any.whl", hash = "sha256:25e861299e5303a0477568557c4045eccc7a34c17fc08e7959558707b9ebe548"}, + {file = "nbclient-0.8.0.tar.gz", hash = "sha256:f9b179cd4b2d7bca965f900a2ebf0db4a12ebff2f36a711cb66861e4ae158e55"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbformat = ">=5.1" +traitlets = ">=5.4" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] +test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.7.4" +description = "Converting Jupyter Notebooks" +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbconvert-7.7.4-py3-none-any.whl", hash = "sha256:ace26f4386d08eb5c55833596a942048c5502a95e05590cb523826a749a40a37"}, + {file = "nbconvert-7.7.4.tar.gz", hash = "sha256:1113d039fa3fc3a846ffa5a3b0a019e85aaa94c566a09fa0c400fb7638e46087"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = "!=5.0.0" +defusedxml = "*" +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<4" +nbclient = ">=0.5.0" +nbformat = ">=5.7" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +tinycss2 = "*" +traitlets = ">=5.1" + +[package.extras] +all = ["nbconvert[docs,qtpdf,serve,test,webpdf]"] +docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] +qtpdf = ["nbconvert[qtpng]"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["flaky", "ipykernel", "ipywidgets (>=7)", "pre-commit", "pytest", "pytest-dependency"] +webpdf = ["playwright"] + +[[package]] +name = "nbformat" +version = "5.9.2" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbformat-5.9.2-py3-none-any.whl", hash = "sha256:1c5172d786a41b82bcfd0c23f9e6b6f072e8fb49c39250219e4acfff1efe89e9"}, + {file = "nbformat-5.9.2.tar.gz", hash = "sha256:5f98b5ba1997dff175e77e0c17d5c10a96eaed2cbd1de3533d1fc35d5e111192"}, +] + +[package.dependencies] +fastjsonschema = "*" +jsonschema = ">=2.6" +jupyter-core = "*" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nest-asyncio" +version = "1.5.7" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.5.7-py3-none-any.whl", hash = "sha256:5301c82941b550b3123a1ea772ba9a1c80bad3a182be8c1a5ae6ad3be57a9657"}, + {file = "nest_asyncio-1.5.7.tar.gz", hash = "sha256:6a80f7b98f24d9083ed24608977c09dd608d83f91cccc24c9d2cba6d10e01c10"}, +] + +[[package]] +name = "notebook" +version = "7.0.2" +description = "Jupyter Notebook - A web-based notebook environment for interactive computing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "notebook-7.0.2-py3-none-any.whl", hash = "sha256:c77b1499dc9b07ce4f4f26990dcb25b2107b434f2536766b51a72a4228d9a4b6"}, + {file = "notebook-7.0.2.tar.gz", hash = "sha256:d70d6a07418c829bd5f54337ce993b7105261d9026f9d3fe68e9b8aa1a20da9a"}, +] + +[package.dependencies] +jupyter-server = ">=2.4.0,<3" +jupyterlab = ">=4.0.2,<5" +jupyterlab-server = ">=2.22.1,<3" +notebook-shim = ">=0.2,<0.3" +tornado = ">=6.2.0" + +[package.extras] +dev = ["hatch", "pre-commit"] +docs = ["myst-parser", "nbsphinx", "pydata-sphinx-theme", "sphinx (>=1.3.6)", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.22.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"] + +[[package]] +name = "notebook-shim" +version = "0.2.3" +description = "A shim layer for notebook traits and config" +optional = false +python-versions = ">=3.7" +files = [ + {file = "notebook_shim-0.2.3-py3-none-any.whl", hash = "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7"}, + {file = "notebook_shim-0.2.3.tar.gz", hash = "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9"}, +] + +[package.dependencies] +jupyter-server = ">=1.8,<3" + +[package.extras] +test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] + +[[package]] +name = "numpy" +version = "1.25.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, + {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, + {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, + {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, + {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, + {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, + {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, + {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, + {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, + {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, +] + +[[package]] +name = "overrides" +version = "7.4.0" +description = "A decorator to automatically detect mismatch when overriding a method." +optional = false +python-versions = ">=3.6" +files = [ + {file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"}, + {file = "overrides-7.4.0.tar.gz", hash = "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pandocfilters" +version = "1.5.0" +description = "Utilities for writing pandoc filters in python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, + {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, +] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pdbpp" +version = "0.10.3" +description = "pdb++, a drop-in replacement for pdb" +optional = false +python-versions = "*" +files = [ + {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"}, + {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, +] + +[package.dependencies] +fancycompleter = ">=0.8" +pygments = "*" +wmctrl = "*" + +[package.extras] +funcsigs = ["funcsigs"] +testing = ["funcsigs", "pytest"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[[package]] +name = "pillow" +version = "10.0.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"}, + {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"}, + {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"}, + {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"}, + {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"}, + {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"}, + {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"}, + {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"}, + {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"}, + {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, + {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, + {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, + {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, + {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, + {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"}, + {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"}, + {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, + {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, + {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, + {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, + {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, + {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"}, + {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"}, + {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"}, + {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"}, + {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"}, + {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"}, + {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"}, + {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"}, + {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"}, + {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"}, + {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"}, + {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"}, + {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"}, + {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prometheus-client" +version = "0.17.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.6" +files = [ + {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, + {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "5.9.5" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "psycopg2-binary" +version = "2.9.7" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "psycopg2-binary-2.9.7.tar.gz", hash = "sha256:1b918f64a51ffe19cd2e230b3240ba481330ce1d4b7875ae67305bd1d37b041c"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ea5f8ee87f1eddc818fc04649d952c526db4426d26bab16efbe5a0c52b27d6ab"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2993ccb2b7e80844d534e55e0f12534c2871952f78e0da33c35e648bf002bbff"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbbc3c5d15ed76b0d9db7753c0db40899136ecfe97d50cbde918f630c5eb857a"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:692df8763b71d42eb8343f54091368f6f6c9cfc56dc391858cdb3c3ef1e3e584"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dcfd5d37e027ec393a303cc0a216be564b96c80ba532f3d1e0d2b5e5e4b1e6e"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17cc17a70dfb295a240db7f65b6d8153c3d81efb145d76da1e4a096e9c5c0e63"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e5666632ba2b0d9757b38fc17337d84bdf932d38563c5234f5f8c54fd01349c9"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7db7b9b701974c96a88997d458b38ccb110eba8f805d4b4f74944aac48639b42"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c82986635a16fb1fa15cd5436035c88bc65c3d5ced1cfaac7f357ee9e9deddd4"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4fe13712357d802080cfccbf8c6266a3121dc0e27e2144819029095ccf708372"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-win32.whl", hash = "sha256:122641b7fab18ef76b18860dd0c772290566b6fb30cc08e923ad73d17461dc63"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-win_amd64.whl", hash = "sha256:f8651cf1f144f9ee0fa7d1a1df61a9184ab72962531ca99f077bbdcba3947c58"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ecc15666f16f97709106d87284c136cdc82647e1c3f8392a672616aed3c7151"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fbb1184c7e9d28d67671992970718c05af5f77fc88e26fd7136613c4ece1f89"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7968fd20bd550431837656872c19575b687f3f6f98120046228e451e4064df"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:094af2e77a1976efd4956a031028774b827029729725e136514aae3cdf49b87b"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26484e913d472ecb6b45937ea55ce29c57c662066d222fb0fbdc1fab457f18c5"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f309b77a7c716e6ed9891b9b42953c3ff7d533dc548c1e33fddc73d2f5e21f9"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d92e139ca388ccfe8c04aacc163756e55ba4c623c6ba13d5d1595ed97523e4b"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2df562bb2e4e00ee064779902d721223cfa9f8f58e7e52318c97d139cf7f012d"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4eec5d36dbcfc076caab61a2114c12094c0b7027d57e9e4387b634e8ab36fd44"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1011eeb0c51e5b9ea1016f0f45fa23aca63966a4c0afcf0340ccabe85a9f65bd"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-win32.whl", hash = "sha256:ded8e15f7550db9e75c60b3d9fcbc7737fea258a0f10032cdb7edc26c2a671fd"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-win_amd64.whl", hash = "sha256:8a136c8aaf6615653450817a7abe0fc01e4ea720ae41dfb2823eccae4b9062a3"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dec5a75a3a5d42b120e88e6ed3e3b37b46459202bb8e36cd67591b6e5feebc1"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc10da7e7df3380426521e8c1ed975d22df678639da2ed0ec3244c3dc2ab54c8"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee919b676da28f78f91b464fb3e12238bd7474483352a59c8a16c39dfc59f0c5"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb1c0e682138f9067a58fc3c9a9bf1c83d8e08cfbee380d858e63196466d5c86"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00d8db270afb76f48a499f7bb8fa70297e66da67288471ca873db88382850bf4"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b0c2b466b2f4d89ccc33784c4ebb1627989bd84a39b79092e560e937a11d4ac"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:51d1b42d44f4ffb93188f9b39e6d1c82aa758fdb8d9de65e1ddfe7a7d250d7ad"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:11abdbfc6f7f7dea4a524b5f4117369b0d757725798f1593796be6ece20266cb"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f02f4a72cc3ab2565c6d9720f0343cb840fb2dc01a2e9ecb8bc58ccf95dc5c06"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-win32.whl", hash = "sha256:81d5dd2dd9ab78d31a451e357315f201d976c131ca7d43870a0e8063b6b7a1ec"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-win_amd64.whl", hash = "sha256:62cb6de84d7767164a87ca97e22e5e0a134856ebcb08f21b621c6125baf61f16"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59f7e9109a59dfa31efa022e94a244736ae401526682de504e87bd11ce870c22"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:95a7a747bdc3b010bb6a980f053233e7610276d55f3ca506afff4ad7749ab58a"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c721ee464e45ecf609ff8c0a555018764974114f671815a0a7152aedb9f3343"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4f37bbc6588d402980ffbd1f3338c871368fb4b1cfa091debe13c68bb3852b3"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac83ab05e25354dad798401babaa6daa9577462136ba215694865394840e31f8"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:024eaeb2a08c9a65cd5f94b31ace1ee3bb3f978cd4d079406aef85169ba01f08"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1c31c2606ac500dbd26381145684d87730a2fac9a62ebcfbaa2b119f8d6c19f4"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:42a62ef0e5abb55bf6ffb050eb2b0fcd767261fa3faf943a4267539168807522"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7952807f95c8eba6a8ccb14e00bf170bb700cafcec3924d565235dffc7dc4ae8"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e02bc4f2966475a7393bd0f098e1165d470d3fa816264054359ed4f10f6914ea"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-win32.whl", hash = "sha256:fdca0511458d26cf39b827a663d7d87db6f32b93efc22442a742035728603d5f"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-win_amd64.whl", hash = "sha256:d0b16e5bb0ab78583f0ed7ab16378a0f8a89a27256bb5560402749dbe8a164d7"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6822c9c63308d650db201ba22fe6648bd6786ca6d14fdaf273b17e15608d0852"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f94cb12150d57ea433e3e02aabd072205648e86f1d5a0a692d60242f7809b15"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5ee89587696d808c9a00876065d725d4ae606f5f7853b961cdbc348b0f7c9a1"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad5ec10b53cbb57e9a2e77b67e4e4368df56b54d6b00cc86398578f1c635f329"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:642df77484b2dcaf87d4237792246d8068653f9e0f5c025e2c692fc56b0dda70"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a8b575ac45af1eaccbbcdcf710ab984fd50af048fe130672377f78aaff6fc1"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f955aa50d7d5220fcb6e38f69ea126eafecd812d96aeed5d5f3597f33fad43bb"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad26d4eeaa0d722b25814cce97335ecf1b707630258f14ac4d2ed3d1d8415265"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ced63c054bdaf0298f62681d5dcae3afe60cbae332390bfb1acf0e23dcd25fc8"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b04da24cbde33292ad34a40db9832a80ad12de26486ffeda883413c9e1b1d5e"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-win32.whl", hash = "sha256:18f12632ab516c47c1ac4841a78fddea6508a8284c7cf0f292cb1a523f2e2379"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb3b8d55924a6058a26db69fb1d3e7e32695ff8b491835ba9f479537e14dcf9f"}, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycountry" +version = "22.3.5" +description = "ISO country, subdivision, language, currency and script definitions and their translations" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "pycountry-22.3.5.tar.gz", hash = "sha256:b2163a246c585894d808f18783e19137cb70a0c18fb36748dc01fc6f109c1646"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "1.10.12" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyreadline" +version = "2.1" +description = "A python implmementation of GNU readline." +optional = false +python-versions = "*" +files = [ + {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, +] + +[[package]] +name = "pyrepl" +version = "0.9.0" +description = "A library for building flexible command line interfaces" +optional = false +python-versions = "*" +files = [ + {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, +] + +[[package]] +name = "pytest" +version = "7.4.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-json-logger" +version = "2.0.7" +description = "A python library adding a json log formatter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, + {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pywinpty" +version = "2.0.11" +description = "Pseudo terminal support for Windows from Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pywinpty-2.0.11-cp310-none-win_amd64.whl", hash = "sha256:452f10ac9ff8ab9151aa8cea9e491a9612a12250b1899278c6a56bc184afb47f"}, + {file = "pywinpty-2.0.11-cp311-none-win_amd64.whl", hash = "sha256:6701867d42aec1239bc0fedf49a336570eb60eb886e81763db77ea2b6c533cc3"}, + {file = "pywinpty-2.0.11-cp38-none-win_amd64.whl", hash = "sha256:0ffd287751ad871141dc9724de70ea21f7fc2ff1af50861e0d232cf70739d8c4"}, + {file = "pywinpty-2.0.11-cp39-none-win_amd64.whl", hash = "sha256:e4e7f023c28ca7aa8e1313e53ba80a4d10171fe27857b7e02f99882dfe3e8638"}, + {file = "pywinpty-2.0.11.tar.gz", hash = "sha256:e244cffe29a894876e2cd251306efd0d8d64abd5ada0a46150a4a71c0b9ad5c5"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "pyzmq" +version = "25.1.1" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, + {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, + {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, + {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, + {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, + {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, + {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, + {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, + {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, + {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, + {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, + {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "qtconsole" +version = "5.4.3" +description = "Jupyter Qt console" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "qtconsole-5.4.3-py3-none-any.whl", hash = "sha256:35fd6e87b1f6d1fd41801b07e69339f8982e76afd4fa8ef35595bc6036717189"}, + {file = "qtconsole-5.4.3.tar.gz", hash = "sha256:5e4082a86a201796b2a5cfd4298352d22b158b51b57736531824715fc2a979dd"}, +] + +[package.dependencies] +ipykernel = ">=4.1" +ipython-genutils = "*" +jupyter-client = ">=4.1" +jupyter-core = "*" +packaging = "*" +pygments = "*" +pyzmq = ">=17.1" +qtpy = ">=2.0.1" +traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2" + +[package.extras] +doc = ["Sphinx (>=1.3)"] +test = ["flaky", "pytest", "pytest-qt"] + +[[package]] +name = "qtpy" +version = "2.3.1" +description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." +optional = false +python-versions = ">=3.7" +files = [ + {file = "QtPy-2.3.1-py3-none-any.whl", hash = "sha256:5193d20e0b16e4d9d3bc2c642d04d9f4e2c892590bd1b9c92bfe38a95d5a2e12"}, + {file = "QtPy-2.3.1.tar.gz", hash = "sha256:a8c74982d6d172ce124d80cafd39653df78989683f760f2281ba91a6e7b9de8b"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] + +[[package]] +name = "referencing" +version = "0.30.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, + {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +description = "A pure python RFC3339 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, + {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +description = "Pure python rfc3986 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, + {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, +] + +[[package]] +name = "rpds-py" +version = "0.9.2" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.9.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:ab6919a09c055c9b092798ce18c6c4adf49d24d4d9e43a92b257e3f2548231e7"}, + {file = "rpds_py-0.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d55777a80f78dd09410bd84ff8c95ee05519f41113b2df90a69622f5540c4f8b"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a216b26e5af0a8e265d4efd65d3bcec5fba6b26909014effe20cd302fd1138fa"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29cd8bfb2d716366a035913ced99188a79b623a3512292963d84d3e06e63b496"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44659b1f326214950a8204a248ca6199535e73a694be8d3e0e869f820767f12f"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:745f5a43fdd7d6d25a53ab1a99979e7f8ea419dfefebcab0a5a1e9095490ee5e"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a987578ac5214f18b99d1f2a3851cba5b09f4a689818a106c23dbad0dfeb760f"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf4151acb541b6e895354f6ff9ac06995ad9e4175cbc6d30aaed08856558201f"}, + {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:03421628f0dc10a4119d714a17f646e2837126a25ac7a256bdf7c3943400f67f"}, + {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13b602dc3e8dff3063734f02dcf05111e887f301fdda74151a93dbbc249930fe"}, + {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fae5cb554b604b3f9e2c608241b5d8d303e410d7dfb6d397c335f983495ce7f6"}, + {file = "rpds_py-0.9.2-cp310-none-win32.whl", hash = "sha256:47c5f58a8e0c2c920cc7783113df2fc4ff12bf3a411d985012f145e9242a2764"}, + {file = "rpds_py-0.9.2-cp310-none-win_amd64.whl", hash = "sha256:4ea6b73c22d8182dff91155af018b11aac9ff7eca085750455c5990cb1cfae6e"}, + {file = "rpds_py-0.9.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e564d2238512c5ef5e9d79338ab77f1cbbda6c2d541ad41b2af445fb200385e3"}, + {file = "rpds_py-0.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f411330a6376fb50e5b7a3e66894e4a39e60ca2e17dce258d53768fea06a37bd"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e7521f5af0233e89939ad626b15278c71b69dc1dfccaa7b97bd4cdf96536bb7"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8d3335c03100a073883857e91db9f2e0ef8a1cf42dc0369cbb9151c149dbbc1b"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d25b1c1096ef0447355f7293fbe9ad740f7c47ae032c2884113f8e87660d8f6e"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a5d3fbd02efd9cf6a8ffc2f17b53a33542f6b154e88dd7b42ef4a4c0700fdad"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5934e2833afeaf36bd1eadb57256239785f5af0220ed8d21c2896ec4d3a765f"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:095b460e117685867d45548fbd8598a8d9999227e9061ee7f012d9d264e6048d"}, + {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:91378d9f4151adc223d584489591dbb79f78814c0734a7c3bfa9c9e09978121c"}, + {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:24a81c177379300220e907e9b864107614b144f6c2a15ed5c3450e19cf536fae"}, + {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:de0b6eceb46141984671802d412568d22c6bacc9b230174f9e55fc72ef4f57de"}, + {file = "rpds_py-0.9.2-cp311-none-win32.whl", hash = "sha256:700375326ed641f3d9d32060a91513ad668bcb7e2cffb18415c399acb25de2ab"}, + {file = "rpds_py-0.9.2-cp311-none-win_amd64.whl", hash = "sha256:0766babfcf941db8607bdaf82569ec38107dbb03c7f0b72604a0b346b6eb3298"}, + {file = "rpds_py-0.9.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1440c291db3f98a914e1afd9d6541e8fc60b4c3aab1a9008d03da4651e67386"}, + {file = "rpds_py-0.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0f2996fbac8e0b77fd67102becb9229986396e051f33dbceada3debaacc7033f"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f30d205755566a25f2ae0382944fcae2f350500ae4df4e795efa9e850821d82"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:159fba751a1e6b1c69244e23ba6c28f879a8758a3e992ed056d86d74a194a0f3"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1f044792e1adcea82468a72310c66a7f08728d72a244730d14880cd1dabe36b"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9251eb8aa82e6cf88510530b29eef4fac825a2b709baf5b94a6094894f252387"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01899794b654e616c8625b194ddd1e5b51ef5b60ed61baa7a2d9c2ad7b2a4238"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0c43f8ae8f6be1d605b0465671124aa8d6a0e40f1fb81dcea28b7e3d87ca1e1"}, + {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:207f57c402d1f8712618f737356e4b6f35253b6d20a324d9a47cb9f38ee43a6b"}, + {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b52e7c5ae35b00566d244ffefba0f46bb6bec749a50412acf42b1c3f402e2c90"}, + {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:978fa96dbb005d599ec4fd9ed301b1cc45f1a8f7982d4793faf20b404b56677d"}, + {file = "rpds_py-0.9.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6aa8326a4a608e1c28da191edd7c924dff445251b94653988efb059b16577a4d"}, + {file = "rpds_py-0.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aad51239bee6bff6823bbbdc8ad85136c6125542bbc609e035ab98ca1e32a192"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd4dc3602370679c2dfb818d9c97b1137d4dd412230cfecd3c66a1bf388a196"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd9da77c6ec1f258387957b754f0df60766ac23ed698b61941ba9acccd3284d1"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:190ca6f55042ea4649ed19c9093a9be9d63cd8a97880106747d7147f88a49d18"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:876bf9ed62323bc7dcfc261dbc5572c996ef26fe6406b0ff985cbcf460fc8a4c"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa2818759aba55df50592ecbc95ebcdc99917fa7b55cc6796235b04193eb3c55"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9ea4d00850ef1e917815e59b078ecb338f6a8efda23369677c54a5825dbebb55"}, + {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5855c85eb8b8a968a74dc7fb014c9166a05e7e7a8377fb91d78512900aadd13d"}, + {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:14c408e9d1a80dcb45c05a5149e5961aadb912fff42ca1dd9b68c0044904eb32"}, + {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:65a0583c43d9f22cb2130c7b110e695fff834fd5e832a776a107197e59a1898e"}, + {file = "rpds_py-0.9.2-cp38-none-win32.whl", hash = "sha256:71f2f7715935a61fa3e4ae91d91b67e571aeb5cb5d10331ab681256bda2ad920"}, + {file = "rpds_py-0.9.2-cp38-none-win_amd64.whl", hash = "sha256:674c704605092e3ebbbd13687b09c9f78c362a4bc710343efe37a91457123044"}, + {file = "rpds_py-0.9.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:07e2c54bef6838fa44c48dfbc8234e8e2466d851124b551fc4e07a1cfeb37260"}, + {file = "rpds_py-0.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fdf55283ad38c33e35e2855565361f4bf0abd02470b8ab28d499c663bc5d7c"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:890ba852c16ace6ed9f90e8670f2c1c178d96510a21b06d2fa12d8783a905193"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50025635ba8b629a86d9d5474e650da304cb46bbb4d18690532dd79341467846"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517cbf6e67ae3623c5127206489d69eb2bdb27239a3c3cc559350ef52a3bbf0b"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0836d71ca19071090d524739420a61580f3f894618d10b666cf3d9a1688355b1"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c439fd54b2b9053717cca3de9583be6584b384d88d045f97d409f0ca867d80f"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f68996a3b3dc9335037f82754f9cdbe3a95db42bde571d8c3be26cc6245f2324"}, + {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7d68dc8acded354c972116f59b5eb2e5864432948e098c19fe6994926d8e15c3"}, + {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f963c6b1218b96db85fc37a9f0851eaf8b9040aa46dec112611697a7023da535"}, + {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a46859d7f947061b4010e554ccd1791467d1b1759f2dc2ec9055fa239f1bc26"}, + {file = "rpds_py-0.9.2-cp39-none-win32.whl", hash = "sha256:e07e5dbf8a83c66783a9fe2d4566968ea8c161199680e8ad38d53e075df5f0d0"}, + {file = "rpds_py-0.9.2-cp39-none-win_amd64.whl", hash = "sha256:682726178138ea45a0766907957b60f3a1bf3acdf212436be9733f28b6c5af3c"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:196cb208825a8b9c8fc360dc0f87993b8b260038615230242bf18ec84447c08d"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c7671d45530fcb6d5e22fd40c97e1e1e01965fc298cbda523bb640f3d923b387"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83b32f0940adec65099f3b1c215ef7f1d025d13ff947975a055989cb7fd019a4"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f67da97f5b9eac838b6980fc6da268622e91f8960e083a34533ca710bec8611"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03975db5f103997904c37e804e5f340c8fdabbb5883f26ee50a255d664eed58c"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:987b06d1cdb28f88a42e4fb8a87f094e43f3c435ed8e486533aea0bf2e53d931"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c861a7e4aef15ff91233751619ce3a3d2b9e5877e0fcd76f9ea4f6847183aa16"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02938432352359805b6da099c9c95c8a0547fe4b274ce8f1a91677401bb9a45f"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ef1f08f2a924837e112cba2953e15aacfccbbfcd773b4b9b4723f8f2ddded08e"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:35da5cc5cb37c04c4ee03128ad59b8c3941a1e5cd398d78c37f716f32a9b7f67"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:141acb9d4ccc04e704e5992d35472f78c35af047fa0cfae2923835d153f091be"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79f594919d2c1a0cc17d1988a6adaf9a2f000d2e1048f71f298b056b1018e872"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:a06418fe1155e72e16dddc68bb3780ae44cebb2912fbd8bb6ff9161de56e1798"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2eb034c94b0b96d5eddb290b7b5198460e2d5d0c421751713953a9c4e47d10"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b08605d248b974eb02f40bdcd1a35d3924c83a2a5e8f5d0fa5af852c4d960af"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0805911caedfe2736935250be5008b261f10a729a303f676d3d5fea6900c96a"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab2299e3f92aa5417d5e16bb45bb4586171c1327568f638e8453c9f8d9e0f020"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c8d7594e38cf98d8a7df25b440f684b510cf4627fe038c297a87496d10a174f"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b9ec12ad5f0a4625db34db7e0005be2632c1013b253a4a60e8302ad4d462afd"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1fcdee18fea97238ed17ab6478c66b2095e4ae7177e35fb71fbe561a27adf620"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:933a7d5cd4b84f959aedeb84f2030f0a01d63ae6cf256629af3081cf3e3426e8"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:686ba516e02db6d6f8c279d1641f7067ebb5dc58b1d0536c4aaebb7bf01cdc5d"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0173c0444bec0a3d7d848eaeca2d8bd32a1b43f3d3fde6617aac3731fa4be05f"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d576c3ef8c7b2d560e301eb33891d1944d965a4d7a2eacb6332eee8a71827db6"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed89861ee8c8c47d6beb742a602f912b1bb64f598b1e2f3d758948721d44d468"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1054a08e818f8e18910f1bee731583fe8f899b0a0a5044c6e680ceea34f93876"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99e7c4bb27ff1aab90dcc3e9d37ee5af0231ed98d99cb6f5250de28889a3d502"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c545d9d14d47be716495076b659db179206e3fd997769bc01e2d550eeb685596"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9039a11bca3c41be5a58282ed81ae422fa680409022b996032a43badef2a3752"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fb39aca7a64ad0c9490adfa719dbeeb87d13be137ca189d2564e596f8ba32c07"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2d8b3b3a2ce0eaa00c5bbbb60b6713e94e7e0becab7b3db6c5c77f979e8ed1f1"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:99b1c16f732b3a9971406fbfe18468592c5a3529585a45a35adbc1389a529a03"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c27ee01a6c3223025f4badd533bea5e87c988cb0ba2811b690395dfe16088cfe"}, + {file = "rpds_py-0.9.2.tar.gz", hash = "sha256:8d70e8f14900f2657c249ea4def963bed86a29b81f81f5b76b5a9215680de945"}, +] + +[[package]] +name = "send2trash" +version = "1.8.2" +description = "Send file to trash natively under Mac OS X, Windows and Linux" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "Send2Trash-1.8.2-py3-none-any.whl", hash = "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679"}, + {file = "Send2Trash-1.8.2.tar.gz", hash = "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312"}, +] + +[package.extras] +nativelib = ["pyobjc-framework-Cocoa", "pywin32"] +objc = ["pyobjc-framework-Cocoa"] +win32 = ["pywin32"] + +[[package]] +name = "setuptools" +version = "68.1.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, + {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[[package]] +name = "sqlalchemy" +version = "1.4.41" +description = "Database Abstraction Library" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "SQLAlchemy-1.4.41-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:13e397a9371ecd25573a7b90bd037db604331cf403f5318038c46ee44908c44d"}, + {file = "SQLAlchemy-1.4.41-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2d6495f84c4fd11584f34e62f9feec81bf373787b3942270487074e35cbe5330"}, + {file = "SQLAlchemy-1.4.41-cp27-cp27m-win32.whl", hash = "sha256:e570cfc40a29d6ad46c9aeaddbdcee687880940a3a327f2c668dd0e4ef0a441d"}, + {file = "SQLAlchemy-1.4.41-cp27-cp27m-win_amd64.whl", hash = "sha256:5facb7fd6fa8a7353bbe88b95695e555338fb038ad19ceb29c82d94f62775a05"}, + {file = "SQLAlchemy-1.4.41-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f37fa70d95658763254941ddd30ecb23fc4ec0c5a788a7c21034fc2305dab7cc"}, + {file = "SQLAlchemy-1.4.41-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:361f6b5e3f659e3c56ea3518cf85fbdae1b9e788ade0219a67eeaaea8a4e4d2a"}, + {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0990932f7cca97fece8017414f57fdd80db506a045869d7ddf2dda1d7cf69ecc"}, + {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd767cf5d7252b1c88fcfb58426a32d7bd14a7e4942497e15b68ff5d822b41ad"}, + {file = "SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5102fb9ee2c258a2218281adcb3e1918b793c51d6c2b4666ce38c35101bb940e"}, + {file = "SQLAlchemy-1.4.41-cp310-cp310-win32.whl", hash = "sha256:2082a2d2fca363a3ce21cfa3d068c5a1ce4bf720cf6497fb3a9fc643a8ee4ddd"}, + {file = "SQLAlchemy-1.4.41-cp310-cp310-win_amd64.whl", hash = "sha256:e4b12e3d88a8fffd0b4ca559f6d4957ed91bd4c0613a4e13846ab8729dc5c251"}, + {file = "SQLAlchemy-1.4.41-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:90484a2b00baedad361402c257895b13faa3f01780f18f4a104a2f5c413e4536"}, + {file = "SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67fc780cfe2b306180e56daaa411dd3186bf979d50a6a7c2a5b5036575cbdbb"}, + {file = "SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad2b727fc41c7f8757098903f85fafb4bf587ca6605f82d9bf5604bd9c7cded"}, + {file = "SQLAlchemy-1.4.41-cp311-cp311-win32.whl", hash = "sha256:59bdc291165b6119fc6cdbc287c36f7f2859e6051dd923bdf47b4c55fd2f8bd0"}, + {file = "SQLAlchemy-1.4.41-cp311-cp311-win_amd64.whl", hash = "sha256:d2e054aed4645f9b755db85bc69fc4ed2c9020c19c8027976f66576b906a74f1"}, + {file = "SQLAlchemy-1.4.41-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:4ba7e122510bbc07258dc42be6ed45997efdf38129bde3e3f12649be70683546"}, + {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0dcf127bb99458a9d211e6e1f0f3edb96c874dd12f2503d4d8e4f1fd103790b"}, + {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e16c2be5cb19e2c08da7bd3a87fed2a0d4e90065ee553a940c4fc1a0fb1ab72b"}, + {file = "SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebeeec5c14533221eb30bad716bc1fd32f509196318fb9caa7002c4a364e4c"}, + {file = "SQLAlchemy-1.4.41-cp36-cp36m-win32.whl", hash = "sha256:3e2ef592ac3693c65210f8b53d0edcf9f4405925adcfc031ff495e8d18169682"}, + {file = "SQLAlchemy-1.4.41-cp36-cp36m-win_amd64.whl", hash = "sha256:eb30cf008850c0a26b72bd1b9be6730830165ce049d239cfdccd906f2685f892"}, + {file = "SQLAlchemy-1.4.41-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:c23d64a0b28fc78c96289ffbd0d9d1abd48d267269b27f2d34e430ea73ce4b26"}, + {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb8897367a21b578b26f5713833836f886817ee2ffba1177d446fa3f77e67c8"}, + {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14576238a5f89bcf504c5f0a388d0ca78df61fb42cb2af0efe239dc965d4f5c9"}, + {file = "SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639e1ae8d48b3c86ffe59c0daa9a02e2bfe17ca3d2b41611b30a0073937d4497"}, + {file = "SQLAlchemy-1.4.41-cp37-cp37m-win32.whl", hash = "sha256:0005bd73026cd239fc1e8ccdf54db58b6193be9a02b3f0c5983808f84862c767"}, + {file = "SQLAlchemy-1.4.41-cp37-cp37m-win_amd64.whl", hash = "sha256:5323252be2bd261e0aa3f33cb3a64c45d76829989fa3ce90652838397d84197d"}, + {file = "SQLAlchemy-1.4.41-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:05f0de3a1dc3810a776275763764bb0015a02ae0f698a794646ebc5fb06fad33"}, + {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0002e829142b2af00b4eaa26c51728f3ea68235f232a2e72a9508a3116bd6ed0"}, + {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ff16cedab5b16a0db79f1bc99e46a6ddececb60c396562e50aab58ddb2871c"}, + {file = "SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfd238f766a5bb5ee5545a62dd03f316ac67966a6a658efb63eeff8158a4bbf"}, + {file = "SQLAlchemy-1.4.41-cp38-cp38-win32.whl", hash = "sha256:58bb65b3274b0c8a02cea9f91d6f44d0da79abc993b33bdedbfec98c8440175a"}, + {file = "SQLAlchemy-1.4.41-cp38-cp38-win_amd64.whl", hash = "sha256:ce8feaa52c1640de9541eeaaa8b5fb632d9d66249c947bb0d89dd01f87c7c288"}, + {file = "SQLAlchemy-1.4.41-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:199a73c31ac8ea59937cc0bf3dfc04392e81afe2ec8a74f26f489d268867846c"}, + {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676d51c9f6f6226ae8f26dc83ec291c088fe7633269757d333978df78d931ab"}, + {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:036d8472356e1d5f096c5e0e1a7e0f9182140ada3602f8fff6b7329e9e7cfbcd"}, + {file = "SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2307495d9e0ea00d0c726be97a5b96615035854972cc538f6e7eaed23a35886c"}, + {file = "SQLAlchemy-1.4.41-cp39-cp39-win32.whl", hash = "sha256:9c56e19780cd1344fcd362fd6265a15f48aa8d365996a37fab1495cae8fcd97d"}, + {file = "SQLAlchemy-1.4.41-cp39-cp39-win_amd64.whl", hash = "sha256:f5fa526d027d804b1f85cdda1eb091f70bde6fb7d87892f6dd5a48925bc88898"}, + {file = "SQLAlchemy-1.4.41.tar.gz", hash = "sha256:0292f70d1797e3c54e862e6f30ae474014648bc9c723e14a2fda730adb0a9791"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "sqlalchemy-utils" +version = "0.41.1" +description = "Various utility functions for SQLAlchemy." +optional = false +python-versions = ">=3.6" +files = [ + {file = "SQLAlchemy-Utils-0.41.1.tar.gz", hash = "sha256:a2181bff01eeb84479e38571d2c0718eb52042f9afd8c194d0d02877e84b7d74"}, + {file = "SQLAlchemy_Utils-0.41.1-py3-none-any.whl", hash = "sha256:6c96b0768ea3f15c0dc56b363d386138c562752b84f647fb8d31a2223aaab801"}, +] + +[package.dependencies] +SQLAlchemy = ">=1.3" + +[package.extras] +arrow = ["arrow (>=0.3.4)"] +babel = ["Babel (>=1.3)"] +color = ["colour (>=0.0.4)"] +encrypted = ["cryptography (>=0.6)"] +intervals = ["intervals (>=0.7.1)"] +password = ["passlib (>=1.6,<2.0)"] +pendulum = ["pendulum (>=2.0.5)"] +phone = ["phonenumbers (>=5.9.2)"] +test = ["Jinja2 (>=2.3)", "Pygments (>=1.2)", "backports.zoneinfo", "docutils (>=0.10)", "flake8 (>=2.4.0)", "flexmock (>=0.9.7)", "isort (>=4.2.2)", "pg8000 (>=1.12.4)", "psycopg (>=3.1.8)", "psycopg2 (>=2.5.1)", "psycopg2cffi (>=2.8.1)", "pymysql", "pyodbc", "pytest (>=2.7.1)", "python-dateutil (>=2.6)", "pytz (>=2014.2)"] +test-all = ["Babel (>=1.3)", "Jinja2 (>=2.3)", "Pygments (>=1.2)", "arrow (>=0.3.4)", "backports.zoneinfo", "colour (>=0.0.4)", "cryptography (>=0.6)", "docutils (>=0.10)", "flake8 (>=2.4.0)", "flexmock (>=0.9.7)", "furl (>=0.4.1)", "intervals (>=0.7.1)", "isort (>=4.2.2)", "passlib (>=1.6,<2.0)", "pendulum (>=2.0.5)", "pg8000 (>=1.12.4)", "phonenumbers (>=5.9.2)", "psycopg (>=3.1.8)", "psycopg2 (>=2.5.1)", "psycopg2cffi (>=2.8.1)", "pymysql", "pyodbc", "pytest (>=2.7.1)", "python-dateutil", "python-dateutil (>=2.6)", "pytz (>=2014.2)"] +timezone = ["python-dateutil"] +url = ["furl (>=0.4.1)"] + +[[package]] +name = "sqlalchemy2-stubs" +version = "0.0.2a35" +description = "Typing Stubs for SQLAlchemy 1.4" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sqlalchemy2-stubs-0.0.2a35.tar.gz", hash = "sha256:bd5d530697d7e8c8504c7fe792ef334538392a5fb7aa7e4f670bfacdd668a19d"}, + {file = "sqlalchemy2_stubs-0.0.2a35-py3-none-any.whl", hash = "sha256:593784ff9fc0dc2ded1895e3322591689db3be06f3ca006e3ef47640baf2d38a"}, +] + +[package.dependencies] +typing-extensions = ">=3.7.4" + +[[package]] +name = "sqlmodel" +version = "0.0.8" +description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +optional = false +python-versions = ">=3.6.1,<4.0.0" +files = [ + {file = "sqlmodel-0.0.8-py3-none-any.whl", hash = "sha256:0fd805719e0c5d4f22be32eb3ffc856eca3f7f20e8c7aa3e117ad91684b518ee"}, + {file = "sqlmodel-0.0.8.tar.gz", hash = "sha256:3371b4d1ad59d2ffd0c530582c2140b6c06b090b32af9b9c6412986d7b117036"}, +] + +[package.dependencies] +pydantic = ">=1.8.2,<2.0.0" +SQLAlchemy = ">=1.4.17,<=1.4.41" +sqlalchemy2-stubs = "*" + +[[package]] +name = "stack-data" +version = "0.6.2" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, + {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "starlette" +version = "0.20.4" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.20.4-py3-none-any.whl", hash = "sha256:c0414d5a56297d37f3db96a84034d61ce29889b9eaccf65eb98a0b39441fcaa3"}, + {file = "starlette-0.20.4.tar.gz", hash = "sha256:42fcf3122f998fefce3e2c5ad7e5edbf0f02cf685d646a83a08d404726af5084"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + +[[package]] +name = "terminado" +version = "0.17.1" +description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "terminado-0.17.1-py3-none-any.whl", hash = "sha256:8650d44334eba354dd591129ca3124a6ba42c3d5b70df5051b6921d506fdaeae"}, + {file = "terminado-0.17.1.tar.gz", hash = "sha256:6ccbbcd3a4f8a25a5ec04991f39a0b8db52dfcd487ea0e578d977e6752380333"}, +] + +[package.dependencies] +ptyprocess = {version = "*", markers = "os_name != \"nt\""} +pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} +tornado = ">=6.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] + +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tornado" +version = "6.3.3" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, + {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, + {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, + {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, + {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, + {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, + {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, +] + +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +description = "RFC 6570 URI Template Processor" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, + {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, +] + +[package.extras] +dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-modern-annotations", "flake8-noqa", "flake8-pyproject", "flake8-requirements", "flake8-typechecking-import", "flake8-use-fstring", "mypy", "pep8-naming", "types-PyYAML"] + +[[package]] +name = "urllib3" +version = "2.0.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.21.1" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.21.1-py3-none-any.whl", hash = "sha256:e47cac98a6da10cd41e6fd036d472c6f58ede6c5dbee3dbee3ef7a100ed97742"}, + {file = "uvicorn-0.21.1.tar.gz", hash = "sha256:0fac9cb342ba099e0d582966005f3fdba5b0290579fed4a6266dc702ca7bb032"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "webcolors" +version = "1.13" +description = "A library for working with the color formats defined by HTML and CSS." +optional = false +python-versions = ">=3.7" +files = [ + {file = "webcolors-1.13-py3-none-any.whl", hash = "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf"}, + {file = "webcolors-1.13.tar.gz", hash = "sha256:c225b674c83fa923be93d235330ce0300373d02885cef23238813b0d5668304a"}, +] + +[package.extras] +docs = ["furo", "sphinx", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinxext-opengraph"] +tests = ["pytest", "pytest-cov"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websocket-client" +version = "1.6.2" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websocket-client-1.6.2.tar.gz", hash = "sha256:53e95c826bf800c4c465f50093a8c4ff091c7327023b10bfaff40cf1ef170eaa"}, + {file = "websocket_client-1.6.2-py3-none-any.whl", hash = "sha256:ce54f419dfae71f4bdba69ebe65bf7f0a93fe71bc009ad3a010aacc3eebad537"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "widgetsnbextension" +version = "4.0.8" +description = "Jupyter interactive widgets for Jupyter Notebook" +optional = false +python-versions = ">=3.7" +files = [ + {file = "widgetsnbextension-4.0.8-py3-none-any.whl", hash = "sha256:2e37f0ce9da11651056280c7efe96f2db052fe8fc269508e3724f5cbd6c93018"}, + {file = "widgetsnbextension-4.0.8.tar.gz", hash = "sha256:9ec291ba87c2dfad42c3d5b6f68713fa18be1acd7476569516b2431682315c17"}, +] + +[[package]] +name = "wmctrl" +version = "0.4" +description = "A tool to programmatically control windows inside X" +optional = false +python-versions = "*" +files = [ + {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, +] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "fd833c28ea185eba4166a54e550e1ca6383102a8188b5023a5e49ae54f21c188" diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..42514e0 --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,41 @@ +[tool.poetry] +name = "instruct-multilingual-backend" +version = "0.1.0" +description = "" +authors = [ + "Herumb Shandilya ", + "Oshan Ivantha ", + "Freddie Vargus ", +] +readme = "README.md" +packages = [{include = "instruct_multilingual"}] + +[tool.poetry.dependencies] +python = "^3.10" +fastapi = "0.85.0" +alembic = "^1.10.2" +SQLAlchemy = "1.4.41" +psycopg2-binary = "^2.9.5" +pydantic = "^1.10.7" +uvicorn = "^0.21.1" +FastAPI-SQLAlchemy = "^0.2.1" +python-dotenv = "^1.0.0" +sqlmodel = "^0.0.8" +python-dateutil = "^2.8.2" +fastapi-discord = "^0.2.4" +pycountry = "^22.3.5" +requests = "^2.28.2" +httpx = "^0.24.0" +pyjwt = "^2.7.0" + +[tool.poetry.group.dev.dependencies] +ipython = "^8.11.0" +pdbpp = "^0.10.3" +pytest = "^7.2.2" +jupyter = "^1.0.0" +matplotlib = "^3.7.1" +sqlalchemy-utils = "^0.41.1" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/backend/run.sh b/backend/run.sh new file mode 100755 index 0000000..4371a17 --- /dev/null +++ b/backend/run.sh @@ -0,0 +1 @@ +ENVIRONMENT=local docker compose up backend database --build diff --git a/backend/scripts/seed_db.py b/backend/scripts/seed_db.py new file mode 100755 index 0000000..d9a586d --- /dev/null +++ b/backend/scripts/seed_db.py @@ -0,0 +1,275 @@ +""" +Script that connects to the local database and then seeds it with fake data. + +Creates a user with an two assigned language codes, and a country code. + +Also adds a fake dataset along with some tasks that are mapped to the dataset. +""" + +import logging +import os +from typing import List + +from dotenv import load_dotenv +from sqlalchemy import select +from sqlalchemy.dialects.postgresql import insert +from sqlmodel import Session, create_engine + +from instruct_multilingual.models import TaskContribution, TaskAudit +from instruct_multilingual.models.country_code import CountryCode +from instruct_multilingual.models.dataset import Dataset +from instruct_multilingual.models.language_code import LanguageCode +from instruct_multilingual.models.task import Task +from instruct_multilingual.models.user import User + +load_dotenv('.env.local') + +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s: %(message)s", +) + +engine = create_engine( + os.environ['INSTRUCT_MULTILINGUAL_APP_DB_URI'], + client_encoding="utf8", + # Applications using this level must be prepared to retry transactions due to serialization failures. + isolation_level="REPEATABLE READ", +) + + +def get_language_code() -> LanguageCode: + """ + Gets a language code to assign to the user. + """ + with Session(engine) as session: + statement = ( + select(LanguageCode) + .where(LanguageCode.code == "spa") + ) + + language_code = session.execute(statement).one()[0] + + return language_code + + +def get_country_code() -> CountryCode: + """ + Gets a country code to assign to the user. + """ + with Session(engine) as session: + statement = ( + select(CountryCode) + .where(CountryCode.code == "ES") + ) + + country_code = session.execute(statement).one()[0] + + return country_code + + +def create_user(language_code: LanguageCode, country_code: CountryCode) -> User: + """ + Creates a new user. + """ + with Session(engine) as session: + statement = ( + insert(User) + .values( + username="test_user", + image_url="https://assets-global.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png", + language_codes=[language_code.id], + country_code=country_code.id, + email="testuser@gmail.com", + discord_id="1234567890", + google_id="1234567890", + ) + .returning(User) + ) + + user = session.execute(statement).one() + session.commit() + + return user + + +def create_dataset(language_code: LanguageCode) -> Dataset: + """ + Creates a dataset that can be mapped to tasks + """ + with Session(engine) as session: + statement = ( + insert(Dataset) + .values( + name="test_dataset", + language_id=language_code.id, + translated=False, + templated=True, + ) + .on_conflict_do_update( + index_elements=["name"], + set_=dict( + name="test_dataset", + language_id=language_code.id, + translated=False, + templated=True, + ), + ) + .returning(Dataset) + ) + + dataset = session.execute(statement).one() + session.commit() + + return dataset + + +def create_tasks(dataset: Dataset) -> List[Task]: + """ + Creates tasks for the given dataset. + """ + fake_tasks = [ + { + "prompt": "This is a test prompt", + "completion": "This is a test completion", + "task_type": "audit_xp3", + "dataset_id": dataset.id, + "language_id": dataset.language_id, + }, + { + "prompt": "Termina la siguiente oración con la mejor opción: Agregar agarre adicional para quitar el frasco. Opciones: - Coloque la almohadilla caliente de silicona sobre la tapa. - Coloque la almohadilla caliente de silicona debajo del frasco. Respuesta:", + "completion": "Coloque una almohadilla caliente de silicona sobre la tapa.", + "task_type": "audit_translation", + "dataset_id": dataset.id, + "language_id": dataset.language_id, + }, + ] + + for i in range(1, 100): + fake_tasks.append({ + "prompt": f"This is test prompt {i}", + "completion": f"This is test completion {i}", + "task_type": "audit_xp3", + "dataset_id": dataset.id, + "language_id": dataset.language_id, + }, + ) + + data = [] + with Session(engine) as session: + for ft in fake_tasks: + statement = ( + insert(Task) + .values(ft) + .on_conflict_do_nothing( + index_elements=["key_hash"], + ) + .returning(Task) + ) + tasks = session.execute(statement).one_or_none() + if tasks: + data.append(tasks) + + session.commit() + + return data + + +def create_task_audits(user: User, tasks: List[Task]) -> List[TaskAudit]: + """ + Creates task audits for the given dataset. + """ + task_audits = [] + for task in tasks: + task_audits.append({ + "task_id": task.id, + "submitted_by": user.id, + "submitted_prompt": task.prompt + " (edited)", + "submitted_completion": task.completion + " (edited)", + "prompt_edited": True, + "completion_edited": True, + "prompt_rating": 0, + "completion_rating": 0, + }, + ) + + data = [] + with Session(engine) as session: + for ta in task_audits: + statement = ( + insert(TaskAudit) + .values(ta) + .on_conflict_do_nothing( + index_elements=["id"], + ) + .returning(TaskAudit) + ) + tasks = session.execute(statement).one_or_none() + if tasks: + data.append(tasks) + + session.commit() + + return data + + +def create_task_contribution(user: User, dataset: Dataset) -> List[TaskContribution]: + """ + Creates contributions for the given dataset. + """ + fake_tasks = [] + for i in range(1, 100): + fake_tasks.append({ + "submitted_by": user.id, + "submitted_prompt": f"This is a contributed prompt {i}", + "submitted_completion": f"This is contributed completion {i}", + "language_id": dataset.language_id, + }, + ) + + data = [] + with Session(engine) as session: + for ft in fake_tasks: + statement = ( + insert(TaskContribution) + .values(ft) + .on_conflict_do_nothing( + index_elements=["id"], + ) + .returning(TaskContribution) + ) + tasks = session.execute(statement).one_or_none() + if tasks: + data.append(tasks) + + session.commit() + + return data + + +def main() -> None: + """ + Main function. + """ + logger.info("Seeding database with fake data...") + + language_code = get_language_code() + country_code = get_country_code() + + user = create_user(language_code, country_code) + dataset = create_dataset(language_code) + tasks = create_tasks(dataset) + task_audits = create_task_audits(user, tasks) + task_contributions = create_task_contribution(user, dataset) + + logger.info("Done!") + + logger.info(f"Created the following user: {user}") + logger.info(f"Created the following language code: {language_code}") + logger.info(f"Created the following dataset: {dataset}") + logger.info(f"Created the following task_audits: {task_audits}") + logger.info(f"Created the following task_contributions: {task_contributions}") + + +if __name__ == "__main__": + main() diff --git a/backend/tests/api/test_auth.py b/backend/tests/api/test_auth.py new file mode 100644 index 0000000..8749ee7 --- /dev/null +++ b/backend/tests/api/test_auth.py @@ -0,0 +1,36 @@ +from jwt import encode + + +def test_authenticated_no_header(test_client, db): + response = test_client.get("/api/v1/auth/authenticated") + assert response.status_code == 200 + assert response.json() == {"is_authenticated": False} + + +def test_authenticated_invalid_provider(test_client, db): + token = encode({"sub": "expired_token"}, "SECRET_KEY", algorithm="HS256") + headers = { + "authorization": f"Bearer {token}" + } + response = test_client.get("/api/v1/auth/authenticated", headers=headers, + params={"auth_provider": "invalid_provider"}) + assert response.status_code == 401 + assert response.json() == {"detail": "Invalid auth provider"} + + +def test_authenticated_expired_token(test_client, db): + expired_token = encode({"sub": "expired_token", "exp": 0}, "SECRET_KEY", algorithm="HS256") + headers = { + "authorization": f"Bearer {expired_token}" + } + response = test_client.get("/api/v1/auth/authenticated", headers=headers, params={"auth_provider": "google"}) + + assert response.status_code == 401 + assert response.json() == {"detail": "Token has expired"} + + +def test_logout(test_client, db): + response = test_client.get("/api/v1/auth/logout") + + assert response.status_code == 200 + assert response.json() == {"redirect_url": "http://localhost:4000"} diff --git a/backend/tests/api/test_auth_discord.py b/backend/tests/api/test_auth_discord.py new file mode 100644 index 0000000..4380288 --- /dev/null +++ b/backend/tests/api/test_auth_discord.py @@ -0,0 +1,8 @@ +from unittest import mock + + +def test_login_route(test_client, db): + response = test_client.get("/api/v1/auth/discord/login") + assert response.status_code == 200 + assert "url" in response.json() + assert response.json()["url"].startswith("https://discord.com/oauth2/authorize") diff --git a/backend/tests/api/test_tasks.py b/backend/tests/api/test_tasks.py new file mode 100644 index 0000000..2454bdb --- /dev/null +++ b/backend/tests/api/test_tasks.py @@ -0,0 +1,387 @@ +import uuid + +from tests.api_utils import responses_sameshape +from tests.db_utils import ( + get_all_tasks_from_db, + get_all_users_from_db, + get_all_task_contributions_from_db, + populate_db_with_tasks, + populate_db_with_users, ) + + +def test_get_tasks(test_client, db): + tasks = populate_db_with_tasks(db) + + user_id = uuid.uuid4() + language_id = tasks[0].language_id + + response = test_client.get( + f"/api/v1/tasks?user_id={user_id}&language_id={language_id}" + ) + assert response.status_code == 200 + + actual_data = response.json() + expected_data = { + "tasks": [ + { + "id": "1", + "prompt": "test prompt 0", + "completion": "test completion 0", + "is_contributed": False, + }, + ], + } + assert responses_sameshape(actual_data, expected_data) + + +def test_get_tasks_contributed(test_client, db): + tasks = get_all_tasks_from_db(db) + user_id = uuid.uuid4() + language_id = tasks[0].language_id + + # get a user from the DB + users = populate_db_with_users(db) + user = users[0] + user_id = user.id + + # add a user contribution + response = test_client.post( + "/api/v1/tasks/submit-contribution", + json={ + "submitted_by": str(user_id), + "language_id": str(language_id), + "submitted_prompt": "test prompt", + "submitted_completion": "test completion", + } + ) + assert response.status_code == 201 + + contribution_id = response.json()["id"] + + # get the tasks + response = test_client.get( + f"/api/v1/tasks?user_id={user_id}&language_id={language_id}" + ) + assert response.status_code == 200 + + actual_data = response.json() + expected_data = { + "tasks": [ + { + "id": contribution_id, + "prompt": "test prompt", + "completion": "test completion", + "is_contributed": True, + }, + ], + } + assert responses_sameshape(actual_data, expected_data) + + +def test_get_tasks_contributed_then_audit(test_client, db): + tasks = get_all_tasks_from_db(db) + user_id = uuid.uuid4() + language_id = tasks[0].language_id + + # get a user from the DB + users = populate_db_with_users(db, prefix=1) + user = users[0] + user_id = user.id + + # add a user contribution + response = test_client.post( + "/api/v1/tasks/submit-contribution", + json={ + "submitted_by": str(user_id), + "language_id": str(language_id), + "submitted_prompt": "test prompt", + "submitted_completion": "test completion", + } + ) + assert response.status_code == 201 + + contribution_id = response.json()["id"] + + # get the tasks + response = test_client.get( + f"/api/v1/tasks?user_id={user_id}&language_id={language_id}" + ) + assert response.status_code == 200 + + actual_data = response.json() + expected_data = { + "tasks": [ + { + "id": contribution_id, + "prompt": "test prompt", + "completion": "test completion", + "is_contributed": True, + }, + ], + } + assert responses_sameshape(actual_data, expected_data) + + contribution_prompt = response.json()["tasks"][0]["prompt"] + contribution_completion = response.json()["tasks"][0]["completion"] + + # then audit that contribution + # then, submit an audit for the task + response = test_client.post( + "/api/v1/tasks/submit-contribution-audit", + json={ + "task_contribution_id": contribution_id, + "submitted_by": str(user_id), + "submitted_prompt": contribution_prompt, + "submitted_completion": contribution_completion, + "prompt_edited": False, + "completion_edited": False, + "prompt_rating": 1, + "completion_rating": 1 + } + ) + assert response.status_code == 201 + + +def test_get_tasks_one_less_after_audit(test_client, db): + """ + Ensure that we get one less task for a user after we audit one. + """ + # get a task from the DB + tasks = get_all_tasks_from_db(db) + task = tasks[1] + language_id = task.language_id + + # get a user from the DB + users = get_all_users_from_db(db) + user = users[1] + user_id = user.id + + # first, ensure we can get a task from the DB + # and we should get a 200 response + response = test_client.get( + f"/api/v1/tasks?user_id={user_id}&language_id={language_id}" + ) + assert response.status_code == 200 + + # then, submit an audit for the task + response = test_client.post( + "/api/v1/tasks/submit-audit", + json={ + "task_id": str(task.id), + "submitted_by": str(user.id), + "submitted_prompt": task.prompt, + "submitted_completion": task.completion, + "prompt_edited": False, + "completion_edited": False, + "prompt_rating": 1, + "completion_rating": 1 + } + ) + assert response.status_code == 201 + + # finally, try and get a task from the DB again. + # we should get a 204 response, because we've already audited + # all the tasks in the database that this user can audit + # for the given language + response = test_client.get( + f"/api/v1/tasks?user_id={user_id}&language_id={language_id}" + ) + assert response.status_code == 204 + + +def test_get_tasks_when_no_tasks_available(test_client, db): + """ + Ensure that we get a 204 response when there are no tasks available. + In this particular case, we'll test for when all tasks have been audited + for a given language 3 times by other users, meaning that for a particular + user, there are no tasks available. + """ + # get a task from the DB + tasks = get_all_tasks_from_db(db) + task = tasks[1] + language_id = task.language_id + + # get two users from the DB + users = populate_db_with_users(db, prefix=2) + current_user = users[0] + other_user = users[1] + + current_user_id = current_user.id + + # first, ensure we can get a task from the DB + # and we should get a 200 response + response = test_client.get( + f"/api/v1/tasks?user_id={current_user_id}&language_id={language_id}" + ) + assert response.status_code == 200 + + # then, submit an audit for every task with this language_id 3 times + # by other users + for task in tasks: + if task.language_id == language_id: + for i in range(3): + response = test_client.post( + "/api/v1/tasks/submit-audit", + json={ + "task_id": str(task.id), + "submitted_by": str(other_user.id), + "submitted_prompt": task.prompt, + "submitted_completion": task.completion, + "prompt_edited": False, + "completion_edited": False, + "prompt_rating": 1, + "completion_rating": 1 + } + ) + assert response.status_code == 201 + + # finally, try and get a task from the DB again. + # we should get a 204 response, because we've already audited + # all the tasks in the database that this user can audit + # for the given language + response = test_client.get( + f"/api/v1/tasks?user_id={current_user_id}&language_id={language_id}" + ) + assert response.status_code == 204 + + +def test_get_tasks_with_invalid_user_id(test_client): + user_id = "invalid" + language_id = uuid.uuid4() + + response = test_client.get( + f"/api/v1/tasks?user_id={user_id}&language_id={language_id}" + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["query", "user_id"], + "msg": "value is not a valid uuid", + "type": "type_error.uuid", + } + ] + } + + +def test_get_tasks_with_invalid_language_code(test_client): + user_id = uuid.uuid4() + language_id = "invalid uuid" + + response = test_client.get( + f"/api/v1/tasks?user_id={user_id}&language_id={language_id}" + ) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["query", "language_id"], + "msg": "value is not a valid uuid", + "type": "type_error.uuid", + } + ] + } + + +def test_get_tasks_with_missing_user_id(test_client): + language_id = uuid.uuid4() + + response = test_client.get(f"/api/v1/tasks?language_id={language_id}") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["query", "user_id"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + + +def test_get_tasks_with_missing_language_id(test_client): + user_id = uuid.uuid4() + + response = test_client.get(f"/api/v1/tasks?user_id={user_id}") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["query", "language_id"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + + +def test_get_tasks_with_missing_user_id_and_language_id(test_client): + response = test_client.get(f"/api/v1/tasks") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["query", "user_id"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["query", "language_id"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +def test_submit_task_contribution_audit(test_client, db): + task_contributions = get_all_task_contributions_from_db(db) + contribution_id = task_contributions[0].id + + user_id = task_contributions[0].submitted_by + + response = test_client.post( + "/api/v1/tasks/submit-contribution-audit", + json={ + "task_contribution_id": str(contribution_id), + "submitted_by": str(user_id), + "submitted_prompt": "test prompt", + "submitted_completion": "test completion", + "prompt_edited": True, + "completion_edited": True, + "prompt_rating": 0, + "completion_rating": 0 + } + ) + assert response.status_code == 201 + + +def test_submit_task_contribution_with_empty_prompt_completion(test_client): + user_id = uuid.uuid4() + language_id = uuid.uuid4() + + response = test_client.post( + "/api/v1/tasks/submit-contribution", + json={ + "submitted_by": str(user_id), + "submitted_prompt": "", + "submitted_completion": "", + "language_id": str(language_id), + } + ) + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "loc": ["body", "submitted_prompt"], + "msg": "prompt must not be empty", + "type": "value_error" + }, + { + "loc": ["body", "submitted_completion"], + "msg": "completion must not be empty", + "type": "value_error" + } + ] + } diff --git a/backend/tests/api_utils.py b/backend/tests/api_utils.py new file mode 100644 index 0000000..ff3a057 --- /dev/null +++ b/backend/tests/api_utils.py @@ -0,0 +1,21 @@ +def responses_sameshape(d1, d2): + """ + Returns True if d1 and d2 have the same shape, False otherwise. + + Recursively checks the shape of d1 and d2. + """ + + if isinstance(d1, dict): + if isinstance(d2, dict): + # then we have shapes to check + return ( + d1.keys() == d2.keys() + and + # so the keys are all the same + all(responses_sameshape(d1[k], d2[k]) for k in d1.keys()) + ) + # thus all values will be tested in the same way. + else: + return False # d1 is a dict, but d2 isn't + else: + return not isinstance(d2, dict) # if d2 is a dict, False, else True. diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..286864b --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,86 @@ +import asyncio + +import pytest +from fastapi.testclient import TestClient +from httpx import AsyncClient +from sqlalchemy_utils import create_database, database_exists, drop_database +from sqlmodel import Session, create_engine + +from instruct_multilingual.config import get_settings + +test_settings = get_settings() + +if test_settings.config.environment != "test": + raise ValueError(f"ENVIRONMENT must be 'test' to run tests. Got {test_settings.config.environment}") + + +@pytest.fixture(scope="session") +def engine(): + postgres_url = test_settings.instruct_multilingual_app_db_uri + engine = create_engine( + postgres_url, + client_encoding="utf8", + pool_pre_ping=True, + isolation_level="REPEATABLE READ", + ) + + if not database_exists(engine.url): + print("creating test database") + create_database(engine.url, template="template0") + print("test database created") + + yield engine + + +@pytest.fixture(scope="session") +def db(engine): + connection = engine.connect() + session = Session(bind=connection) + + session.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') + session.execute("CREATE EXTENSION IF NOT EXISTS pgcrypto;") + session.commit() + + yield session + + session.close() + + if database_exists(engine.url): + print("dropping test database") + drop_database(engine.url) + print("test database dropped") + + connection.close() + + +@pytest.fixture(scope="session") +def test_client(db): + from main import app + + with TestClient(app) as client: + yield client + + +@pytest.fixture(scope="session") +def anyio_backend(): + return "asyncio" + + +@pytest.fixture(scope="session") +def event_loop(): + """ + Override the default event loop with a new one. + + See: https://github.com/tortoise/tortoise-orm/issues/638 + """ + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +@pytest.fixture(scope="session") +async def async_test_client(db): + from main import app + + async with AsyncClient(app=app, base_url="http://test") as client: + yield client \ No newline at end of file diff --git a/backend/tests/db_utils.py b/backend/tests/db_utils.py new file mode 100644 index 0000000..5d3ab92 --- /dev/null +++ b/backend/tests/db_utils.py @@ -0,0 +1,108 @@ +from sqlmodel import select + +from instruct_multilingual.models import ( + Dataset, + LanguageCode, + Task, + TaskContribution, + User, +) + + +def populate_db_with_datasets(database): + language_codes = database.exec(select(LanguageCode)).all() + + datasets = [] + for i in range(len(language_codes)): + # create a dataset for each language code + dataset = Dataset( + name=f"test dataset {i}", + language_id=language_codes[i].id, + translated=True, + templated=True, + ) + database.add(dataset) + database.commit() + database.refresh(dataset) + datasets.append(dataset) + + return datasets + + +def populate_db_with_tasks(database): + populate_db_with_datasets(database) + + datasets = database.exec(select(Dataset)).all() + + tasks = [] + for i in range(len(datasets)): + # create a task for each dataset + task = Task( + prompt=f"test prompt {i}", + completion=f"test completion {i}", + dataset_id=datasets[i].id, + task_type="audit_translation", + language_id=datasets[i].language_id, + ) + database.add(task) + database.commit() + database.refresh(task) + tasks.append(task) + + return tasks + + +def clear_tasks_from_db(database): + tasks = database.exec(select(Task)).all() + + for task in tasks: + database.delete(task) + database.commit() + + return tasks + + +def get_one_task_from_db(database): + task = database.exec(select(Task)).first() + return task + + +def get_all_tasks_from_db(database): + tasks = database.exec(select(Task)).all() + return tasks + + +def populate_db_with_users(database, prefix=0): + users = [] + for i in range(3): + user = User( + username=f"test user {i}", + email=f"testemail{prefix}{i}@fake-aya-mail.com", + image_url=f"test image url {i}", + ) + database.add(user) + database.commit() + database.refresh(user) + users.append(user) + + return users + + +def clear_users_from_db(database): + users = database.exec(select(User)).all() + + for user in users: + database.delete(user) + database.commit() + + return users + + +def get_all_users_from_db(database): + users = database.exec(select(User)).all() + return users + + +def get_all_task_contributions_from_db(database): + task_contributions = database.exec(select(TaskContribution)).all() + return task_contributions diff --git a/cloudbuild-backend-staging.yaml b/cloudbuild-backend-staging.yaml new file mode 100644 index 0000000..a42172f --- /dev/null +++ b/cloudbuild-backend-staging.yaml @@ -0,0 +1,49 @@ +steps: +# Set the PR_BUILD flag +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$_PR_BUILD" == "true" ]; then + echo "true" > /workspace/PR_BUILD.txt + else + echo "false" > /workspace/PR_BUILD.txt + fi + id: 'set-pr-build-flag-staging' + +# Build the backend Docker image. +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-t', 'gcr.io/$PROJECT_ID/backend:$SHORT_SHA', './backend/'] + id: 'build-backend-image-staging' + +# Push the backend Docker image to Google Container Registry +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/backend:$SHORT_SHA'] + id: 'push-backend-image-staging' + +# Deploy the backend to Google Cloud Run +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$(cat /workspace/PR_BUILD.txt)" == "false" ]; then + gcloud run deploy instruct-multilingual-backend-staging \ + --image gcr.io/$PROJECT_ID/backend:$SHORT_SHA \ + --region ${_REGION} \ + --platform managed \ + --allow-unauthenticated \ + --concurrency 5 \ + --cpu 1 \ + --memory 2Gi \ + --min-instances 2 \ + --max-instances 3 \ + --set-cloudsql-instances $PROJECT_ID:${_REGION}:instruct-multilingual-app-db-staging-2 \ + --vpc-connector $(gcloud secrets versions access latest --secret=VPC_CONNECTOR_BACKEND_PROD) \ + --port 8080 \ + --set-env-vars ENVIRONMENT=staging,INSTRUCT_MULTILINGUAL_APP_DB_URI=${_INSTRUCT_MULTILINGUAL_APP_DB_URI},JWT_SECRET=${_JWT_SECRET},JWT_ALGORITHM=${_JWT_ALGORITHM},JWT_EXPIRATION_TIME=${_JWT_EXPIRATION_TIME},DISCORD_API_BASE_URL=${_DISCORD_API_BASE_URL},DISCORD_CLIENT_ID=${_DISCORD_CLIENT_ID},DISCORD_CLIENT_SECRET=${_DISCORD_CLIENT_SECRET},DISCORD_REDIRECT_URI=${_DISCORD_REDIRECT_URI},GOOGLE_CLIENT_ID=${_GOOGLE_CLIENT_ID},GOOGLE_CLIENT_SECRET=${_GOOGLE_CLIENT_SECRET},GOOGLE_REDIRECT_URI=${_GOOGLE_REDIRECT_URI},FRONTEND_URL=https://instruct-multilingual-frontend-staging-dtjnk4f6ra-ue.a.run.app,FOR_AI_URL=https://instruct-multilingual-frontend-staging-dtjnk4f6ra-ue.a.run.app,APP_NAME=${_APP_NAME} + echo $(gcloud run services describe instruct-multilingual-backend-staging --platform managed --region ${_REGION} --format 'value(status.url)') > /workspace/BACKEND_URL.txt + fi + id: 'deploy-backend-staging' + timeout: '600s' diff --git a/cloudbuild-backend.yaml b/cloudbuild-backend.yaml new file mode 100644 index 0000000..ec6379a --- /dev/null +++ b/cloudbuild-backend.yaml @@ -0,0 +1,49 @@ +steps: +# Set the PR_BUILD flag +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$_PR_BUILD" == "true" ]; then + echo "true" > /workspace/PR_BUILD.txt + else + echo "false" > /workspace/PR_BUILD.txt + fi + id: 'set-pr-build-flag' + +# Build the backend Docker image. +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-t', 'gcr.io/$PROJECT_ID/backend:$SHORT_SHA', './backend/'] + id: 'build-backend-image' + +# Push the backend Docker image to Google Container Registry +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/backend:$SHORT_SHA'] + id: 'push-backend-image' + +# Deploy the backend to Google Cloud Run +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$(cat /workspace/PR_BUILD.txt)" == "false" ]; then + gcloud run deploy instruct-multilingual-backend \ + --image gcr.io/$PROJECT_ID/backend:$SHORT_SHA \ + --region ${_REGION} \ + --platform managed \ + --allow-unauthenticated \ + --concurrency 5 \ + --cpu 2 \ + --memory 2Gi \ + --min-instances 2 \ + --max-instances 10 \ + --set-cloudsql-instances $PROJECT_ID:${_REGION}:instruct-multilingual-app-db \ + --vpc-connector $(gcloud secrets versions access latest --secret=VPC_CONNECTOR_BACKEND_PROD) \ + --port 8080 \ + --set-env-vars ENVIRONMENT=production,INSTRUCT_MULTILINGUAL_APP_DB_URI=${_INSTRUCT_MULTILINGUAL_APP_DB_URI},JWT_SECRET=${_JWT_SECRET},JWT_ALGORITHM=${_JWT_ALGORITHM},JWT_EXPIRATION_TIME=${_JWT_EXPIRATION_TIME},DISCORD_API_BASE_URL=${_DISCORD_API_BASE_URL},DISCORD_CLIENT_ID=${_DISCORD_CLIENT_ID},DISCORD_CLIENT_SECRET=${_DISCORD_CLIENT_SECRET},DISCORD_REDIRECT_URI=${_DISCORD_REDIRECT_URI},GOOGLE_CLIENT_ID=${_GOOGLE_CLIENT_ID},GOOGLE_CLIENT_SECRET=${_GOOGLE_CLIENT_SECRET},GOOGLE_REDIRECT_URI=${_GOOGLE_REDIRECT_URI},FRONTEND_URL=${_FRONTEND_URL},FOR_AI_URL=${_FOR_AI_URL},APP_NAME=${_APP_NAME},DISCORD_WEBHOOK_URL=${_DISCORD_WEBHOOK_URL} + echo $(gcloud run services describe instruct-multilingual-backend --platform managed --region ${_REGION} --format 'value(status.url)') > /workspace/BACKEND_URL.txt + fi + id: 'deploy-backend' + timeout: '600s' diff --git a/cloudbuild-frontend-staging.yaml b/cloudbuild-frontend-staging.yaml new file mode 100644 index 0000000..9b1ab75 --- /dev/null +++ b/cloudbuild-frontend-staging.yaml @@ -0,0 +1,71 @@ +steps: +# Set the PR_BUILD flag +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$_PR_BUILD" == "true" ]; then + echo "true" > /workspace/PR_BUILD.txt + else + echo "false" > /workspace/PR_BUILD.txt + fi + id: 'set-pr-build-flag-staging' + +# Build the frontend Docker image +- name: 'gcr.io/cloud-builders/docker' + args: [ + 'build', + '--build-arg', + 'ENVIRONMENT=staging', + '--build-arg', + 'GOOGLE_ANALYTICS_ID=$_GOOGLE_ANALYTICS_ID', + '-t', + 'gcr.io/$PROJECT_ID/frontend:$SHORT_SHA', + './frontend/' + ] + id: 'build-frontend-image-staging' + +# Push the frontend Docker image to Google Container Registry +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/frontend:$SHORT_SHA'] + id: 'push-frontend-image-staging' + +# Deploy the frontend to Google Cloud Run +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$(cat /workspace/PR_BUILD.txt)" == "false" ]; then + gcloud run deploy instruct-multilingual-frontend-staging \ + --image gcr.io/$PROJECT_ID/frontend:$SHORT_SHA \ + --project $PROJECT_ID \ + --region ${_REGION} \ + --platform managed \ + --allow-unauthenticated \ + --concurrency 5 \ + --cpu 1 \ + --memory 2Gi \ + --min-instances 2 \ + --max-instances 3 \ + --set-env-vars ENVIRONMENT=staging,BACKEND_URL=https://instruct-multilingual-backend-staging-dtjnk4f6ra-ue.a.run.app + echo $(gcloud run services describe instruct-multilingual-frontend-staging --platform managed --region ${_REGION} --format 'value(status.url)') > /workspace/FRONTEND_URL.txt + fi + id: 'deploy-frontend-staging' + timeout: '600s' + +# Update the backend with the correct FRONTEND_URL +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$(cat /workspace/PR_BUILD.txt)" == "false" ]; then + gcloud run services update instruct-multilingual-backend-staging \ + --region ${_REGION} \ + --platform managed \ + --update-env-vars FRONTEND_URL="$(cat /workspace/FRONTEND_URL.txt)" + fi + id: 'update-backend-frontend-url-staging' + diff --git a/cloudbuild-frontend.yaml b/cloudbuild-frontend.yaml new file mode 100644 index 0000000..170cb88 --- /dev/null +++ b/cloudbuild-frontend.yaml @@ -0,0 +1,71 @@ +steps: +# Set the PR_BUILD flag +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$_PR_BUILD" == "true" ]; then + echo "true" > /workspace/PR_BUILD.txt + else + echo "false" > /workspace/PR_BUILD.txt + fi + id: 'set-pr-build-flag' + +# Build the frontend Docker image +- name: 'gcr.io/cloud-builders/docker' + args: [ + 'build', + '--build-arg', + 'ENVIRONMENT=production', + '--build-arg', + 'GOOGLE_ANALYTICS_ID=$_GOOGLE_ANALYTICS_ID', + '-t', + 'gcr.io/$PROJECT_ID/frontend:$SHORT_SHA', + './frontend/' + ] + id: 'build-frontend-image' + +# Push the frontend Docker image to Google Container Registry +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/frontend:$SHORT_SHA'] + id: 'push-frontend-image' + +# Deploy the frontend to Google Cloud Run +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$(cat /workspace/PR_BUILD.txt)" == "false" ]; then + gcloud run deploy instruct-multilingual-frontend \ + --image gcr.io/$PROJECT_ID/frontend:$SHORT_SHA \ + --project $PROJECT_ID \ + --region ${_REGION} \ + --platform managed \ + --allow-unauthenticated \ + --concurrency 5 \ + --cpu 2 \ + --memory 2Gi \ + --min-instances 5 \ + --max-instances 15 \ + --set-env-vars ENVIRONMENT=production,BACKEND_URL=https://instruct-multilingual-backend-dtjnk4f6ra-ue.a.run.app + echo $(gcloud run services describe instruct-multilingual-frontend --platform managed --region ${_REGION} --format 'value(status.url)') > /workspace/FRONTEND_URL.txt + fi + id: 'deploy-frontend' + timeout: '600s' + +# Update the backend with the correct FRONTEND_URL +- name: 'gcr.io/cloud-builders/gcloud' + entrypoint: 'bash' + args: + - '-c' + - | + if [ "$(cat /workspace/PR_BUILD.txt)" == "false" ]; then + gcloud run services update instruct-multilingual-backend \ + --region ${_REGION} \ + --platform managed \ + --update-env-vars FRONTEND_URL="$(cat /workspace/FRONTEND_URL.txt)" + fi + id: 'update-backend-frontend-url' + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b774874 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,104 @@ +version: '3' +services: + database: + build: + context: ./docker + dockerfile: Dockerfile.postgres + container_name: postgresdb + volumes: + - postgresdb:/var/lib/postgresql/data + env_file: + - ./backend/.env.local + ports: + - "5432:5432" + expose: + - 5432 + networks: + - app_network + + backend: + build: + context: ./backend + dockerfile: Dockerfile + env_file: + - ./backend/.env.${ENVIRONMENT} + ports: + - "8080:8080" + expose: + - 8080 + networks: + - app_network + depends_on: + - database + stdin_open: true + tty: true + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + environment: + - NODE_ENV=local + - BACKEND_URL=http://backend:8080 + - PORT=4000 + ports: + - "4000:4000" + expose: + - 4000 + networks: + - app_network + depends_on: + - backend + + daily_leaderboard_job: + build: + context: ./backend/ + dockerfile: jobs/daily/Dockerfile + env_file: + - ./backend/.env.${ENVIRONMENT} + networks: + - app_network + depends_on: + - database + + weekly_leaderboard_job: + build: + context: ./backend/ + dockerfile: jobs/weekly/Dockerfile + env_file: + - ./backend/.env.${ENVIRONMENT} + networks: + - app_network + depends_on: + - database + + by_language_leaderboard_job: + build: + context: ./backend/ + dockerfile: jobs/by_language/Dockerfile + env_file: + - ./backend/.env.${ENVIRONMENT} + networks: + - app_network + depends_on: + - database + + overall_leaderboard_job: + build: + context: ./backend/ + dockerfile: jobs/overall/Dockerfile + env_file: + - ./backend/.env.${ENVIRONMENT} + networks: + - app_network + depends_on: + - database + stdin_open: true + tty: true + +networks: + app_network: + driver: bridge + +volumes: + postgresdb: diff --git a/docker/Dockerfile.postgres b/docker/Dockerfile.postgres new file mode 100644 index 0000000..fa0fcc1 --- /dev/null +++ b/docker/Dockerfile.postgres @@ -0,0 +1,9 @@ +FROM postgres:14 + +RUN apt-get update && apt-get install -y postgresql-contrib + +RUN echo "CREATE EXTENSION \"uuid-ossp\";" > /docker-entrypoint-initdb.d/init-extensions.sql +RUN echo "CREATE EXTENSION IF NOT EXISTS pgcrypto;" >> /docker-entrypoint-initdb.d/init-extensions.sql +RUN echo "DO \$body\$ BEGIN IF NOT EXISTS (SELECT FROM pg_catalog.pg_user WHERE usename = 'backendapp') THEN CREATE USER backendapp WITH PASSWORD 'password'; END IF; END \$body\$;" >> /docker-entrypoint-initdb.d/init-user.sql +RUN echo "GRANT ALL PRIVILEGES ON DATABASE instruct_multilingual TO backendapp;" >> /docker-entrypoint-initdb.d/init-user.sql +RUN echo "ALTER USER backendapp CREATEDB;" >> /docker-entrypoint-initdb.d/init-user.sql \ No newline at end of file diff --git a/frontend/.babelrc b/frontend/.babelrc new file mode 100644 index 0000000..2b7bafa --- /dev/null +++ b/frontend/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] +} diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..2fd83a6 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,3 @@ +.idea +node_modules +dist diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..1f472cb --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,6 @@ +ENVIRONMENT=local +PORT=80 + +BACKEND_URL=http://127.0.0.1:8080 + +GOOGLE_ANALYTICS_ID= diff --git a/frontend/.eslintrc b/frontend/.eslintrc new file mode 100644 index 0000000..8190cf5 --- /dev/null +++ b/frontend/.eslintrc @@ -0,0 +1,7 @@ +{ + "extends": ["react-app", "plugin:prettier/recommended"], + "plugins": ["prettier"], + "rules": { + "prettier/prettier": "error" + } +} diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..5c2bc99 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,29 @@ +{ + "endOfLine": "lf", + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5", + "importOrder": [ + "^(react/(.*)$)|^(react$)", + "^(next/(.*)$)|^(next$)", + "", + "", + "^types$", + "^@/types/(.*)$", + "^@/config/(.*)$", + "^@/lib/(.*)$", + "^@/components/(.*)$", + "^@/styles/(.*)$", + "^[./]" + ], + "importOrderSeparation": false, + "importOrderSortSpecifiers": true, + "importOrderBuiltinModulesToTop": true, + "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"], + "importOrderMergeDuplicateImports": true, + "importOrderCombineTypeAndValueImports": true, + "plugins": [ + "@ianvs/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss" + ] +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..ff5680d --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,25 @@ +# Use an official Node.js runtime as a parent image +FROM node:16.11.0-alpine3.14 + +ARG ENVIRONMENT +ENV ENVIRONMENT=${ENVIRONMENT} + +ARG GOOGLE_ANALYTICS_ID +ENV GOOGLE_ANALYTICS_ID=${GOOGLE_ANALYTICS_ID} + +# Set the working directory to /frontend +WORKDIR /frontend + +# Copy the frontend code into the container +COPY ./ /frontend/ + +# Install dependencies using npm +RUN npm install + +# Build the production version of the app +RUN npm run build + +EXPOSE 80 + +# Define the command to start the app +CMD ["npm", "start"] diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..c85393f --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,32 @@ + + + + + + + Aya by C4AI + + + + + +

+ + + + + + + + + + + + + + + + diff --git a/frontend/jest.config.mjs b/frontend/jest.config.mjs new file mode 100644 index 0000000..fc879bf --- /dev/null +++ b/frontend/jest.config.mjs @@ -0,0 +1,195 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +export default { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/var/folders/w6/9kxch5yx1l32hjj97k1mzfw40000gn/T/jest_dx", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + // coverageProvider: "babel", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "jsdom", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/frontend/lighthouserc.cjs b/frontend/lighthouserc.cjs new file mode 100644 index 0000000..5eac462 --- /dev/null +++ b/frontend/lighthouserc.cjs @@ -0,0 +1,10 @@ +module.exports = { + ci: { + collect: { + staticDistDir: "./dist", + }, + upload: { + target: "temporary-public-storage", + }, + }, +}; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ff36c58 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,13719 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@chakra-ui/react": "^2.6.0", + "@emotion/cache": "^11.11.0", + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "axios": "^1.3.4", + "compression": "^1.7.4", + "diff-match-patch": "^1.0.5", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "framer-motion": "^10.12.4", + "jwt-decode": "^3.1.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-ga4": "^2.1.0", + "react-icons": "^4.8.0", + "react-router-dom": "^6.9.0", + "react-select": "^5.7.3", + "react-star-ratings": "^2.3.0", + "recoil": "^0.7.7", + "stylis": "^4.2.0", + "stylis-plugin-rtl": "^2.1.1", + "tailwindcss": "^3.3.1" + }, + "devDependencies": { + "@babel/core": "^7.21.8", + "@babel/preset-env": "^7.21.5", + "@ianvs/prettier-plugin-sort-imports": "^3.7.2", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^3.1.0", + "autoprefixer": "^10.4.14", + "babel-jest": "^29.5.0", + "eslint": "^8.38.0", + "eslint-config-prettier": "^8.8.0", + "eslint-config-react-app": "^7.0.1", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "prettier": "^2.8.7", + "prettier-plugin-tailwindcss": "^0.2.7", + "sass": "^1.60.0", + "vite": "^4.2.0", + "vite-plugin-eslint": "^1.8.1" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", + "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", + "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helpers": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.21.3.tgz", + "integrity": "sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "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/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", + "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", + "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz", + "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", + "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", + "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dependencies": { + "@babel/types": "^7.21.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", + "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", + "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", + "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.21.0.tgz", + "integrity": "sha512-MfgX49uRrFUTL/HvWtmx3zmpyzMMr4MTj3d527MLlr/4RTT9G/ytFFP7qet2uM2Ve03b+BkpWUpK+lRXnQ+v9w==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.21.0.tgz", + "integrity": "sha512-tIoPpGBR8UuM4++ccWN3gifhVvQu7ZizuR1fklhRJrd5ewgbkUS+0KVFeWWxELtn18NTLoW32XV7zyOgIAiz+w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", + "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz", + "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz", + "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/template": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz", + "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-flow": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz", + "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz", + "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-simple-access": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", + "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.21.0.tgz", + "integrity": "sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz", + "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz", + "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz", + "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz", + "integrity": "sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz", + "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.5.tgz", + "integrity": "sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.21.5", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.21.5", + "@babel/plugin-transform-destructuring": "^7.21.3", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.21.5", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.5", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.21.3", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.21.5", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.20.7", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.21.5", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.21.5", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", + "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-transform-typescript": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@chakra-ui/accordion": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-2.1.11.tgz", + "integrity": "sha512-mfVPmqETp9pyRDHJ33AdF19oHv/LyxVzQJtlxUByuvs8Cj9QQZ2LQLg5kejm+b3mj03A7A6yfbuo3RNaI4Bhsg==", + "dependencies": { + "@chakra-ui/descendant": "3.0.14", + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-controllable-state": "2.0.8", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.0.16" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/alert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-2.1.0.tgz", + "integrity": "sha512-OcfHwoXI5VrmM+tHJTHT62Bx6TfyfCxSa0PWUOueJzSyhlUOKBND5we6UtrOB7D0jwX45qKKEDJOLG5yCG21jQ==", + "dependencies": { + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.0.13" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/anatomy": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.1.2.tgz", + "integrity": "sha512-pKfOS/mztc4sUXHNc8ypJ1gPWSolWT770jrgVRfolVbYlki8y5Y+As996zMF6k5lewTu6j9DQequ7Cc9a69IVQ==" + }, + "node_modules/@chakra-ui/avatar": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.2.9.tgz", + "integrity": "sha512-fjO25iNeMxSZKYGvbAqdMjsRus9Hgvhb+Ux8jNwKcfg47nqT+wVieMqsPdpQ0ggAuh1872oVvg2q1GfDdieMmA==", + "dependencies": { + "@chakra-ui/image": "2.0.16", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/breadcrumb": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.1.5.tgz", + "integrity": "sha512-p3eQQrHQBkRB69xOmNyBJqEdfCrMt+e0eOH+Pm/DjFWfIVIbnIaFbmDCeWClqlLa21Ypc6h1hR9jEmvg8kmOog==", + "dependencies": { + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/breakpoint-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz", + "integrity": "sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "node_modules/@chakra-ui/button": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.0.18.tgz", + "integrity": "sha512-E3c99+lOm6ou4nQVOTLkG+IdOPMjsQK+Qe7VyP8A/xeAMFONuibrWPRPpprr4ZkB4kEoLMfNuyH2+aEza3ScUA==", + "dependencies": { + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.0.13" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/card": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/card/-/card-2.1.6.tgz", + "integrity": "sha512-fFd/WAdRNVY/WOSQv4skpy0WeVhhI0f7dTY1Sm0jVl0KLmuP/GnpsWtKtqWjNcV00K963EXDyhlk6+9oxbP4gw==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/checkbox": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-2.2.15.tgz", + "integrity": "sha512-Ju2yQjX8azgFa5f6VLPuwdGYobZ+rdbcYqjiks848JvPc75UsPhpS05cb4XlrKT7M16I8txDA5rPJdqqFicHCA==", + "dependencies": { + "@chakra-ui/form-control": "2.0.18", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.0.7", + "@chakra-ui/react-use-controllable-state": "2.0.8", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/react-use-safe-layout-effect": "2.0.5", + "@chakra-ui/react-use-update-effect": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/visually-hidden": "2.0.15", + "@zag-js/focus-visible": "0.2.2" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/clickable": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.0.14.tgz", + "integrity": "sha512-jfsM1qaD74ZykLHmvmsKRhDyokLUxEfL8Il1VoZMNX5RBI0xW/56vKpLTFF/v/+vLPLS+Te2cZdD4+2O+G6ulA==", + "dependencies": { + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/close-button": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.0.17.tgz", + "integrity": "sha512-05YPXk456t1Xa3KpqTrvm+7smx+95dmaPiwjiBN3p7LHUQVHJd8ZXSDB0V+WKi419k3cVQeJUdU/azDO2f40sw==", + "dependencies": { + "@chakra-ui/icon": "3.0.16" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/color-mode": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.1.12.tgz", + "integrity": "sha512-sYyfJGDoJSLYO+V2hxV9r033qhte5Nw/wAn5yRGGZnEEN1dKPEdWQ3XZvglWSDTNd0w9zkoH2w6vP4FBBYb/iw==", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/control-box": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-2.0.13.tgz", + "integrity": "sha512-FEyrU4crxati80KUF/+1Z1CU3eZK6Sa0Yv7Z/ydtz9/tvGblXW9NFanoomXAOvcIFLbaLQPPATm9Gmpr7VG05A==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/counter": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.0.14.tgz", + "integrity": "sha512-KxcSRfUbb94dP77xTip2myoE7P2HQQN4V5fRJmNAGbzcyLciJ+aDylUU/UxgNcEjawUp6Q242NbWb1TSbKoqog==", + "dependencies": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/css-reset": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-2.1.1.tgz", + "integrity": "sha512-jwEOfIAWmQsnChHQTW/eRE+dfE4MjmhvSvoUug5nkV1pI7veC/20noFlIZxzi82EbiQI8Fs0+Jnusgxr2yaOHA==", + "peerDependencies": { + "@emotion/react": ">=10.0.35", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/descendant": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.0.14.tgz", + "integrity": "sha512-+Ahvp9H4HMpfScIv9w1vaecGz7qWAaK1YFHHolz/SIsGLaLGlbdp+5UNabQC7L6TUnzzJDQDxzwif78rTD7ang==", + "dependencies": { + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-merge-refs": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/dom-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/dom-utils/-/dom-utils-2.0.6.tgz", + "integrity": "sha512-PVtDkPrDD5b8aoL6Atg7SLjkwhWb7BwMcLOF1L449L3nZN+DAO3nyAh6iUhZVJyunELj9d0r65CDlnMREyJZmA==" + }, + "node_modules/@chakra-ui/editable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.0.0.tgz", + "integrity": "sha512-q/7C/TM3iLaoQKlEiM8AY565i9NoaXtS6N6N4HWIEL5mZJPbMeHKxrCHUZlHxYuQJqFOGc09ZPD9fAFx1GkYwQ==", + "dependencies": { + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.0.7", + "@chakra-ui/react-use-controllable-state": "2.0.8", + "@chakra-ui/react-use-focus-on-pointer-down": "2.0.6", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/react-use-safe-layout-effect": "2.0.5", + "@chakra-ui/react-use-update-effect": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/event-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/event-utils/-/event-utils-2.0.8.tgz", + "integrity": "sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==" + }, + "node_modules/@chakra-ui/focus-lock": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-2.0.16.tgz", + "integrity": "sha512-UuAdGCPVrCa1lecoAvpOQD7JFT7a9RdmhKWhFt5ioIcekSLJcerdLHuuL3w0qz//8kd1/SOt7oP0aJqdAJQrCw==", + "dependencies": { + "@chakra-ui/dom-utils": "2.0.6", + "react-focus-lock": "^2.9.2" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/form-control": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.0.18.tgz", + "integrity": "sha512-I0a0jG01IAtRPccOXSNugyRdUAe8Dy40ctqedZvznMweOXzbMCF1m+sHPLdWeWC/VI13VoAispdPY0/zHOdjsQ==", + "dependencies": { + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/hooks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.0.tgz", + "integrity": "sha512-GZE64mcr20w+3KbCUPqQJHHmiFnX5Rcp8jS3YntGA4D5X2qU85jka7QkjfBwv/iduZ5Ei0YpCMYGCpi91dhD1Q==", + "dependencies": { + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/utils": "2.0.15", + "compute-scroll-into-view": "1.0.20", + "copy-to-clipboard": "3.3.3" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/icon": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.0.16.tgz", + "integrity": "sha512-RpA1X5Ptz8Mt39HSyEIW1wxAz2AXyf9H0JJ5HVx/dBdMZaGMDJ0HyyPBVci0m4RCoJuyG1HHG/DXJaVfUTVAeg==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/image": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.16.tgz", + "integrity": "sha512-iFypk1slgP3OK7VIPOtkB0UuiqVxNalgA59yoRM43xLIeZAEZpKngUVno4A2kFS61yKN0eIY4hXD3Xjm+25EJA==", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.0.5", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/input": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.0.22.tgz", + "integrity": "sha512-dCIC0/Q7mjZf17YqgoQsnXn0bus6vgriTRn8VmxOc+WcVl+KBSTBWujGrS5yu85WIFQ0aeqQvziDnDQybPqAbA==", + "dependencies": { + "@chakra-ui/form-control": "2.0.18", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/layout": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-2.1.19.tgz", + "integrity": "sha512-g7xMVKbQFCODwKCkEF4/OmdPsr/fAavWUV+DGc1ZWVPdroUlg1FGTpK9bOTwkC/gnko7cMClILA+BIPR3Ylu9Q==", + "dependencies": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/lazy-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/lazy-utils/-/lazy-utils-2.0.5.tgz", + "integrity": "sha512-UULqw7FBvcckQk2n3iPO56TMJvDsNv0FKZI6PlUNJVaGsPbsYxK/8IQ60vZgaTVPtVcjY6BE+y6zg8u9HOqpyg==" + }, + "node_modules/@chakra-ui/live-region": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-2.0.13.tgz", + "integrity": "sha512-Ja+Slk6ZkxSA5oJzU2VuGU7TpZpbMb/4P4OUhIf2D30ctmIeXkxTWw1Bs1nGJAVtAPcGS5sKA+zb89i8g+0cTQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/media-query": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-3.2.12.tgz", + "integrity": "sha512-8pSLDf3oxxhFrhd40rs7vSeIBfvOmIKHA7DJlGUC/y+9irD24ZwgmCtFnn+y3gI47hTJsopbSX+wb8nr7XPswA==", + "dependencies": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.0.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/menu": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-2.1.13.tgz", + "integrity": "sha512-O7ESUIxbqWINRaO9jkPbZ8vJVW+lxZIZ9K0q828XgYBMh5o7BS82XhT7li7CxWaSQNqBxS4XE9BU7btp1ADMrQ==", + "dependencies": { + "@chakra-ui/clickable": "2.0.14", + "@chakra-ui/descendant": "3.0.14", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.0.13", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-animation-state": "2.0.8", + "@chakra-ui/react-use-controllable-state": "2.0.8", + "@chakra-ui/react-use-disclosure": "2.0.8", + "@chakra-ui/react-use-focus-effect": "2.0.10", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/react-use-outside-click": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.0.16" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/modal": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.2.11.tgz", + "integrity": "sha512-2J0ZUV5tEzkPiawdkgPz6bmex7NXAde1VXooMwdvK+vuT8PV3U61yorTJOZVLdw7TjjI1Yo94mzsp6UwBud43Q==", + "dependencies": { + "@chakra-ui/close-button": "2.0.17", + "@chakra-ui/focus-lock": "2.0.16", + "@chakra-ui/portal": "2.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.0.16", + "aria-hidden": "^1.2.2", + "react-remove-scroll": "^2.5.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/number-input": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.0.19.tgz", + "integrity": "sha512-HDaITvtMEqOauOrCPsARDxKD9PSHmhWywpcyCSOX0lMe4xx2aaGhU0QQFhsJsykj8Er6pytMv6t0KZksdDv3YA==", + "dependencies": { + "@chakra-ui/counter": "2.0.14", + "@chakra-ui/form-control": "2.0.18", + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.0.7", + "@chakra-ui/react-use-event-listener": "2.0.7", + "@chakra-ui/react-use-interval": "2.0.5", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/react-use-safe-layout-effect": "2.0.5", + "@chakra-ui/react-use-update-effect": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/number-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", + "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==" + }, + "node_modules/@chakra-ui/object-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", + "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==" + }, + "node_modules/@chakra-ui/pin-input": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-2.0.20.tgz", + "integrity": "sha512-IHVmerrtHN8F+jRB3W1HnMir1S1TUCWhI7qDInxqPtoRffHt6mzZgLZ0izx8p1fD4HkW4c1d4/ZLEz9uH9bBRg==", + "dependencies": { + "@chakra-ui/descendant": "3.0.14", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-controllable-state": "2.0.8", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/popover": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-2.1.10.tgz", + "integrity": "sha512-UCEW+zp2GEuNYvyK42+cQECSMSBFWcA0CD7Ip6TUL32BLF8EkYz5U5Gdx5Nhd/mlSE2lxo7c72/LOQd0emsO/A==", + "dependencies": { + "@chakra-ui/close-button": "2.0.17", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.0.13", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-animation-state": "2.0.8", + "@chakra-ui/react-use-disclosure": "2.0.8", + "@chakra-ui/react-use-focus-effect": "2.0.10", + "@chakra-ui/react-use-focus-on-pointer-down": "2.0.6", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/popper": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.0.13.tgz", + "integrity": "sha512-FwtmYz80Ju8oK3Z1HQfisUE7JIMmDsCQsRBu6XuJ3TFQnBHit73yjZmxKjuRJ4JgyT4WBnZoTF3ATbRKSagBeg==", + "dependencies": { + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@popperjs/core": "^2.9.3" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/portal": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.0.16.tgz", + "integrity": "sha512-bVID0qbQ0l4xq38LdqAN4EKD4/uFkDnXzFwOlviC9sl0dNhzICDb1ltuH/Adl1d2HTMqyN60O3GO58eHy7plnQ==", + "dependencies": { + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-safe-layout-effect": "2.0.5" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/progress": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.1.6.tgz", + "integrity": "sha512-hHh5Ysv4z6bK+j2GJbi/FT9CVyto2PtNUNwBmr3oNMVsoOUMoRjczfXvvYqp0EHr9PCpxqrq7sRwgQXUzhbDSw==", + "dependencies": { + "@chakra-ui/react-context": "2.0.8" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/provider": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-2.2.3.tgz", + "integrity": "sha512-vLvs69tkq3D7sjmGV5ry8c93TKK0K5XfT2hTWr0QRPRvsccDSoEbYtCI8lb36kOZdXhYa/K8nd81vM+UBp0tzw==", + "dependencies": { + "@chakra-ui/css-reset": "2.1.1", + "@chakra-ui/portal": "2.0.16", + "@chakra-ui/react-env": "3.0.0", + "@chakra-ui/system": "2.5.6", + "@chakra-ui/utils": "2.0.15" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/radio": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-2.0.22.tgz", + "integrity": "sha512-GsQ5WAnLwivWl6gPk8P1x+tCcpVakCt5R5T0HumF7DGPXKdJbjS+RaFySrbETmyTJsKY4QrfXn+g8CWVrMjPjw==", + "dependencies": { + "@chakra-ui/form-control": "2.0.18", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5", + "@zag-js/focus-visible": "0.2.2" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.6.0.tgz", + "integrity": "sha512-dhufu/A4O5tQ65p//XGfvUqSrf0qRAgTmFRlBZ7HucyxF5RStQ65FXiTXV0s4Pj0X5hgSJm3oCasV6UD6MXYbw==", + "dependencies": { + "@chakra-ui/accordion": "2.1.11", + "@chakra-ui/alert": "2.1.0", + "@chakra-ui/avatar": "2.2.9", + "@chakra-ui/breadcrumb": "2.1.5", + "@chakra-ui/button": "2.0.18", + "@chakra-ui/card": "2.1.6", + "@chakra-ui/checkbox": "2.2.15", + "@chakra-ui/close-button": "2.0.17", + "@chakra-ui/control-box": "2.0.13", + "@chakra-ui/counter": "2.0.14", + "@chakra-ui/css-reset": "2.1.1", + "@chakra-ui/editable": "3.0.0", + "@chakra-ui/focus-lock": "2.0.16", + "@chakra-ui/form-control": "2.0.18", + "@chakra-ui/hooks": "2.2.0", + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/image": "2.0.16", + "@chakra-ui/input": "2.0.22", + "@chakra-ui/layout": "2.1.19", + "@chakra-ui/live-region": "2.0.13", + "@chakra-ui/media-query": "3.2.12", + "@chakra-ui/menu": "2.1.13", + "@chakra-ui/modal": "2.2.11", + "@chakra-ui/number-input": "2.0.19", + "@chakra-ui/pin-input": "2.0.20", + "@chakra-ui/popover": "2.1.10", + "@chakra-ui/popper": "3.0.13", + "@chakra-ui/portal": "2.0.16", + "@chakra-ui/progress": "2.1.6", + "@chakra-ui/provider": "2.2.3", + "@chakra-ui/radio": "2.0.22", + "@chakra-ui/react-env": "3.0.0", + "@chakra-ui/select": "2.0.19", + "@chakra-ui/skeleton": "2.0.24", + "@chakra-ui/slider": "2.0.23", + "@chakra-ui/spinner": "2.0.13", + "@chakra-ui/stat": "2.0.18", + "@chakra-ui/stepper": "2.1.0", + "@chakra-ui/styled-system": "2.9.0", + "@chakra-ui/switch": "2.0.27", + "@chakra-ui/system": "2.5.6", + "@chakra-ui/table": "2.0.17", + "@chakra-ui/tabs": "2.1.9", + "@chakra-ui/tag": "3.0.0", + "@chakra-ui/textarea": "2.0.19", + "@chakra-ui/theme": "3.1.0", + "@chakra-ui/theme-utils": "2.0.16", + "@chakra-ui/toast": "6.1.2", + "@chakra-ui/tooltip": "2.2.7", + "@chakra-ui/transition": "2.0.16", + "@chakra-ui/utils": "2.0.15", + "@chakra-ui/visually-hidden": "2.0.15" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/react-children-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-children-utils/-/react-children-utils-2.0.6.tgz", + "integrity": "sha512-QVR2RC7QsOsbWwEnq9YduhpqSFnZGvjjGREV8ygKi8ADhXh93C8azLECCUVgRJF2Wc+So1fgxmjLcbZfY2VmBA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-context": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.0.8.tgz", + "integrity": "sha512-tRTKdn6lCTXM6WPjSokAAKCw2ioih7Eg8cNgaYRSwKBck8nkz9YqxgIIEj3dJD7MGtpl24S/SNI98iRWkRwR/A==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-env": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-3.0.0.tgz", + "integrity": "sha512-tfMRO2v508HQWAqSADFrwZgR9oU10qC97oV6zGbjHh9ALP0/IcFR+Bi71KRTveDTm85fMeAzZYGj57P3Dsipkw==", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", + "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-animation-state": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.0.8.tgz", + "integrity": "sha512-xv9zSF2Rd1mHWQ+m5DLBWeh4atF8qrNvsOs3MNrvxKYBS3f79N3pqcQGrWAEvirXWXfiCeje2VAkEggqFRIo+Q==", + "dependencies": { + "@chakra-ui/dom-utils": "2.0.6", + "@chakra-ui/react-use-event-listener": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-callback-ref": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.0.7.tgz", + "integrity": "sha512-YjT76nTpfHAK5NxplAlZsQwNju5KmQExnqsWNPFeOR6vvbC34+iPSTr+r91i1Hdy7gBSbevsOsd5Wm6RN3GuMw==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-controllable-state": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.0.8.tgz", + "integrity": "sha512-F7rdCbLEmRjwwODqWZ3y+mKgSSHPcLQxeUygwk1BkZPXbKkJJKymOIjIynil2cbH7ku3hcSIWRvuhpCcfQWJ7Q==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-disclosure": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.0.8.tgz", + "integrity": "sha512-2ir/mHe1YND40e+FyLHnDsnDsBQPwzKDLzfe9GZri7y31oU83JSbHdlAXAhp3bpjohslwavtRCp+S/zRxfO9aQ==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-event-listener": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.0.7.tgz", + "integrity": "sha512-4wvpx4yudIO3B31pOrXuTHDErawmwiXnvAN7gLEOVREi16+YGNcFnRJ5X5nRrmB7j2MDUtsEDpRBFfw5Z9xQ5g==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-focus-effect": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.0.10.tgz", + "integrity": "sha512-HswfpzjP8gCQM3/fbeR+8wrYqt0B3ChnVTqssnYXqp9Fa/5Y1Kx1ZADUWW93zMs5SF7hIEuNt8uKeh1/3HTcqQ==", + "dependencies": { + "@chakra-ui/dom-utils": "2.0.6", + "@chakra-ui/react-use-event-listener": "2.0.7", + "@chakra-ui/react-use-safe-layout-effect": "2.0.5", + "@chakra-ui/react-use-update-effect": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-focus-on-pointer-down": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.0.6.tgz", + "integrity": "sha512-OigXiLRVySn3tyVqJ/rn57WGuukW8TQe8fJYiLwXbcNyAMuYYounvRxvCy2b53sQ7QIZamza0N0jhirbH5FNoQ==", + "dependencies": { + "@chakra-ui/react-use-event-listener": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-interval": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.0.5.tgz", + "integrity": "sha512-1nbdwMi2K87V6p5f5AseOKif2CkldLaJlq1TOqaPRwb7v3aU9rltBtYdf+fIyuHSToNJUV6wd9budCFdLCl3Fg==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-latest-ref": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.0.5.tgz", + "integrity": "sha512-3mIuFzMyIo3Ok/D8uhV9voVg7KkrYVO/pwVvNPJOHsDQqCA6DpYE4WDsrIx+fVcwad3Ta7SupexR5PoI+kq6QQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-merge-refs": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.0.7.tgz", + "integrity": "sha512-zds4Uhsc+AMzdH8JDDkLVet9baUBgtOjPbhC5r3A0ZXjZvGhCztFAVE3aExYiVoMPoHLKbLcqvCWE6ioFKz1lw==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-outside-click": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.1.0.tgz", + "integrity": "sha512-JanCo4QtWvMl9ZZUpKJKV62RlMWDFdPCE0Q64a7eWTOQgWWcpyBW7TOYRunQTqrK30FqkYFJCOlAWOtn+6Rw7A==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-pan-event": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.0.9.tgz", + "integrity": "sha512-xu35QXkiyrgsHUOnctl+SwNcwf9Rl62uYE5y8soKOZdBm8E+FvZIt2hxUzK1EoekbJCMzEZ0Yv1ZQCssVkSLaQ==", + "dependencies": { + "@chakra-ui/event-utils": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.0.5", + "framesync": "6.1.2" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-previous": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-previous/-/react-use-previous-2.0.5.tgz", + "integrity": "sha512-BIZgjycPE4Xr+MkhKe0h67uHXzQQkBX/u5rYPd65iMGdX1bCkbE0oorZNfOHLKdTmnEb4oVsNvfN6Rfr+Mnbxw==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-safe-layout-effect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.0.5.tgz", + "integrity": "sha512-MwAQBz3VxoeFLaesaSEN87reVNVbjcQBDex2WGexAg6hUB6n4gc1OWYH/iXp4tzp4kuggBNhEHkk9BMYXWfhJQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-size": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-size/-/react-use-size-2.0.10.tgz", + "integrity": "sha512-fdIkH14GDnKQrtQfxX8N3gxbXRPXEl67Y3zeD9z4bKKcQUAYIMqs0MsPZY+FMpGQw8QqafM44nXfL038aIrC5w==", + "dependencies": { + "@zag-js/element-size": "0.3.2" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-timeout": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-timeout/-/react-use-timeout-2.0.5.tgz", + "integrity": "sha512-QqmB+jVphh3h/CS60PieorpY7UqSPkrQCB7f7F+i9vwwIjtP8fxVHMmkb64K7VlzQiMPzv12nlID5dqkzlv0mw==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.0.7" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-update-effect": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.0.7.tgz", + "integrity": "sha512-vBM2bmmM83ZdDtasWv3PXPznpTUd+FvqBC8J8rxoRmvdMEfrxTiQRBJhiGHLpS9BPLLPQlosN6KdFU97csB6zg==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", + "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", + "dependencies": { + "@chakra-ui/utils": "2.0.15" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/select": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.0.19.tgz", + "integrity": "sha512-eAlFh+JhwtJ17OrB6fO6gEAGOMH18ERNrXLqWbYLrs674Le7xuREgtuAYDoxUzvYXYYTTdOJtVbcHGriI3o6rA==", + "dependencies": { + "@chakra-ui/form-control": "2.0.18", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==" + }, + "node_modules/@chakra-ui/skeleton": { + "version": "2.0.24", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.0.24.tgz", + "integrity": "sha512-1jXtVKcl/jpbrJlc/TyMsFyI651GTXY5ma30kWyTXoby2E+cxbV6OR8GB/NMZdGxbQBax8/VdtYVjI0n+OBqWA==", + "dependencies": { + "@chakra-ui/media-query": "3.2.12", + "@chakra-ui/react-use-previous": "2.0.5", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/slider": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-2.0.23.tgz", + "integrity": "sha512-/eyRUXLla+ZdBUPXpakE3SAS2JS8mIJR6qcUYiPVKSpRAi6tMyYeQijAXn2QC1AUVd2JrG8Pz+1Jy7Po3uA7cA==", + "dependencies": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.0.7", + "@chakra-ui/react-use-controllable-state": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.0.5", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/react-use-pan-event": "2.0.9", + "@chakra-ui/react-use-size": "2.0.10", + "@chakra-ui/react-use-update-effect": "2.0.7" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/spinner": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-2.0.13.tgz", + "integrity": "sha512-T1/aSkVpUIuiYyrjfn1+LsQEG7Onbi1UE9ccS/evgf61Dzy4GgTXQUnDuWFSgpV58owqirqOu6jn/9eCwDlzlg==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stat": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.0.18.tgz", + "integrity": "sha512-wKyfBqhVlIs9bkSerUc6F9KJMw0yTIEKArW7dejWwzToCLPr47u+CtYO6jlJHV6lRvkhi4K4Qc6pyvtJxZ3VpA==", + "dependencies": { + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stepper": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/stepper/-/stepper-2.1.0.tgz", + "integrity": "sha512-Xo/3U+nduhLWNUAAQ0XuIeJjXhSCrxyEJ0PSGwR+2I8PJq82GDIxXjvfpeDLCHoB225l3Wyuy4paeIHkUQhDxA==", + "dependencies": { + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/styled-system": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.0.tgz", + "integrity": "sha512-rToN30eOezrTZ5qBHmWqEwsYPenHtc3WU6ODAfMUwNnmCJQiu2erRGv8JwIjmRJnKSOEnNKccI2UXe2EwI6+JA==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.0.11", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/switch": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-2.0.27.tgz", + "integrity": "sha512-z76y2fxwMlvRBrC5W8xsZvo3gP+zAEbT3Nqy5P8uh/IPd5OvDsGeac90t5cgnQTyxMOpznUNNK+1eUZqtLxWnQ==", + "dependencies": { + "@chakra-ui/checkbox": "2.2.15", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/system": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.5.6.tgz", + "integrity": "sha512-sKzonHUbjOnRxZvcysN8pqa3y0OkTb9xWPhNFnvye/Km8vZhw4SfHKbVpRXedMPVp5Q3PHOxqAXOs6Q0kpo6KA==", + "dependencies": { + "@chakra-ui/color-mode": "2.1.12", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/styled-system": "2.9.0", + "@chakra-ui/theme-utils": "2.0.16", + "@chakra-ui/utils": "2.0.15", + "react-fast-compare": "3.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/table": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-2.0.17.tgz", + "integrity": "sha512-OScheTEp1LOYvTki2NFwnAYvac8siAhW9BI5RKm5f5ORL2gVJo4I72RUqE0aKe1oboxgm7CYt5afT5PS5cG61A==", + "dependencies": { + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tabs": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-2.1.9.tgz", + "integrity": "sha512-Yf8e0kRvaGM6jfkJum0aInQ0U3ZlCafmrYYni2lqjcTtThqu+Yosmo3iYlnullXxCw5MVznfrkb9ySvgQowuYg==", + "dependencies": { + "@chakra-ui/clickable": "2.0.14", + "@chakra-ui/descendant": "3.0.14", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-controllable-state": "2.0.8", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/react-use-safe-layout-effect": "2.0.5", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-3.0.0.tgz", + "integrity": "sha512-YWdMmw/1OWRwNkG9pX+wVtZio+B89odaPj6XeMn5nfNN8+jyhIEpouWv34+CO9G0m1lupJTxPSfgLAd7cqXZMA==", + "dependencies": { + "@chakra-ui/icon": "3.0.16", + "@chakra-ui/react-context": "2.0.8" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/textarea": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-2.0.19.tgz", + "integrity": "sha512-adJk+qVGsFeJDvfn56CcJKKse8k7oMGlODrmpnpTdF+xvlsiTM+1GfaJvgNSpHHuQFdz/A0z1uJtfGefk0G2ZA==", + "dependencies": { + "@chakra-ui/form-control": "2.0.18", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/theme": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.1.0.tgz", + "integrity": "sha512-lO2p37lyEGVmGUrr+lakHpnvrJHkkfPnSM+w9MGmR0V0rqIGTIBrirBO07vDccNRS17jcXjA8d9QZEBYzIVyNw==", + "dependencies": { + "@chakra-ui/anatomy": "2.1.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.0.17" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.8.0" + } + }, + "node_modules/@chakra-ui/theme-tools": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.0.17.tgz", + "integrity": "sha512-Auu38hnihlJZQcPok6itRDBbwof3TpXGYtDPnOvrq4Xp7jnab36HLt7KEXSDPXbtOk3ZqU99pvI1en5LbDrdjg==", + "dependencies": { + "@chakra-ui/anatomy": "2.1.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.0" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.0.0" + } + }, + "node_modules/@chakra-ui/theme-utils": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.16.tgz", + "integrity": "sha512-xVrQ8YEhIX51PB27kbEGHoQ3G78erSykqOeIPkoxaEfWBV4Ba83o7RwEZG8/Qa7c7S4qYPmCSGynegBWrsQpHA==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.0", + "@chakra-ui/theme": "3.1.0", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/toast": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-6.1.2.tgz", + "integrity": "sha512-hKSv6tX0zgZIZDMpIzs0kZM56sYrD5lvlLQ5JfERLi0KTSTeP+vbYh4+Vg3GTXPCn1JBF7mZRX0gU22WEMfJ8A==", + "dependencies": { + "@chakra-ui/alert": "2.1.0", + "@chakra-ui/close-button": "2.0.17", + "@chakra-ui/portal": "2.0.16", + "@chakra-ui/react-context": "2.0.8", + "@chakra-ui/react-use-timeout": "2.0.5", + "@chakra-ui/react-use-update-effect": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.0", + "@chakra-ui/theme": "3.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": "2.5.6", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/tooltip": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-2.2.7.tgz", + "integrity": "sha512-ImUJ6NnVqARaYqpgtO+kzucDRmxo8AF3jMjARw0bx2LxUkKwgRCOEaaRK5p5dHc0Kr6t5/XqjDeUNa19/sLauA==", + "dependencies": { + "@chakra-ui/popper": "3.0.13", + "@chakra-ui/portal": "2.0.16", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-disclosure": "2.0.8", + "@chakra-ui/react-use-event-listener": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.0.7", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/transition": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-2.0.16.tgz", + "integrity": "sha512-E+RkwlPc3H7P1crEXmXwDXMB2lqY2LLia2P5siQ4IEnRWIgZXlIw+8Em+NtHNgusel2N+9yuB0wT9SeZZeZ3CQ==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/visually-hidden": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-2.0.15.tgz", + "integrity": "sha512-WWULIiucYRBIewHKFA7BssQ2ABLHLVd9lrUo3N3SZgR0u4ZRDDVEUNOy+r+9ruDze8+36dGbN9wsN1IdELtdOw==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.10.6", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", + "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/serialize": "^1.1.1", + "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.1.3" + } + }, + "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/babel-plugin/node_modules/stylis": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", + "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + }, + "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.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", + "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "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.10.6", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", + "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.6", + "@emotion/cache": "^11.10.5", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "dependencies": { + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/unitless": "^0.8.0", + "@emotion/utils": "^1.2.0", + "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.10.6", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz", + "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.6", + "@emotion/is-prop-valid": "^1.2.0", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "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/android-arm": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.13.tgz", + "integrity": "sha512-5tZZ/hLIfBmt7E8JsE5KbsknoAFmoElkg+A/gjyPtmSQvJjPf+9GsSJihid8VMa08lrsYyaEXOT9RLh3xXQONw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.13.tgz", + "integrity": "sha512-F5DgvJMV2ZEpLNpPCO7FEk1wy8O5tg6cikWSB6uvvncsgE1xgbPlm+Boio/4820C2/mj713X83X1h01v0qoeHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.13.tgz", + "integrity": "sha512-5m1UUslzpfVrumG3m3Zv2x9VNAcvMOQWJy009y6jt10tcHpzIq2/b0I0k4fz0QYqGSNS1GteRIhVPN4H7OyCXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.13.tgz", + "integrity": "sha512-TXbXp/05r7heRsG8yWwbHw9diay+wXIyRNcIHFoNARRIGahYbTW/qwJzE37zkfxLIUPHgR/SyLTUlnTICg14ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.13.tgz", + "integrity": "sha512-Ku9Db2sblCxFvQdEO7X9nBaLR/S81uch81e2Q2+Os5z1NcnsFjuqhIYH0Gm6KNNpIKaEbC7gCLbiIPbLLMX4Pg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.13.tgz", + "integrity": "sha512-t1T5/nIf2j+FdSf1Fa3dcU0cXycr0nK4xJe52qjWa+1I249mM5NBY1ODjiabZxZ0x3CG05y4fd9bxfDLy9kQtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.13.tgz", + "integrity": "sha512-/zbkgEO4gY2qGZr9UNAGI38w/FwUY4bx4EC88k9VeiCKNr3ukNgwH/oIgB5Z9/OqpkNLlcS4w9e2d/MIiy5fbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.13.tgz", + "integrity": "sha512-RrhjzrCF6aCDH248nUAQoldnRmN7nHMxv85GOj5AH+qkxxYvcig7fnUmgANngntRu4btXhN9WKHMgQ5seERDMw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.13.tgz", + "integrity": "sha512-siu3QZrQ7eGrSttvFaRKyjT7kNRbUuHEKzCCyqRh19MbpGokGY13jbIsBEjx6JmH3T50hds325oweS9Ey2ihAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.13.tgz", + "integrity": "sha512-ADHA1PqP5gIegehVP0RvxMmNPxpLgetI8QCwYOjUheGXKIKWSdUN8ZS3rusQv3NGZmFCpYdMZzFoI0QtzzGAdw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.13.tgz", + "integrity": "sha512-n1JQPxETmR0brkpWlJHeohReEPLH+m00bnJdNnFyHN3zLBt1QypevuZSmnmFWsC+7r7HTwWILj3lBDjtPH3ydg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.13.tgz", + "integrity": "sha512-d0pnD/j5KKQ43xtSIvOD+wNIy6D/Vh9GbXVRa3u4zCyiJMYWjxkPkbBzlEgNjdDmUM+5gBFen9k7B8Xscy+Myg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.13.tgz", + "integrity": "sha512-C9sMpa/VcGLjVtsT01sXtzZNS7bAZ+icUclkKkiUwBQ9hzT+J+/Xpj+EykI5hB3KgtxQVo4XUahanFoZNxbQ1g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.13.tgz", + "integrity": "sha512-jYkc5EpNpvjccAHNYekiAtklusVGWftR0VVLtng7dJzDyy+5adAsf1fOG3LllP0WALxS55/w6boLE/728J/bXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.13.tgz", + "integrity": "sha512-4jAJI5O6E/hATL4lsrG2A+noDjZ377KlATVFKwV3SWaNHj+OvoXe/T84ScQIXEtPI7ndJyLkMYruXj8RR5Ilyw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.13.tgz", + "integrity": "sha512-F8PXDeT+3eQpPjf4bmNJapPLu0SKKlWRGPQvBQqVS+YDGoMKnyyYp2UENLFMV8zT7kS39zKxZRZvUL3fMz/7Ww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.13.tgz", + "integrity": "sha512-9jWfzbFCnIZdHjNs+00KQHArUbp7kjQDNmiuqkwGOQFs67m4/dKNupBv2DP5hTqVlQY4tW4RG3qpb6Y3zOHJeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.13.tgz", + "integrity": "sha512-ALbOMlTIBkAVi6KqYjONa7u2oH95RN7OpetFqMtjufFLBiSaayRuwUzhs2yuR9CfGT4qi0jv6HQDav+EG314TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.13.tgz", + "integrity": "sha512-FJBLYL4PkrZGeuHzEqme+0DjNetxkJ+XbB+Aoeow7aQ53JCwsA0/mo8sS5aPkDHgCnMkN4A5GLoFTlDj3BKDrQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.13.tgz", + "integrity": "sha512-Qrvst9RkLz4qgi3hqswNliYuKW92/HGJnd7xLWkGaGPa8S4qsONf81FW0ebDc5iUHb0I7QJwQATutvghTabnFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.13.tgz", + "integrity": "sha512-pZ/NIgz861XaUPlIkPFjP55nJ4PJa0o/CD4zgeRb1Q9FVE+8GvdB6ifJcK05jRhny5hKExhnRFIdgHmmCYH8vg==", + "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.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", + "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.1", + "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.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", + "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz", + "integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==" + }, + "node_modules/@floating-ui/dom": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.7.tgz", + "integrity": "sha512-DyqylONj1ZaBnzj+uBnVfzdjjCkFCL2aA9ESHLyUOGSqb03RpbLMImP1ekIQXYs4KLk9jAjJfZAU8hXfWSahEg==", + "dependencies": { + "@floating-ui/core": "^1.2.6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.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": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@ianvs/prettier-plugin-sort-imports": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@ianvs/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-3.7.2.tgz", + "integrity": "sha512-bVckKToJM8XV2wTOG1VpeXrSmfAG49esVrikbxeFbY51RJdNke9AdMANJtGuACB59uo+pGlz0wBdWFrRzWyO1A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.17.7", + "@babel/generator": "^7.17.7", + "@babel/parser": "^7.17.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "javascript-natural-sort": "0.7.1", + "lodash.clone": "^4.5.0", + "lodash.isequal": "^4.5.0" + }, + "peerDependencies": { + "@vue/compiler-sfc": ">=3.0.0", + "prettier": "2.x" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/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/@jest/console/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/@jest/console/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/@jest/console/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/@jest/console/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/@jest/console/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/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/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/@jest/core/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/@jest/core/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/@jest/core/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/@jest/core/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/@jest/core/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/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "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==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "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==", + "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==", + "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==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", + "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", + "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==", + "dev": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "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.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.5.tgz", + "integrity": "sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", + "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" + }, + "node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "18.15.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", + "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.0.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.29.tgz", + "integrity": "sha512-wXHktgUABxplw1+UnljseDq4+uztQyp2tlWZRIxHlpchsCFqiYkvaDS8JR7eKOQm8wziTH/el5qL7D6gYNkYcw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.0.tgz", + "integrity": "sha512-itag0qpN6q2UMM6Xgk6xoHa0D0/P+M17THnr4SVgqn9Rgam5k/He33MA7/D7QoJcdMxHFyX7U9imaBonAX/6qA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.57.0", + "@typescript-eslint/type-utils": "5.57.0", + "@typescript-eslint/utils": "5.57.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.57.0.tgz", + "integrity": "sha512-0RnrwGQ7MmgtOSnzB/rSGYr2iXENi6L+CtPzX3g5ovo0HlruLukSEKcc4s+q0IEc+DLTDc7Edan0Y4WSQ/bFhw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.57.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.0.tgz", + "integrity": "sha512-orrduvpWYkgLCyAdNtR1QIWovcNZlEm6yL8nwH/eTxWLd8gsP+25pdLHYzL2QdkqrieaDwLpytHqycncv0woUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.57.0", + "@typescript-eslint/types": "5.57.0", + "@typescript-eslint/typescript-estree": "5.57.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.57.0.tgz", + "integrity": "sha512-NANBNOQvllPlizl9LatX8+MHi7bx7WGIWYjPHDmQe5Si/0YEYfxSljJpoTyTWFTgRy3X8gLYSE4xQ2U+aCozSw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.57.0", + "@typescript-eslint/visitor-keys": "5.57.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.57.0.tgz", + "integrity": "sha512-kxXoq9zOTbvqzLbdNKy1yFrxLC6GDJFE2Yuo3KqSwTmDOFjUGeWSakgoXT864WcK5/NAJkkONCiKb1ddsqhLXQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.57.0", + "@typescript-eslint/utils": "5.57.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.57.0.tgz", + "integrity": "sha512-mxsod+aZRSyLT+jiqHw1KK6xrANm19/+VFALVFP5qa/aiJnlP38qpyaTd0fEKhWvQk6YeNZ5LGwI1pDpBRBhtQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.57.0.tgz", + "integrity": "sha512-LTzQ23TV82KpO8HPnWuxM2V7ieXW8O142I7hQTxWIHDcCEIjtkat6H96PFkYBQqGFLW/G/eVVOB9Z8rcvdY/Vw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.57.0", + "@typescript-eslint/visitor-keys": "5.57.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.57.0.tgz", + "integrity": "sha512-ps/4WohXV7C+LTSgAL5CApxvxbMkl9B9AUZRtnEFonpIxZDIT7wC1xfvuJONMidrkB9scs4zhtRyIwHh4+18kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.57.0", + "@typescript-eslint/types": "5.57.0", + "@typescript-eslint/typescript-estree": "5.57.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.57.0.tgz", + "integrity": "sha512-ery2g3k0hv5BLiKpPuwYt9KBkAp2ugT6VvyShXdLOkax895EC55sP0Tx5L0fZaQueiK3fBLvHVvEl3jFS5ia+g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.57.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz", + "integrity": "sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.20.12", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.27.0", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.1.0-beta.0" + } + }, + "node_modules/@zag-js/element-size": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.3.2.tgz", + "integrity": "sha512-bVvvigUGvAuj7PCkE5AbzvTJDTw5f3bg9nQdv+ErhVN8SfPPppLJEmmWdxqsRzrHXgx8ypJt/+Ty0kjtISVDsQ==" + }, + "node_modules/@zag-js/focus-visible": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.2.2.tgz", + "integrity": "sha512-0j2gZq8HiZ51z4zNnSkF1iSkqlwRDvdH+son3wHdoz+7IUdMN/5Exd4TxMJ+gq2Of1DiXReYLL9qqh2PdQ4wgA==" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "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/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.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-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==", + "dev": true, + "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==", + "dev": true, + "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==", + "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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "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/aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "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-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "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.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "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.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "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/axe-core": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", + "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "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==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "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/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001469", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz", + "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "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/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "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/color2k": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz", + "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "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/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js-compat": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.1.tgz", + "integrity": "sha512-QmchCua884D8wWskMX8tW5ydINzd8oSJVx38lx/pVkFGqztxt73GYre3pm/hyYq8bPf+MW5In4I/uRShFDsbrA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "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/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssjanus": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cssjanus/-/cssjanus-2.1.0.tgz", + "integrity": "sha512-kAijbny3GmdOi9k+QT6DGIXqFvL96aksNlGr4Rhk9qXDZYWUojU4bRc3IHWxdaLNOqgEZHuXoe5Wl2l7dxLW5g==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "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/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.340.tgz", + "integrity": "sha512-zx8hqumOqltKsv/MF50yvdAlPF9S/4PXbyfzJS6ZGhbddGkRegdwImmfSVqCkEziYzrIGZ/TlrzBND4FysfkDg==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "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.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "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.17.13", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.13.tgz", + "integrity": "sha512-4ixMwdErBcQHgTBeoxnowENCPKWFAGxgTyKHMK8gqn9sZaC7ZNWFKtim16g2rzQ2b/FYyy3lIUUJboFtjolhqg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.13", + "@esbuild/android-arm64": "0.17.13", + "@esbuild/android-x64": "0.17.13", + "@esbuild/darwin-arm64": "0.17.13", + "@esbuild/darwin-x64": "0.17.13", + "@esbuild/freebsd-arm64": "0.17.13", + "@esbuild/freebsd-x64": "0.17.13", + "@esbuild/linux-arm": "0.17.13", + "@esbuild/linux-arm64": "0.17.13", + "@esbuild/linux-ia32": "0.17.13", + "@esbuild/linux-loong64": "0.17.13", + "@esbuild/linux-mips64el": "0.17.13", + "@esbuild/linux-ppc64": "0.17.13", + "@esbuild/linux-riscv64": "0.17.13", + "@esbuild/linux-s390x": "0.17.13", + "@esbuild/linux-x64": "0.17.13", + "@esbuild/netbsd-x64": "0.17.13", + "@esbuild/openbsd-x64": "0.17.13", + "@esbuild/sunos-x64": "0.17.13", + "@esbuild/win32-arm64": "0.17.13", + "@esbuild/win32-ia32": "0.17.13", + "@esbuild/win32-x64": "0.17.13" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.17.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.13.tgz", + "integrity": "sha512-eFLQhJq98qijGRcv9je/9M4Mz1suZ+pOtj62ArsLd0gubNGhhQDz6T30X2X3f1KZ8lkKkr+zN5vtZzx1GAMoFw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "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/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", + "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.38.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "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.1.1", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.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", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "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.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "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-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/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-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "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.0", + "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/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-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.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/eslint-plugin-testing-library": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.10.2.tgz", + "integrity": "sha512-f1DmDWcz5SDM+IpCkEX0lbFqrrTs8HRsEElzDEqN/EBI0hpRj8Cns5+IVANXswE8/LeybIJqPAOQIFu2j5Y5sw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.43.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "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/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/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "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.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "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.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "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/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/focus-lock": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.11.6.tgz", + "integrity": "sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg==", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": 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/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/framer-motion": { + "version": "10.12.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.12.4.tgz", + "integrity": "sha512-9gLtv8T6dui0tujHROR+VM3kdJyKiFCFiD94IQE+0OuX6LaIyXtdVpviokVdrHSb1giWhmmX4yzoucALMx6mtw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, + "node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "dependencies": { + "tslib": "2.4.0" + } + }, + "node_modules/framesync/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "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==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "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-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "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==", + "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/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/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "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/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "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/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "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==", + "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==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "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-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-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-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dependencies": { + "has": "^1.0.3" + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "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": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "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-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "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-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "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/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/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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/istanbul-lib-report/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/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/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/jest-circus/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/jest-circus/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/jest-circus/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/jest-circus/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/jest-circus/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/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/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/jest-cli/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/jest-cli/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/jest-cli/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/jest-cli/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/jest-cli/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/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/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/jest-config/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/jest-config/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/jest-config/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/jest-config/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/jest-config/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/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/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/jest-diff/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/jest-diff/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/jest-diff/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/jest-diff/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/jest-diff/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/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/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/jest-each/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/jest-each/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/jest-each/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/jest-each/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/jest-each/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/jest-environment-jsdom": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz", + "integrity": "sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/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/jest-matcher-utils/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/jest-matcher-utils/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/jest-matcher-utils/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/jest-matcher-utils/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/jest-matcher-utils/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/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/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/jest-resolve/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/jest-resolve/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/jest-resolve/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/jest-resolve/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/jest-resolve/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/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/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/jest-runner/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/jest-runner/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/jest-runner/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/jest-runner/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/jest-runner/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/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/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/jest-runtime/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/jest-runtime/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/jest-runtime/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/jest-runtime/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/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/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/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/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/jest-snapshot/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/jest-snapshot/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/jest-snapshot/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/jest-snapshot/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/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/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/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/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/jest-util/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/jest-util/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/jest-util/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/jest-util/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/jest-util/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/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/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/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/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/jest-validate/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/jest-validate/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/jest-validate/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/jest-validate/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/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/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/jest-watcher/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/jest-watcher/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/jest-watcher/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/jest-watcher/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/jest-watcher/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/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/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/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "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/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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-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.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "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": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, + "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/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "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/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "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/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz", + "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==", + "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-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "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.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "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.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "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/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "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==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz", + "integrity": "sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "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/prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.7.tgz", + "integrity": "sha512-jQopIOgjLpX+y8HeD56XZw7onupRTC0cw7eKKUimI7vhjkPF5/1ltW5LyqaPtSyc8HvEpvNZsvvsGFa2qpa59w==", + "dev": true, + "engines": { + "node": ">=12.17.0" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-php": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@shufo/prettier-plugin-blade": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "prettier": ">=2.2.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*", + "prettier-plugin-twig-melody": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-php": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@shufo/prettier-plugin-blade": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "prettier-plugin-twig-melody": { + "optional": true + } + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/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==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "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==", + "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/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "dependencies": { + "@babel/runtime": "^7.12.13" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.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-fast-compare": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.1.tgz", + "integrity": "sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==" + }, + "node_modules/react-focus-lock": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.4.tgz", + "integrity": "sha512-7pEdXyMseqm3kVjhdVH18sovparAzLg5h6WvIx7/Ck3ekjhrrDMEegHSa3swwC8wgfdd7DIdUVRGeiHT9/7Sgg==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^0.11.6", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" + }, + "node_modules/react-icons": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", + "peerDependencies": { + "react": "*" + } + }, + "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-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", + "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "dependencies": { + "@remix-run/router": "1.4.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", + "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "dependencies": { + "@remix-run/router": "1.4.0", + "react-router": "6.9.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-select": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.3.tgz", + "integrity": "sha512-z8i3NCuFFWL3w27xq92rBkVI2onT0jzIIPe480HlBjXJ3b5o6Q+Clp4ydyeKrj9DZZ3lrjawwLC5NGl0FSvUDg==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-star-ratings": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-star-ratings/-/react-star-ratings-2.3.0.tgz", + "integrity": "sha512-34Z/oFNDRRn4ZcX7F3t9ccnpo7SQ32gD/vsusQOBc6B6vlqaGR6tke1/Yx3jTDjemKRSmXqhKgpPTR7/JAXq6A==", + "dependencies": { + "classnames": "^2.2.1", + "prop-types": "^15.6.0", + "react": "^16.1.0" + } + }, + "node_modules/react-star-ratings/node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.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-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "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": "3.20.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.2.tgz", + "integrity": "sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "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==", + "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-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/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/sass": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.60.0.tgz", + "integrity": "sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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==", + "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/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==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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==", + "dev": true + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "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-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/stylis-plugin-rtl": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/stylis-plugin-rtl/-/stylis-plugin-rtl-2.1.1.tgz", + "integrity": "sha512-q6xIkri6fBufIO/sV55md2CbgS5c6gg9EhSVATtHHCdOnbN/jcI0u3lYhNVeuI65c4lQPo67g8xmq5jrREvzlg==", + "dependencies": { + "cssjanus": "^2.0.1" + }, + "peerDependencies": { + "stylis": "4.x" + } + }, + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/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==", + "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/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/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==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "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/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "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-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "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/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "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/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.21", + "resolve": "^1.22.1", + "rollup": "^3.18.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-eslint": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz", + "integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.2.1", + "@types/eslint": "^8.4.5", + "rollup": "^2.77.2" + }, + "peerDependencies": { + "eslint": ">=7", + "vite": ">=2" + } + }, + "node_modules/vite-plugin-eslint/node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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-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.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "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/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..e21425b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,60 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "start": "node server.js", + "develop": "node server.js & vite build", + "dev": "vite", + "lint": "eslint 'src/**/*.{js,jsx}'", + "build": "npm run lint && vite build --mode prod", + "preview": "vite preview", + "test": "jest" + }, + "dependencies": { + "@chakra-ui/react": "^2.6.0", + "@emotion/cache": "^11.11.0", + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "axios": "^1.3.4", + "compression": "^1.7.4", + "diff-match-patch": "^1.0.5", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "framer-motion": "^10.12.4", + "jwt-decode": "^3.1.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-ga4": "^2.1.0", + "react-icons": "^4.8.0", + "react-router-dom": "^6.9.0", + "react-select": "^5.7.3", + "react-star-ratings": "^2.3.0", + "recoil": "^0.7.7", + "stylis": "^4.2.0", + "stylis-plugin-rtl": "^2.1.1", + "tailwindcss": "^3.3.1" + }, + "devDependencies": { + "@babel/core": "^7.21.8", + "@babel/preset-env": "^7.21.5", + "@ianvs/prettier-plugin-sort-imports": "^3.7.2", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^3.1.0", + "autoprefixer": "^10.4.14", + "babel-jest": "^29.5.0", + "eslint": "^8.38.0", + "eslint-config-prettier": "^8.8.0", + "eslint-config-react-app": "^7.0.1", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "prettier": "^2.8.7", + "prettier-plugin-tailwindcss": "^0.2.7", + "sass": "^1.60.0", + "vite": "^4.2.0", + "vite-plugin-eslint": "^1.8.1" + } +} diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/public/JCA616_Aya_logo_White_AYA_logo.png b/frontend/public/JCA616_Aya_logo_White_AYA_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a071c0d3160b9898825147db0332fa3cb2f76943 GIT binary patch literal 45226 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

ngX9sGV_zJs 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 z%0D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 + + + + + + + diff --git a/frontend/public/forai.png b/frontend/public/forai.png new file mode 100644 index 0000000000000000000000000000000000000000..0861bbebf7989f9fc890b5383b41d1e362f063e0 GIT binary patch literal 3991 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 f#_K#0mvkRelctgY0lpdqru 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

C8Gwqu{R2wxHT)O7ULnx72h5Tg8S8R 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 + \ No newline at end of file diff --git a/frontend/routes.js b/frontend/routes.js new file mode 100644 index 0000000..18dd5b1 --- /dev/null +++ b/frontend/routes.js @@ -0,0 +1,11 @@ +// routes.js +import express from "express"; + +const router = express.Router(); + +// An endpoint to get the BACKEND_URL +router.get("/backend-url", (req, res) => { + res.send({ backendUrl: process.env.BACKEND_URL }); +}); + +export default router; diff --git a/frontend/server.js b/frontend/server.js new file mode 100644 index 0000000..f8c3f52 --- /dev/null +++ b/frontend/server.js @@ -0,0 +1,41 @@ +import path from "path"; +import { fileURLToPath } from "url"; +import compression from "compression"; +import dotenv from "dotenv"; +import express from "express"; + +import routes from "./routes.js"; + +dotenv.config(); + +// Get current file's path and directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Create an Express app instance +const app = express(); + +// Use compression middleware +app.use(compression()); + +// Serve the static files from the React app +app.use(express.static(path.join(__dirname, "dist"))); + +// Add routes +app.use(routes); + +// Handles any requests that don't match the ones above +app.get("*", (req, res) => { + res.sendFile(path.join(__dirname + "/dist/index.html")); +}); + +// Set the app's listening port +const port = process.env.PORT || 80; +app.listen(port); + +// Log information +console.log("App is listening on port " + port); +console.log("ENVIRONMENT: " + process.env.ENVIRONMENT); +console.log("PORT: " + process.env.PORT); +console.log("BACKEND_URL: " + process.env.BACKEND_URL); +console.log("GOOGLE_ANALYTICS_ID: " + process.env.GOOGLE_ANALYTICS_ID); diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..8471c52 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,54 @@ +import React, { Suspense } from "react"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; + +import AuthHandler from "./components/AuthHandler.jsx"; +import PrivateRoute from "./components/PrivateRoute.jsx"; +import Error401 from "./pages/Error401.jsx"; +import GradioAnalytics from "./pages/GradioAnalytics.jsx"; +import Settings from "./pages/Settings.jsx"; +import TeamPage from "./pages/TeamPage.jsx"; +import UserContributionPage from "./pages/UserContributionPage/index.jsx"; + +const LoginCallback = React.lazy(() => + import("./components/LoginCallback.jsx") +); +const AnalyticsListener = React.lazy(() => + import("./listeners/AnalyticsListener.jsx") +); +const AddDetails = React.lazy(() => import("./pages/AddDetails.jsx")); +const Error404 = React.lazy(() => import("./pages/Error404.jsx")); +const Home = React.lazy(() => import("./pages/Home.jsx")); +const LeaderBoard = React.lazy(() => import("./pages/LeaderBoard.jsx")); +const Workspace = React.lazy(() => import("./pages/Workspace.jsx")); + +const App = () => { + return ( +

}> + + + } /> + } /> + } /> + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + + + + + ); +}; + +export default App; diff --git a/frontend/src/__tests__/helpers/ageGroup.test.js b/frontend/src/__tests__/helpers/ageGroup.test.js new file mode 100644 index 0000000..fb4c6b1 --- /dev/null +++ b/frontend/src/__tests__/helpers/ageGroup.test.js @@ -0,0 +1,118 @@ +import { + fetchAndCacheAgeGroupOptions, + getAgeGroupOptions, + getCachedAgeGroupOptions, +} from "../../helpers/ageGroup.jsx"; + +const cacheExpirationTime = 12 * 60 * 60 * 1000; // 12 hours in milliseconds + +describe("fetchAndCacheAgeGroupOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should cache age group options data", () => { + const options = [ + { id: [0, 17], name: "Under 17 years old", code: "0-17" }, + { id: [18, 24], name: "18-24 years old", code: "18-24" }, + { id: [25, 34], name: "25-34 years old", code: "25-34" }, + { id: [35, 44], name: "35-44 years old", code: "35-44" }, + { id: [45, 54], name: "45-54 years old", code: "45-54" }, + { id: [55, 64], name: "55-64 years old", code: "55-64" }, + { id: [65, 74], name: "65-74 years old", code: "65-74" }, + { id: [75, 120], name: "75 years or older", code: "75-120" }, + ]; + + fetchAndCacheAgeGroupOptions(); + + const cachedData = localStorage.getItem("ageGroupOptions"); + expect(cachedData).toBeTruthy(); + + const { options: cachedOptions, timestamp } = JSON.parse(cachedData); + expect(cachedOptions).toEqual(options); + expect(typeof timestamp).toBe("number"); + }); +}); + +describe("getCachedAgeGroupOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return null if no data is cached", () => { + const result = getCachedAgeGroupOptions(); + expect(result).toBeNull(); + }); + + it("should return null if cached data has expired", () => { + const timestamp = Date.now() - cacheExpirationTime - 1; + const cachedData = JSON.stringify({ options: [], timestamp }); + localStorage.setItem("ageGroupOptions", cachedData); + + const result = getCachedAgeGroupOptions(); + expect(result).toBeNull(); + }); + + it("should return cached data if it has not expired", () => { + const timestamp = Date.now(); + const options = [ + { id: [0, 17], name: "Under 17 years old", code: "0-17" }, + { id: [18, 24], name: "18-24 years old", code: "18-24" }, + { id: [25, 34], name: "25-34 years old", code: "25-34" }, + { id: [35, 44], name: "35-44 years old", code: "35-44" }, + { id: [45, 54], name: "45-54 years old", code: "45-54" }, + { id: [55, 64], name: "55-64 years old", code: "55-64" }, + { id: [65, 74], name: "65-74 years old", code: "65-74" }, + { id: [75, 120], name: "75 years or older", code: "75-120" }, + ]; + + const cachedData = JSON.stringify({ options, timestamp }); + localStorage.setItem("ageGroupOptions", cachedData); + + const result = getCachedAgeGroupOptions(); + expect(result).toEqual(options); + }); +}); + +describe("getAgeGroupOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return cached data if it exists", () => { + const options = [ + { id: [0, 17], name: "Under 17 years old", code: "0-17" }, + { id: [18, 24], name: "18-24 years old", code: "18-24" }, + { id: [25, 34], name: "25-34 years old", code: "25-34" }, + { id: [35, 44], name: "35-44 years old", code: "35-44" }, + { id: [45, 54], name: "45-54 years old", code: "45-54" }, + { id: [55, 64], name: "55-64 years old", code: "55-64" }, + { id: [65, 74], name: "65-74 years old", code: "65-74" }, + { id: [75, 120], name: "75 years or older", code: "75-120" }, + ]; + + const cachedData = JSON.stringify({ options, timestamp: Date.now() }); + localStorage.setItem("ageGroupOptions", cachedData); + + const result = getAgeGroupOptions(); + expect(result).toEqual(options); + }); + + it("should fetch and cache data if it does not exist", () => { + const options = [ + { id: [0, 17], name: "Under 17 years old", code: "0-17" }, + { id: [18, 24], name: "18-24 years old", code: "18-24" }, + { id: [25, 34], name: "25-34 years old", code: "25-34" }, + { id: [35, 44], name: "35-44 years old", code: "35-44" }, + { id: [45, 54], name: "45-54 years old", code: "45-54" }, + { id: [55, 64], name: "55-64 years old", code: "55-64" }, + { id: [65, 74], name: "65-74 years old", code: "65-74" }, + { id: [75, 120], name: "75 years or older", code: "75-120" }, + ]; + + fetchAndCacheAgeGroupOptions(); + + const result = getAgeGroupOptions(); + expect(result).toEqual(options); + }); +}); diff --git a/frontend/src/__tests__/helpers/auth.test.js b/frontend/src/__tests__/helpers/auth.test.js new file mode 100644 index 0000000..a755dc5 --- /dev/null +++ b/frontend/src/__tests__/helpers/auth.test.js @@ -0,0 +1,95 @@ +import axios from "axios"; + +import { + handleLogin, + handleLogout, + isAuthenticated, +} from "../../helpers/auth.jsx"; +import { getApiUrl } from "../../helpers/config.jsx"; + +jest.mock("axios"); +jest.mock("../../helpers/config.jsx"); + +// Mock window.location +delete window.location; +window.location = { href: "" }; + +describe("auth functions", () => { + beforeEach(() => { + axios.get.mockClear(); + axios.post.mockClear(); + getApiUrl.mockClear(); + window.location.href = ""; + }); + + test("handleLogin", async () => { + const apiUrl = "http://test.api"; + const loginUrl = "http://test.login"; + getApiUrl.mockResolvedValue(apiUrl); + axios.get.mockResolvedValue({ data: { url: loginUrl } }); + + await handleLogin("google"); + + expect(getApiUrl).toHaveBeenCalled(); + expect(axios.get).toHaveBeenCalledWith(`${apiUrl}/auth/google/login`); + expect(window.location.href).toEqual(loginUrl); + }); + + test("handleLogin", async () => { + const apiUrl = "http://test.api"; + const loginUrl = "http://test.login"; + getApiUrl.mockResolvedValue(apiUrl); + axios.get.mockResolvedValue({ data: { url: loginUrl } }); + + await handleLogin("discord"); + + expect(getApiUrl).toHaveBeenCalled(); + expect(axios.get).toHaveBeenCalledWith(`${apiUrl}/auth/discord/login`); + expect(window.location.href).toEqual(loginUrl); + }); + + test("handleLogout", async () => { + const apiUrl = "http://test.api"; + const redirectUrl = "http://test.redirect"; + getApiUrl.mockResolvedValue(apiUrl); + axios.get.mockResolvedValue({ + status: 200, + data: { redirect_url: redirectUrl }, + }); + + localStorage.setItem("user", "mockUser"); + + await handleLogout(); + + expect(getApiUrl).toHaveBeenCalled(); + expect(axios.get).toHaveBeenCalledWith(`${apiUrl}/auth/logout`); + expect(localStorage.getItem("user")).toBeNull(); + expect(window.location.href).toEqual(redirectUrl); + }); + + test("isAuthenticated", async () => { + const apiUrl = "http://test.api"; + const isAuthenticatedResult = true; + getApiUrl.mockResolvedValue(apiUrl); + axios.get.mockResolvedValue({ + data: { + is_authenticated: isAuthenticatedResult, + }, + }); + + localStorage.setItem("auth_provider", "google"); + + const result = await isAuthenticated(); + + expect(getApiUrl).toHaveBeenCalled(); + expect(axios.get).toHaveBeenCalledWith(`${apiUrl}/auth/authenticated`, { + headers: { + Authorization: null, + }, + params: { + auth_provider: "google", + }, + }); + expect(result).toEqual(isAuthenticatedResult); + }); +}); diff --git a/frontend/src/__tests__/helpers/config.test.js b/frontend/src/__tests__/helpers/config.test.js new file mode 100644 index 0000000..5a73a08 --- /dev/null +++ b/frontend/src/__tests__/helpers/config.test.js @@ -0,0 +1,41 @@ +import axios from "axios"; + +import { getApiUrl } from "../../helpers/config.jsx"; + +jest.mock("axios"); + +describe("getApiUrl", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should return the production backend URL when the environment is production", async () => { + const backendUrl = "https://yourproductionurl.com"; + axios.get.mockResolvedValue({ data: { backendUrl } }); + + const apiUrl = await getApiUrl(); + + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith("/backend-url"); + expect(apiUrl).toEqual(`${backendUrl}/api/v1`); + }); + + it("should log an error if retrieving the backend URL fails", async () => { + process.env.ENVIRONMENT = "production"; + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + axios.get.mockRejectedValue(new Error("Error fetching backend URL")); + + await getApiUrl(); + + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith("/backend-url"); + expect(consoleErrorSpy).toHaveBeenCalledTimes(1); + expect(consoleErrorSpy).toHaveBeenCalledWith( + new Error("Error fetching backend URL") + ); + + consoleErrorSpy.mockRestore(); + }); +}); diff --git a/frontend/src/__tests__/helpers/country.test.js b/frontend/src/__tests__/helpers/country.test.js new file mode 100644 index 0000000..4a2b299 --- /dev/null +++ b/frontend/src/__tests__/helpers/country.test.js @@ -0,0 +1,137 @@ +import axios from "axios"; + +import { + fetchAndCacheCountries, + getCachedCountryOptions, + getCountryOptions, +} from "../../helpers/country.jsx"; + +const cacheExpirationTime = 12 * 60 * 60 * 1000; // 12 hours in milliseconds + +jest.mock("axios"); + +describe("fetchAndCacheCountries function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should fetch and cache countries data", async () => { + const responseData = { + options: [ + { id: 1, name: "Country 1" }, + { id: 2, name: "Country 2" }, + ], + }; + + axios.get.mockResolvedValueOnce(process.env.BACKEND_URL); + axios.get.mockResolvedValueOnce({ data: responseData }); + + await fetchAndCacheCountries(); + + const cachedData = localStorage.getItem("countryOptions"); + expect(cachedData).toBeTruthy(); + + const { data, timestamp } = JSON.parse(cachedData); + expect(data).toEqual(responseData.options); + expect(typeof timestamp).toBe("number"); + }); + + it("should return an empty array if an error occurs", async () => { + axios.get.mockRejectedValueOnce(new Error("Error")); + + const result = await fetchAndCacheCountries(); + + expect(result).toEqual([]); + }); +}); + +describe("getCachedCountryOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return null if no data is cached", () => { + const result = getCachedCountryOptions(); + expect(result).toBeNull(); + }); + + it("should return null if cached data has expired", () => { + const timestamp = Date.now() - cacheExpirationTime - 1; + const cachedData = JSON.stringify({ data: [], timestamp }); + localStorage.setItem("countryOptions", cachedData); + + const result = getCachedCountryOptions(); + expect(result).toBeNull(); + }); + + it("should return cached data if it has not expired", () => { + const timestamp = Date.now(); + const responseData = { + options: [ + { id: 1, name: "Country 1" }, + { id: 2, name: "Country 2" }, + ], + }; + const cachedData = JSON.stringify({ + data: responseData.options, + timestamp, + }); + localStorage.setItem("countryOptions", cachedData); + + const result = getCachedCountryOptions(); + expect(result).toEqual(responseData.options); + }); +}); + +describe("getCountryOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return cached data if it exists", async () => { + const responseData = { + options: [ + { id: 1, name: "Country 1" }, + { id: 2, name: "Country 2" }, + ], + }; + const cachedData = JSON.stringify({ + data: responseData.options, + timestamp: Date.now(), + }); + localStorage.setItem("countryOptions", cachedData); + + const result = await getCountryOptions(); + expect(result).toEqual(responseData.options); + }); + + it("should fetch and cache data if it does not exist", async () => { + const responseData = { + options: [ + { id: 1, name: "Country 1" }, + { id: 2, name: "Country 2" }, + ], + }; + + axios.get.mockResolvedValueOnce(process.env.BACKEND_URL); + axios.get.mockResolvedValueOnce({ data: responseData }); + + const result = await getCountryOptions(); + + expect(result).toEqual(responseData.options); + + const cachedData = localStorage.getItem("countryOptions"); + expect(cachedData).toBeTruthy(); + + const { data, timestamp } = JSON.parse(cachedData); + expect(data).toEqual(responseData.options); + expect(typeof timestamp).toBe("number"); + }); + + it("should return an empty array if an error occurs", async () => { + axios.get.mockRejectedValueOnce(new Error("Error")); + const result = await getCountryOptions(); + + expect(result).toEqual([]); + }); +}); diff --git a/frontend/src/__tests__/helpers/gender.test.js b/frontend/src/__tests__/helpers/gender.test.js new file mode 100644 index 0000000..87c479a --- /dev/null +++ b/frontend/src/__tests__/helpers/gender.test.js @@ -0,0 +1,105 @@ +import { + fetchAndCacheGenderOptions, + getCachedGenderOptions, + getGenderOptions, +} from "../../helpers/gender.jsx"; + +const cacheExpirationTime = 12 * 60 * 60 * 1000; // 12 hours in milliseconds + +describe("fetchAndCacheGenderOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should cache gender options data", () => { + const options = [ + { id: "male", name: "Male", code: "001" }, + { id: "female", name: "Female", code: "002" }, + { id: "non-binary", name: "Non-binary", code: "003" }, + { id: "prefer not to say", name: "Prefer not to say", code: "004" }, + { id: "other", name: "Other", code: "005" }, + ]; + + fetchAndCacheGenderOptions(); + + const cachedData = localStorage.getItem("genderOptions"); + expect(cachedData).toBeTruthy(); + + const { options: cachedOptions, timestamp } = JSON.parse(cachedData); + expect(cachedOptions).toEqual(options); + expect(typeof timestamp).toBe("number"); + }); +}); + +describe("getCachedGenderOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return null if no data is cached", () => { + const result = getCachedGenderOptions(); + expect(result).toBeNull(); + }); + + it("should return null if cached data has expired", () => { + const timestamp = Date.now() - cacheExpirationTime - 1; + const cachedData = JSON.stringify({ options: [], timestamp }); + localStorage.setItem("genderOptions", cachedData); + + const result = getCachedGenderOptions(); + expect(result).toBeNull(); + }); + + it("should return cached data if it has not expired", () => { + const timestamp = Date.now(); + const options = [ + { id: "male", name: "Male", code: "001" }, + { id: "female", name: "Female", code: "002" }, + { id: "non-binary", name: "Non-binary", code: "003" }, + { id: "prefer not to say", name: "Prefer not to say", code: "004" }, + { id: "other", name: "Other", code: "005" }, + ]; + + const cachedData = JSON.stringify({ options, timestamp }); + localStorage.setItem("genderOptions", cachedData); + + const result = getCachedGenderOptions(); + expect(result).toEqual(options); + }); +}); + +describe("getGenderOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return cached data if it exists", () => { + const options = [ + { id: "male", name: "Male", code: "001" }, + { id: "female", name: "Female", code: "002" }, + { id: "non-binary", name: "Non-binary", code: "003" }, + { id: "prefer not to say", name: "Prefer not to say", code: "004" }, + { id: "other", name: "Other", code: "005" }, + ]; + + const cachedData = JSON.stringify({ options, timestamp: Date.now() }); + localStorage.setItem("genderOptions", cachedData); + + const result = getGenderOptions(); + expect(result).toEqual(options); + }); + + it("should fetch and cache data if it does not exist", () => { + const options = [ + { id: "male", name: "Male", code: "001" }, + { id: "female", name: "Female", code: "002" }, + { id: "non-binary", name: "Non-binary", code: "003" }, + { id: "prefer not to say", name: "Prefer not to say", code: "004" }, + { id: "other", name: "Other", code: "005" }, + ]; + fetchAndCacheGenderOptions(); + + const result = getGenderOptions(); + expect(result).toEqual(options); + }); +}); diff --git a/frontend/src/__tests__/helpers/language.test.js b/frontend/src/__tests__/helpers/language.test.js new file mode 100644 index 0000000..251092a --- /dev/null +++ b/frontend/src/__tests__/helpers/language.test.js @@ -0,0 +1,180 @@ +import axios from "axios"; + +import { + fetchAndCacheLanguages, + getCachedLanguageOptions, + getLanguageOptions, + getLanguagesByUUIDs, +} from "../../helpers/language.jsx"; + +const cacheExpirationTime = 12 * 60 * 60 * 1000; // 12 hours in milliseconds + +jest.mock("axios"); + +describe("fetchAndCacheLanguages function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should fetch and cache languages data", async () => { + const responseData = { + options: [ + { id: 1, name: "Language 1" }, + { id: 2, name: "Language 2" }, + ], + }; + + axios.get.mockResolvedValueOnce(process.env.BACKEND_URL); + axios.get.mockResolvedValueOnce({ data: responseData }); + + await fetchAndCacheLanguages(); + + const cachedData = localStorage.getItem("languageOptions"); + expect(cachedData).toBeTruthy(); + + const { data, timestamp } = JSON.parse(cachedData); + expect(data).toEqual(responseData.options); + expect(typeof timestamp).toBe("number"); + }); + + it("should return an empty array if an error occurs", async () => { + axios.get.mockRejectedValueOnce(new Error("Error")); + + const result = await fetchAndCacheLanguages(); + + expect(result).toEqual([]); + }); +}); + +describe("getCachedLanguageOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return null if no data is cached", () => { + const result = getCachedLanguageOptions(); + expect(result).toBeNull(); + }); + + it("should return null if cached data has expired", () => { + const timestamp = Date.now() - cacheExpirationTime - 1; + const cachedData = JSON.stringify({ data: [], timestamp }); + localStorage.setItem("languageOptions", cachedData); + + const result = getCachedLanguageOptions(); + expect(result).toBeNull(); + }); + + it("should return cached data if it has not expired", () => { + const timestamp = Date.now(); + const responseData = { + options: [ + { id: 1, name: "Language 1" }, + { id: 2, name: "Language 2" }, + ], + }; + const cachedData = JSON.stringify({ + data: responseData.options, + timestamp, + }); + localStorage.setItem("languageOptions", cachedData); + + const result = getCachedLanguageOptions(); + expect(result).toEqual(responseData.options); + }); +}); + +describe("getLanguageOptions function", () => { + afterEach(() => { + localStorage.clear(); + }); + + it("should return cached data if it exists", async () => { + const responseData = { + options: [ + { id: 1, name: "Language 1" }, + { id: 2, name: "Language 2" }, + ], + }; + const cachedData = JSON.stringify({ + data: responseData.options, + timestamp: Date.now(), + }); + localStorage.setItem("languageOptions", cachedData); + + const result = await getLanguageOptions(); + expect(result).toEqual(responseData.options); + }); + + it("should fetch and cache data if it does not exist", async () => { + const responseData = { + options: [ + { id: 1, name: "Language 1" }, + { id: 2, name: "Language 2" }, + ], + }; + + axios.get.mockResolvedValueOnce(process.env.BACKEND_URL); + axios.get.mockResolvedValueOnce({ data: responseData }); + + const result = await getLanguageOptions(); + + expect(result).toEqual(responseData.options); + + const cachedData = localStorage.getItem("languageOptions"); + expect(cachedData).toBeTruthy(); + + const { data, timestamp } = JSON.parse(cachedData); + expect(data).toEqual(responseData.options); + expect(typeof timestamp).toBe("number"); + }); + + it("should return an empty array if an error occurs", async () => { + axios.get.mockRejectedValueOnce(new Error("Error")); + const result = await getLanguageOptions(); + + expect(result).toEqual([]); + }); +}); + +describe("getLanguagesByUUIDs function", () => { + it("should return an empty array if no language UUIDs are provided", async () => { + const result = await getLanguagesByUUIDs([]); + expect(result).toEqual([]); + }); + + it("should return an empty array if an error occurs", async () => { + const languageUUIDs = [1, 2]; + + jest.spyOn(console, "error"); + + axios.get.mockRejectedValueOnce(new Error("Error")); + + const result = await getLanguagesByUUIDs(languageUUIDs); + + expect(result).toEqual([]); + expect(console.error).toHaveBeenCalled(); + }); + + it("should return the languages in the requested order", async () => { + const allLanguages = [ + { id: 1, name: "Language 1" }, + { id: 2, name: "Language 2" }, + { id: 3, name: "Language 3" }, + ]; + const languageUUIDs = [3, 2, 1]; + + jest.spyOn(console, "error"); + + axios.get.mockResolvedValueOnce(process.env.BACKEND_URL); + axios.get.mockResolvedValueOnce({ data: { options: allLanguages } }); + + const result = await getLanguagesByUUIDs(languageUUIDs); + + expect(result).toEqual([ + { id: 3, name: "Language 3" }, + { id: 2, name: "Language 2" }, + { id: 1, name: "Language 1" }, + ]); + }); +}); diff --git a/frontend/src/components/About.jsx b/frontend/src/components/About.jsx new file mode 100644 index 0000000..eed3ac4 --- /dev/null +++ b/frontend/src/components/About.jsx @@ -0,0 +1,53 @@ +import React from "react"; +import { Box, Container, Text, useBreakpointValue } from "@chakra-ui/react"; + +function About() { + const fontSizeTitle = useBreakpointValue({ base: "3xl", md: "5xl" }); + const fontSizeContent = useBreakpointValue({ base: "md", md: "lg" }); + + return ( + + + + What is Aya? + + + Recent breakthroughs in NLP technology have focused on English, + leaving other languages behind. One of the biggest hurdles to + improving multilingual model performance is access to high-quality + examples of multilingual text. In January 2023 the Cohere For AI + community set out on an ambitious open science research project. +
+
+ With members from over 100 countries around the world, we sought to + leverage the great strength of our diversity to make meaningful + contributions to fundamental machine-learning questions. Our ultimate + goal is to release a high-quality multilingual dataset. In sharing + this artifact broadly, we will support future projects that aim to + build technology for everyone, including those who use any of the + 7,000+ languages spoken around the world. As technological progress + races forward, join us to ensure no language is left behind. +
+
+ What does the word Aya mean? The word Aya has its origins in the Twi + language, and is translated to “fern”. Aya is a symbol of endurance, + resourcefulness and defiance. Similar to our initiatives names, we + believe it is a long term effort of endurance to make sure that no + language is left behind. +
+
+
+ ); +} + +export default About; diff --git a/frontend/src/components/AuthHandler.jsx b/frontend/src/components/AuthHandler.jsx new file mode 100644 index 0000000..5e55a7e --- /dev/null +++ b/frontend/src/components/AuthHandler.jsx @@ -0,0 +1,22 @@ +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; +import { useSetRecoilState } from "recoil"; + +import { isAuthenticated } from "../helpers/auth.jsx"; +import { isAuthenticatedState } from "../recoil/atoms/isAuthenticatedState.jsx"; + +const AuthHandler = () => { + const setIsAuthenticatedState = useSetRecoilState(isAuthenticatedState); + const location = useLocation(); + + useEffect(() => { + (async () => { + const auth = await isAuthenticated(); + setIsAuthenticatedState(auth); + })(); + }, [location, setIsAuthenticatedState]); + + return null; +}; + +export default AuthHandler; diff --git a/frontend/src/components/DialectInput.jsx b/frontend/src/components/DialectInput.jsx new file mode 100644 index 0000000..7d1e71d --- /dev/null +++ b/frontend/src/components/DialectInput.jsx @@ -0,0 +1,38 @@ +import React from "react"; +import CreatableSelect from "react-select/creatable"; + +const NoDropdownIndicator = (props) => { + return null; +}; + +const DialectInput = ({ dialects, setDialects }) => { + const handleChange = (values) => { + const newDialects = values ? values.map((value) => value.label) : []; + setDialects(newDialects); + }; + + const handleMenuOpen = () => false; // Prevent menu from opening + + const formattedDialects = dialects.map((dialect) => ({ + label: dialect, + value: dialect, + })); + + return ( + null, + }} // Remove dropdown indicator + onMenuOpen={handleMenuOpen} + noOptionsMessage={() => "Type to add a dialect"} + /> + ); +}; + +export default DialectInput; diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx new file mode 100644 index 0000000..52fac43 --- /dev/null +++ b/frontend/src/components/Footer.jsx @@ -0,0 +1,58 @@ +import React from "react"; +import { + Box, + Flex, + HStack, + Image, + Link, + Spacer, + Text, + useBreakpointValue, +} from "@chakra-ui/react"; + +const Footer = () => { + const isMobileScreen = useBreakpointValue({ base: true, md: false }); + const boxSize = useBreakpointValue({ base: "1.5rem", md: "2rem" }); + const fontSize = useBreakpointValue({ base: "sm", md: "md", lg: "xl" }); + + return ( + + + + Made by the C4AI Open Science Community. + + {isMobileScreen && } + + + + The Aya Project + + + The Aya Project + + + + {isMobileScreen && } + + + ); +}; + +export default Footer; diff --git a/frontend/src/components/GetDetails.jsx b/frontend/src/components/GetDetails.jsx new file mode 100644 index 0000000..118e22d --- /dev/null +++ b/frontend/src/components/GetDetails.jsx @@ -0,0 +1,228 @@ +import React, { useEffect, useState } from "react"; +import { + Button, + Center, + Container, + Text, + VStack, + useToast, +} from "@chakra-ui/react"; +import axios from "axios"; + +import { getAgeGroupOptions } from "../helpers/ageGroup.jsx"; +import { getApiUrl } from "../helpers/config.jsx"; +import { getCountryOptions } from "../helpers/country.jsx"; +import { + trackButtonClickGA, + trackUserAddDetailsAbandonedGA, +} from "../helpers/ga.jsx"; +import { getGenderOptions } from "../helpers/gender.jsx"; +import { getLanguageOptions } from "../helpers/language.jsx"; +import { updateUserProfile } from "../helpers/user.jsx"; +import DialectInput from "./DialectInput"; +import MultiSelectDropdown from "./MultiSelectDropdown"; +import SingleSelectDropdown from "./SingleSelectDropdown"; + +function GetDetails() { + const toast = useToast(); + + const [languageList, setLanguageList] = useState([]); + const [countryList, setCountryList] = useState([]); + const [selectedCountry, setSelectedCountry] = useState(null); + const [selectedLanguages, setSelectedLanguages] = useState([]); + + const [genderList, setGenderList] = useState([]); + const [ageGroupList, setAgeGroupList] = useState([]); + const [selectedGender, setSelectedGender] = useState(null); + const [selectedAgeBucket, setSelectedAgeBucket] = useState(null); + + const [dialects, setDialects] = useState([]); + + const [currentUserSettings, setCurrentUserSettings] = useState({}); + + const [loading, setLoading] = useState(true); + + const handleBeforeUnload = (event) => { + trackUserAddDetailsAbandonedGA(); + }; + + useEffect(() => { + window.addEventListener("beforeunload", handleBeforeUnload); + + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload); + }; + }, []); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + + const [languages, countries] = await Promise.all([ + getLanguageOptions(), + getCountryOptions(), + ]); + setLanguageList(languages); + setCountryList(countries); + + const genders = getGenderOptions(); + setGenderList(genders); + + const ageGroup = getAgeGroupOptions(); + setAgeGroupList(ageGroup); + + const apiUrl = await getApiUrl(); + const user_id = JSON.parse(localStorage.getItem("user"))["id"]; + const response = await axios.get(`${apiUrl}/users/${user_id}`); + const user = response.data; + setCurrentUserSettings(user); + + const currentCountryCode = user.country_code; + if (currentCountryCode) { + const currentCountryOption = countries.find( + (option) => option.id === currentCountryCode + ); + setSelectedCountry(currentCountryOption); + } + + const currentLanguageCodes = user.language_codes; + if (currentLanguageCodes && currentLanguageCodes.length !== 0) { + const currentLanguagesOption = languages.filter( + (option) => + currentLanguageCodes && currentLanguageCodes.includes(option.id) + ); + setSelectedLanguages(currentLanguagesOption); + } + + const currentGender = user.gender; + if (currentGender) { + const currentGenderOption = genders.find( + (option) => option.id === currentGender + ); + setSelectedGender(currentGenderOption); + } + + const currentAgeGroup = user.age_range; + if (currentAgeGroup) { + const ageRangeCode = `${currentAgeGroup[0]}-${currentAgeGroup[1]}`; + const currentAgeGroupOption = ageGroup.find( + (option) => option.code === ageRangeCode + ); + setSelectedAgeBucket(currentAgeGroupOption); + } + + const currentDialects = user.dialects; + setDialects(Array.isArray(currentDialects) ? currentDialects : []); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + const handleSubmit = async () => { + trackButtonClickGA("submit-get-details-button"); + + if (selectedLanguages.length === 0) { + toast({ + title: "Please select a language.", + description: "You need to select a language to proceed forward.", + status: "warning", + duration: 3000, + isClosable: true, + }); + return; + } + + await updateUserProfile( + selectedCountry, + selectedLanguages, + selectedGender, + selectedAgeBucket, + dialects + ); + window.location.href = "/workspace"; + }; + + return ( + <> + {!loading && currentUserSettings && ( + + + + Just one step more... + + + + + Select the country you currently reside in + + + + + + + Select the languages you are comfortable giving feedback on. You + should have good written fluency in the languages you specify. + + + + + + Gender + + + + + Age Range + + + + + + Self report dialect + + + + +
+ +
+
+
+ )} + + ); +} + +export default GetDetails; diff --git a/frontend/src/components/GetStartedDiscordButton.jsx b/frontend/src/components/GetStartedDiscordButton.jsx new file mode 100644 index 0000000..9eb8c65 --- /dev/null +++ b/frontend/src/components/GetStartedDiscordButton.jsx @@ -0,0 +1,47 @@ +import React, { useEffect, useState } from "react"; +import { Image } from "@chakra-ui/react"; +import { useNavigate } from "react-router-dom"; + +import { handleLogin, isAuthenticated } from "../helpers/auth.jsx"; + +export const GetStartedDiscordButton = () => { + // useNavigate hook for programmatic navigation + const navigateTo = useNavigate(); + const [authenticated, setAuthenticated] = useState(false); + + // Check if the user is already authenticated on component mount + useEffect(() => { + (async () => { + const isUserAuthenticated = await isAuthenticated(); + setAuthenticated(isUserAuthenticated); + })(); + }, []); + + const handleButtonClick = async () => { + if (!authenticated) { + await handleLogin("discord"); + const isUserAuthenticated = await isAuthenticated(); + setAuthenticated(isUserAuthenticated); + } else { + navigateTo("/workspace"); + } + }; + + return ( + + ); +}; diff --git a/frontend/src/components/GetStartedGoogleButton.jsx b/frontend/src/components/GetStartedGoogleButton.jsx new file mode 100644 index 0000000..9233a69 --- /dev/null +++ b/frontend/src/components/GetStartedGoogleButton.jsx @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from "react"; +import { Image } from "@chakra-ui/react"; +import { useNavigate } from "react-router-dom"; + +import { handleLogin, isAuthenticated } from "../helpers/auth.jsx"; + +export const GetStartedGoogleButton = () => { + // useNavigate hook for programmatic navigation + const navigateTo = useNavigate(); + const [authenticated, setAuthenticated] = useState(false); + + // Check if the user is already authenticated on component mount + useEffect(() => { + (async () => { + const isUserAuthenticated = await isAuthenticated(); + setAuthenticated(isUserAuthenticated); + })(); + }, []); + + const handleButtonClick = async () => { + if (!authenticated) { + await handleLogin("google"); + const isUserAuthenticated = await isAuthenticated(); + setAuthenticated(isUserAuthenticated); + } else { + navigateTo("/workspace"); + } + }; + + return ( + <> + {!authenticated ? ( + + ) : ( + <> + )} + + ); +}; diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx new file mode 100644 index 0000000..11037e2 --- /dev/null +++ b/frontend/src/components/Header.jsx @@ -0,0 +1,129 @@ +import React from "react"; +import { + Box, + Button, + Center, + Flex, + Heading, + IconButton, + Image, + Link, + Text, + useBreakpointValue, +} from "@chakra-ui/react"; +import { FaChartPie, FaUsers } from "react-icons/fa"; + +const Header = () => { + const breakpoint = useBreakpointValue({ base: "base", lg: "lg", xl: "xl" }); + + return ( + + + + The Aya Project + + + {breakpoint === "base" ? ( + <> +
+ + + The Aya Project + + + A C4AI Community Project + + +
+ } + color="#4368e0" + mr="0.5rem" + > + } + color="#4368e0" + ml="0.5rem" + > + + ) : ( + + + + + + The Aya Project + + + A C4AI Community Project + + + + + + + + + + + )} +
+
+
+ ); +}; + +export default Header; diff --git a/frontend/src/components/Hero.jsx b/frontend/src/components/Hero.jsx new file mode 100644 index 0000000..0b4cd25 --- /dev/null +++ b/frontend/src/components/Hero.jsx @@ -0,0 +1,166 @@ +import React from "react"; +import { + Box, + Container, + Flex, + HStack, + Image, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Text, + VStack, + useBreakpointValue, + useDisclosure, +} from "@chakra-ui/react"; + +import { GetStartedDiscordButton } from "./GetStartedDiscordButton.jsx"; +import { GetStartedGoogleButton } from "./GetStartedGoogleButton.jsx"; + +const Hero = () => { + const direction = useBreakpointValue({ base: "column", md: "row" }); + const imageBoxSize = useBreakpointValue({ + sm: "16rem", + base: "22rem", + md: "32rem", + }); + const displayImage = useBreakpointValue({ base: "none", md: "block" }); + const { isOpen, onOpen, onClose } = useDisclosure(); + + return ( + <> + + + + Sign In/Sign Up + + + + + + + + + + + + + + + Cohere For AI + + + + Aya: An Open Science Initiative to Accelerate Multilingual AI + Progress + + + Our goal is to accelerate NLP breakthroughs for the rest of the + world’s languages through open science collaboration. + + + +
+ + + + + + + + + + + + + ); +}; + +export default Hero; diff --git a/frontend/src/components/HowItWorks.jsx b/frontend/src/components/HowItWorks.jsx new file mode 100644 index 0000000..eee3a83 --- /dev/null +++ b/frontend/src/components/HowItWorks.jsx @@ -0,0 +1,192 @@ +import React, { useEffect, useState } from "react"; +import { + Box, + Button, + Card, + CardBody, + Container, + Heading, + SimpleGrid, + Stack, + Text, + useBreakpointValue, +} from "@chakra-ui/react"; +import { useNavigate } from "react-router-dom"; + +import { handleLogin, isAuthenticated } from "../helpers/auth.jsx"; +import { trackButtonClickGA } from "../helpers/ga.jsx"; + +function HowItWorks() { + const fontSize = useBreakpointValue({ base: "3xl", md: "5xl" }); + const columns = useBreakpointValue({ base: 1, md: 3 }); + const isSmallScreen = useBreakpointValue({ base: true, md: false }); + + const navigateTo = useNavigate(); + const [authenticated, setAuthenticated] = useState(false); + + // Check if the user is already authenticated on component mount + useEffect(() => { + (async () => { + const isUserAuthenticated = await isAuthenticated(); + setAuthenticated(isUserAuthenticated); + })(); + }, []); + + const handleButtonClick = async (taskId) => { + if (!authenticated) { + await handleLogin(); + const isUserAuthenticated = await isAuthenticated(); + setAuthenticated(isUserAuthenticated); + } else { + navigateTo(`/workspace?task=${taskId}`); + } + }; + + return ( + + + + How it Works? + + + + + + + Rate Model Performance + + + You will be asked to rate and edit model data to improve it. + +
+ +
+
+
+
+ + + + + Contribute Your Language + + + You can share your own examples of data that you think is + great. + +
+ +
+
+
+
+ + + + + Review User Feedback + + + Audit the work of other contributors to ensure quality and + consistency. + +
+ +
+
+
+
+
+
+
+ ); +} + +export default HowItWorks; diff --git a/frontend/src/components/LeaderBoardDisplay.jsx b/frontend/src/components/LeaderBoardDisplay.jsx new file mode 100644 index 0000000..911725e --- /dev/null +++ b/frontend/src/components/LeaderBoardDisplay.jsx @@ -0,0 +1,499 @@ +import React, { useEffect, useState } from "react"; +import { + Box, + Button, + Container, + Flex, + HStack, + Link, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, + Text, + VStack, + useMediaQuery, + useToast, +} from "@chakra-ui/react"; +import { BsFillArrowLeftCircleFill } from "react-icons/bs"; +import { useNavigate } from "react-router-dom"; +import Select from "react-select"; + +import { getLanguageOptions } from "../helpers/language.jsx"; +import { + getDailyLeaderboard, + getLanguageLeaderboard, + getOverallLeaderboard, + getWeeklyLeaderboard, +} from "../helpers/leaderboard"; +import { getUser, getUserLanguages } from "../helpers/user.jsx"; +import LeaderBoardOverallDetailsRow from "./LeaderBoardOverallDetailsRow"; +import LeaderBoardOverallRow from "./LeaderBoardOverallRow"; +import LeaderBoardRow from "./LeaderBoardRow"; + +function getMondayOfCurrentWeek(dateString) { + // Parse the date string into a Date object + let date = new Date(dateString.replace(/-/g, "/")); + + let day = date.getDay(); + let diff = date.getDate() - day + (day === 0 ? -6 : 1); // adjust when day is Sunday + + // Create new Date with calculated difference + let monday = new Date(date.setDate(diff)); + + // Format Date object to YYYY-MM-DD string + let year = monday.getFullYear(); + let month = monday.getMonth() + 1; // JavaScript months are 0-based + let dayOfMonth = monday.getDate(); + + // Pad month and day with leading zeros if necessary + month = month < 10 ? "0" + month : month; + dayOfMonth = dayOfMonth < 10 ? "0" + dayOfMonth : dayOfMonth; + + return `${year}-${month}-${dayOfMonth}`; +} + +function LeaderBoardDisplay() { + const toast = useToast(); + const [data, setData] = useState([]); + const [currentUserData, setCurrentUserData] = useState({}); + + const [userId, setUserId] = useState(null); + const [tabIndex, setTabIndex] = useState(0); + const [isLoading, setIsLoading] = useState(false); + + const [languages, setLanguages] = useState([]); + const [languageOptionsListed, setLanguageOptionsListed] = useState([]); + const [selectedLanguageCode, setSelectedLanguageCode] = useState(null); + const [userLanguages, setUserLanguages] = useState([]); + const [userLanguageOptionsListed, setUserLanguageOptionsListed] = useState( + [] + ); + const [groupOptions, setGroupOptions] = useState([]); + + const [overallData, setOverallData] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const recordsPerPage = 20; + const [isLargerScreen] = useMediaQuery("(min-width: 768px)"); + + useEffect(() => { + const user = getUser(); + if (user && user.id) { + setUserId(user.id); + } + + // Fetch the user's language list + (async () => { + const fetchedLanguages = await getUserLanguages(); + const allLanguages = await getLanguageOptions(); + setLanguages(allLanguages); + setUserLanguages(fetchedLanguages); + })(); + }, []); + + useEffect(() => { + setLanguageOptionsListed( + languages.map((option) => ({ + value: option.code, + label: option.name, + })) + ); + }, [languages]); + + useEffect(() => { + setUserLanguageOptionsListed( + userLanguages.map((option) => ({ + value: option.code, + label: option.name, + })) + ); + }, [userLanguages]); + + useEffect(() => { + const value = + userLanguageOptionsListed.length > 0 + ? userLanguageOptionsListed[0].value + : null; + + const groupOptions = [ + { + label: "My Languages", + options: userLanguageOptionsListed, + }, + { + label: "All Languages", + options: languageOptionsListed, + }, + ]; + setSelectedLanguageCode(value); + setGroupOptions(groupOptions); + }, [languageOptionsListed, userLanguages]); + + useEffect(() => { + const fetchData = async () => { + await fetchOverallLeaderboard(userId, currentPage, recordsPerPage); + }; + fetchData(); + }, [currentPage]); + + const getCurrentDate = () => { + const currentDate = new Date(); + return `${currentDate.getFullYear()}-${ + currentDate.getMonth() + 1 + }-${currentDate.getDate()}`; + }; + + const fetchDailyLeaderboard = async (userID) => { + const dailyData = await getDailyLeaderboard(userID, getCurrentDate()); + if (dailyData.records.length === 0) { + toast({ + title: "No Contributions Today", + description: + "That means if you make a contribution now, you'll be at the top of the leaderboard!", + status: "warning", + duration: 3000, + isClosable: true, + }); + } + setData(dailyData.records); + setCurrentUserData(dailyData.current_user); + }; + + const fetchWeeklyLeaderboard = async (userID) => { + const weeklyData = await getWeeklyLeaderboard( + userID, + getMondayOfCurrentWeek(getCurrentDate()) + ); + if (weeklyData.records.length === 0) { + toast({ + title: "No Contributions this week!", + description: + "That means if you make a contribution now, you'll be at the top of the leaderboard!", + status: "warning", + duration: 3000, + isClosable: true, + }); + } + setData(weeklyData.records); + setCurrentUserData(weeklyData.current_user); + }; + + const fetchOverallLeaderboard = async ( + userID, + currentPage, + recordsPerPage + ) => { + const offset = (currentPage - 1) * recordsPerPage; + const overallData = await getOverallLeaderboard( + userID, + offset, + recordsPerPage + ); + setOverallData(overallData); + if (overallData.records.length === 0) { + toast({ + title: "No Contributions ever!", + description: + "That means if you make a contribution now, you'll be at the top of the leaderboard!", + status: "warning", + duration: 3000, + isClosable: true, + }); + } + setData(overallData.records); + setCurrentUserData(overallData.current_user); + }; + + const fetchLanguageLeaderboard = async (userID) => { + const langData = await getLanguageLeaderboard(userID, selectedLanguageCode); + if (langData.records.length === 0) { + toast({ + title: "No Contributions for this Language!", + description: + "That means if you make a contribution now, you'll be at the top of the leaderboard!", + status: "warning", + duration: 3000, + isClosable: true, + }); + } + setData(langData.records); + setCurrentUserData(langData.current_user); + }; + + const handleTabChange = async (userID, index) => { + setIsLoading(true); + setTabIndex(index); + const userCurrentLanguage = userLanguageOptionsListed[0]?.value || null; + switch (index) { + case 0: + await fetchDailyLeaderboard(userID); + break; + case 1: + await fetchWeeklyLeaderboard(userID); + break; + case 2: + await fetchOverallLeaderboard(userID, currentPage, recordsPerPage); + break; + case 3: + setSelectedLanguageCode(userCurrentLanguage); + await fetchLanguageLeaderboard(userID); + break; + default: + break; + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchDailyLeaderboard(userId); + }, [userId]); + + useEffect(() => { + if (selectedLanguageCode && tabIndex === 3) { + fetchLanguageLeaderboard(userId); + } + }, [selectedLanguageCode, tabIndex]); + + const handleLanguageChange = (value) => { + setSelectedLanguageCode(value); + }; + + const handleFirstPage = () => { + setCurrentPage(1); + }; + + const handleLastPage = () => { + setCurrentPage(Math.ceil(overallData.total_count / recordsPerPage)); + }; + + const handleNextPage = () => { + if (!overallData) { + console.log("Data is not yet available"); + return; + } + + if (currentPage < Math.ceil(overallData.total_count / recordsPerPage)) { + setCurrentPage((prevPage) => prevPage + 1); + } + }; + + const handlePreviousPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + const navigate = useNavigate(); + + return ( + <> + + + + navigate("/workspace?task=1")} + /> + + Leaderboard + + + + {isLoading ? ( + Loading... + ) : ( + handleTabChange(userId, index)} + index={tabIndex} + > + + + Daily + + + Weekly + + + Overall + + + Language + + + + {/* First TabPanel */} + + + {data.map((item) => ( + + ))} + + + + + + {/* Second TabPanel */} + + + {data.map((item) => ( + + ))} + + + + + + {/* Third TabPanel */} + + + + + + {data.map((item) => ( + + ))} + + + {/* here is the current user information at the bottom of each page*/} + + + {/* Add the pagination code only in the Overall tab */} + + + + {overallData && ( + + {currentPage}/ + {Math.ceil(overallData.total_count / recordsPerPage)} + + )} + + + + + {/* Fourth TabPanel */} + + + ); +} + +export default MultiSelectDropdown; diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx new file mode 100644 index 0000000..527b749 --- /dev/null +++ b/frontend/src/components/Navbar.jsx @@ -0,0 +1,154 @@ +import React, { useEffect, useState } from "react"; +import { + Avatar, + Box, + Button, + Flex, + Image, + Link, + Menu, + MenuButton, + MenuItem, + MenuList, + Spacer, + useBreakpointValue, +} from "@chakra-ui/react"; +import { MdLeaderboard } from "react-icons/all.js"; +import { useNavigate } from "react-router-dom"; + +import { handleLogout } from "../helpers/auth.jsx"; +import { trackButtonClickGA } from "../helpers/ga.jsx"; + +// The Navbar component is responsible for displaying the header +// with the Cohere for AI logo, the leaderboard button, and the user menu. +const Navbar = ({ linkTo = "/" }) => { + const [imageSrc, setImageSrc] = useState(null); + const isSmallScreen = useBreakpointValue({ base: true, md: false }); + + const navigateTo = useNavigate(); + + const navigateToHome = () => { + navigateTo("/"); + }; + + const navigateToLeaderboard = () => { + navigateTo("/leaderboard"); + }; + + const navigateToSettings = () => { + navigateTo("/settings"); + }; + + // Fetch the user's avatar image on component mount + useEffect(() => { + const serializedUser = localStorage.getItem("user"); + const user = JSON.parse(serializedUser); + const avatarUrl = user.image_url; + fetch(avatarUrl) // Replace with your Flask endpoint URL + .then((response) => response.blob()) + .then((blob) => { + const objectUrl = URL.createObjectURL(blob); + setImageSrc(objectUrl); + }); + }, []); + + // Render the header with the logo, leaderboard button, and user menu + return ( + + + + The Aya Project + + + + + + + + + { + trackButtonClickGA("my-contributions-button"); + navigateTo("/workspace"); + }} + > + Workspace + + { + trackButtonClickGA("my-contributions-button"); + navigateTo("/contributions"); + }} + > + My Contributions + + { + trackButtonClickGA("settings-button"); + navigateToSettings(); + }} + > + Settings + + { + trackButtonClickGA("sign-out-button"); + handleLogout(); + }} + > + Logout + + + + + + ); +}; + +export default Navbar; diff --git a/frontend/src/components/PrivateRoute.jsx b/frontend/src/components/PrivateRoute.jsx new file mode 100644 index 0000000..61c2d26 --- /dev/null +++ b/frontend/src/components/PrivateRoute.jsx @@ -0,0 +1,34 @@ +import React, { useEffect, useState } from "react"; +import { Navigate, Outlet, useLocation } from "react-router-dom"; +import { useRecoilValue, useSetRecoilState } from "recoil"; + +import { isAuthenticated } from "../helpers/auth.jsx"; +import { isAuthenticatedState } from "../recoil/atoms/isAuthenticatedState.jsx"; + +const PrivateRoute = ({ element, ...rest }) => { + const [isAuthenticating, setIsAuthenticating] = useState(true); + const isAuthenticatedRecoil = useRecoilValue(isAuthenticatedState); + const setIsAuthenticatedState = useSetRecoilState(isAuthenticatedState); + const location = useLocation(); + + useEffect(() => { + const checkAuthentication = async () => { + const auth = await isAuthenticated(); + setIsAuthenticatedState(auth); + setIsAuthenticating(false); + }; + checkAuthentication(); + }, [location, setIsAuthenticatedState]); + + if (isAuthenticating) { + return null; // Or a loading spinner + } + + if (!isAuthenticatedRecoil) { + return ; + } + + return ; +}; + +export default PrivateRoute; diff --git a/frontend/src/components/SingleSelectDropdown.jsx b/frontend/src/components/SingleSelectDropdown.jsx new file mode 100644 index 0000000..3bbce13 --- /dev/null +++ b/frontend/src/components/SingleSelectDropdown.jsx @@ -0,0 +1,36 @@ +import React from "react"; +import Select from "react-select"; + +function SingleSelectDropdown(props) { + const optionsListed = props.options.map((option) => ({ + value: option.id, + label: option.name, + code: option.code, + })); + + let currentValue; + if (props.entity === "Age Range" && props.currentValue) { + const ageRangeCode = `${props.currentValue[0]}-${props.currentValue[1]}`; + currentValue = optionsListed.find((option) => option.code === ageRangeCode); + } else { + currentValue = optionsListed.find( + (option) => option.value === props.currentValue + ); + } + + return ( + { + handleLanguageChange(selectedOption.value); + }} + styles={{ + control: (provided) => ({ + ...provided, + width: 150, + borderRadius: "999px", + background: "#f0f9ff", + border: "none", + padding: "0.25rem", + "@media (min-width: 768px)": { + width: 250, + }, + }), + }} + /> + )} + + + + Prompt + + + {isPromptThumbsUp === null && ( + + )} + + + + Look out for: + + + Correct Grammar + + + + Reasonable Length + + + + Clear Instructions + + +